using System.Net;
using System.Net.Http.Json;
using System.Text.Json;
using MaBibli.Shared.Catalogue;
using MaBibli.Shared.Dtos;
using MaBibli.Shared.Entites;

namespace MaBibli.Client.Services;

/// <summary>
/// Accès à l'API depuis le client Blazor.
/// </summary>
/// <remarks>
/// Aucun cache : la phase 3 suppose le réseau disponible. Le cache hors-ligne est la phase 6.
/// </remarks>
public sealed class ServiceLivresApi(HttpClient http)
{
    private static readonly JsonSerializerOptions Json = new(JsonSerializerDefaults.Web);

    public async Task<IReadOnlyList<LivreDto>> ListerAsync(CritereLivres criteres, CancellationToken ct = default)
    {
        var parametres = new List<string>();

        if (!string.IsNullOrWhiteSpace(criteres.Recherche))
        {
            parametres.Add($"recherche={Uri.EscapeDataString(criteres.Recherche.Trim())}");
        }

        if (criteres.Format is { } format)
        {
            parametres.Add($"format={format}");
        }

        if (criteres.Statut is { } statut)
        {
            parametres.Add($"statut={statut}");
        }

        if (criteres.AuteurId is { } auteurId)
        {
            parametres.Add($"auteurId={auteurId}");
        }

        var url = "api/livres" + (parametres.Count > 0 ? "?" + string.Join('&', parametres) : string.Empty);

        return await http.GetFromJsonAsync<List<LivreDto>>(url, Json, ct) ?? [];
    }

    public async Task<LivreDto?> ObtenirAsync(int id, CancellationToken ct = default)
    {
        var reponse = await http.GetAsync($"api/livres/{id}", ct);
        return reponse.StatusCode == HttpStatusCode.NotFound
            ? null
            : await LireAsync<LivreDto>(reponse, ct);
    }

    public async Task<ResultatApi<LivreDto>> CreerAsync(EnregistrementLivre saisie, CancellationToken ct = default)
    {
        var reponse = await http.PostAsJsonAsync("api/livres", saisie, Json, ct);
        return await InterpreterAsync<LivreDto>(reponse, ct);
    }

    public async Task<ResultatApi<LivreDto>> ModifierAsync(int id, EnregistrementLivre saisie, CancellationToken ct = default)
    {
        var reponse = await http.PutAsJsonAsync($"api/livres/{id}", saisie, Json, ct);
        return await InterpreterAsync<LivreDto>(reponse, ct);
    }

    /// <summary>
    /// Change le statut de lecture <b>de l'utilisateur courant</b> ; <c>null</c> le remet à
    /// « non commencé ». Le serveur sait qui appelle : le client ne transmet jamais d'identité.
    /// </summary>
    public async Task<ResultatApi<LivreDto>> ChangerStatutAsync(
        int id, Statut? statut, CancellationToken ct = default)
    {
        var reponse = await http.PatchAsJsonAsync(
            $"api/livres/{id}/statut", new ChangementStatut { Statut = statut }, Json, ct);
        return await InterpreterAsync<LivreDto>(reponse, ct);
    }

    public async Task<IReadOnlyList<AuteurDto>> ListerAuteursAsync(CancellationToken ct = default) =>
        await http.GetFromJsonAsync<List<AuteurDto>>("api/auteurs", Json, ct) ?? [];

    public async Task<AuteurDto?> ObtenirAuteurAsync(int id, CancellationToken ct = default)
    {
        var reponse = await http.GetAsync($"api/auteurs/{id}", ct);
        return reponse.StatusCode == HttpStatusCode.NotFound
            ? null
            : await LireAsync<AuteurDto>(reponse, ct);
    }

    /// <summary>Rapprochements d'auteurs ambigus, que le serveur refuse d'appliquer seul.</summary>
    public async Task<IReadOnlyList<SuggestionRapprochement>> ListerRapprochementsAsync(
        CancellationToken ct = default) =>
        await http.GetFromJsonAsync<List<SuggestionRapprochement>>("api/auteurs/rapprochements", Json, ct) ?? [];

    public Task<string?> AccepterRapprochementAsync(int sourceId, int cibleId, CancellationToken ct = default) =>
        DeciderAsync("accepter", sourceId, cibleId, ct);

    public Task<string?> RefuserRapprochementAsync(int sourceId, int cibleId, CancellationToken ct = default) =>
        DeciderAsync("refuser", sourceId, cibleId, ct);

    private async Task<string?> DeciderAsync(string action, int sourceId, int cibleId, CancellationToken ct)
    {
        var reponse = await http.PostAsJsonAsync(
            $"api/auteurs/rapprochements/{action}",
            new DecisionRapprochement { SourceId = sourceId, CibleId = cibleId },
            Json,
            ct);

        return reponse.IsSuccessStatusCode ? null : await MessageErreurAsync(reponse, ct);
    }

    public async Task<string?> SupprimerAsync(int id, CancellationToken ct = default)
    {
        var reponse = await http.DeleteAsync($"api/livres/{id}", ct);
        return reponse.IsSuccessStatusCode ? null : await MessageErreurAsync(reponse, ct);
    }

    public async Task<ResultatLookupIsbn?> ChercherIsbnAsync(string isbn, CancellationToken ct = default)
    {
        var reponse = await http.GetAsync($"api/isbn/{Uri.EscapeDataString(isbn)}", ct);
        return reponse.IsSuccessStatusCode
            ? await LireAsync<ResultatLookupIsbn>(reponse, ct)
            : null;
    }

    public async Task<UtilisateurCourant> ObtenirUtilisateurAsync(CancellationToken ct = default)
    {
        try
        {
            return await http.GetFromJsonAsync<UtilisateurCourant>("api/moi", Json, ct)
                   ?? UtilisateurCourant.Anonyme;
        }
        catch (HttpRequestException)
        {
            return UtilisateurCourant.Anonyme;
        }
    }

    private static async Task<T?> LireAsync<T>(HttpResponseMessage reponse, CancellationToken ct)
    {
        reponse.EnsureSuccessStatusCode();
        return await reponse.Content.ReadFromJsonAsync<T>(Json, ct);
    }

    private static async Task<ResultatApi<T>> InterpreterAsync<T>(HttpResponseMessage reponse, CancellationToken ct)
    {
        if (reponse.IsSuccessStatusCode)
        {
            var valeur = await reponse.Content.ReadFromJsonAsync<T>(Json, ct);
            return new ResultatApi<T>(valeur, null);
        }

        return new ResultatApi<T>(default, await MessageErreurAsync(reponse, ct));
    }

    /// <summary>Récupère le message d'erreur de l'API pour l'afficher tel quel à l'utilisateur.</summary>
    private static async Task<string> MessageErreurAsync(HttpResponseMessage reponse, CancellationToken ct)
    {
        try
        {
            var corps = await reponse.Content.ReadFromJsonAsync<ErreurApi>(Json, ct);
            if (!string.IsNullOrWhiteSpace(corps?.Erreur))
            {
                return corps.Erreur;
            }
        }
        catch (Exception)
        {
            // Corps illisible ou vide : on retombe sur le code HTTP.
        }

        return reponse.StatusCode switch
        {
            HttpStatusCode.NotFound => "Ce livre n'existe plus.",
            _ => $"L'enregistrement a échoué ({(int)reponse.StatusCode}).",
        };
    }

    private sealed record ErreurApi(string? Erreur);
}

/// <summary>Résultat d'un appel d'écriture : la valeur, ou un message d'erreur affichable.</summary>
public readonly record struct ResultatApi<T>(T? Valeur, string? Erreur)
{
    public bool EstOk => Erreur is null;
}
