using System.Text;
using MaBibli.Api.Services.Catalogue;
using MaBibli.Api.Services.Identite;
using MaBibli.Api.Services.Souhaits;
using MaBibli.Shared.Dtos;

namespace MaBibli.Api.Endpoints;

public static class SouhaitsEndpoints
{
    /// <summary>
    /// La liste d'envies et son export.
    /// </summary>
    /// <remarks>
    /// <b>Aucun point d'entrée n'accepte de nom d'utilisateur</b>, et il ne doit jamais y en
    /// avoir : la liste rendue est toujours celle de l'appelant, identifié par les en-têtes
    /// SSOwat. C'est la même règle que pour les statuts de lecture, et elle est ici encore plus
    /// nette — le sens de la fonctionnalité est de préparer un cadeau sans que l'autre le voie.
    /// </remarks>
    public static IEndpointRouteBuilder MapSouhaitsEndpoints(this IEndpointRouteBuilder routes)
    {
        var groupe = routes.MapGroup("/api/souhaits").WithTags("Liste d'envies");

        groupe.MapGet("/", async (
                IServiceSouhaits service,
                IFournisseurUtilisateur utilisateurs,
                CancellationToken ct) =>
                Results.Ok(await service.ListerAsync(utilisateurs.Obtenir().Identifiant, ct)))
            .WithName("ListerSouhaits")
            .WithSummary("La liste d'envies de l'appelant, groupée par auteur.")
            .WithDescription(
                "Personnelle : deux membres du foyer obtiennent deux listes différentes. "
                + "Sans identité, la liste est vide.")
            .Produces<IReadOnlyList<SouhaitDto>>();

        groupe.MapPost("/", async (
                EnregistrementSouhait saisie,
                IServiceSouhaits service,
                IFournisseurUtilisateur utilisateurs,
                CancellationToken ct) =>
            {
                var resultat = await service.AjouterAsync(
                    saisie, utilisateurs.Obtenir().Identifiant, ct);

                return resultat.Erreur is not null
                    ? Results.BadRequest(new { erreur = resultat.Erreur })
                    : Results.Created($"/api/souhaits/{resultat.Souhait!.Id}", resultat.Souhait);
            })
            .WithName("AjouterSouhait")
            .WithSummary("Ajoute un livre à la liste d'envies de l'appelant.")
            .Produces<SouhaitDto>(StatusCodes.Status201Created)
            .Produces(StatusCodes.Status400BadRequest);

        groupe.MapDelete("/{id:int}", async (
                int id,
                IServiceSouhaits service,
                IFournisseurUtilisateur utilisateurs,
                CancellationToken ct) =>
            {
                var supprime = await service.SupprimerAsync(
                    id, utilisateurs.Obtenir().Identifiant, ct);

                return supprime ? Results.NoContent() : Results.NotFound();
            })
            .WithName("SupprimerSouhait")
            .WithSummary("Retire un livre de la liste d'envies de l'appelant.")
            .WithDescription(
                "404 si l'envie n'existe pas OU appartient à quelqu'un d'autre : les deux cas "
                + "sont volontairement indiscernables.")
            .Produces(StatusCodes.Status204NoContent)
            .Produces(StatusCodes.Status404NotFound);

        // Deux formats parce que les deux usages décrits dans IDEES.md diffèrent : le .txt
        // s'emporte en librairie et se lit tel quel, le .csv s'ouvre dans un tableur pour se
        // répartir des achats. Ce sont deux fichiers texte : aucune dépendance ni pour les
        // produire, ni pour les lire.
        groupe.MapGet("/export.txt", (
                IServiceSouhaits service, IFournisseurUtilisateur utilisateurs, CancellationToken ct) =>
                ExporterAsync(FormatExportSouhaits.Texte, service, utilisateurs, ct))
            .WithName("ExporterSouhaitsTexte")
            .WithSummary("Liste d'envies en texte lisible, groupée par auteur.")
            .ExcludeFromDescription();

        groupe.MapGet("/export.csv", (
                IServiceSouhaits service, IFournisseurUtilisateur utilisateurs, CancellationToken ct) =>
                ExporterAsync(FormatExportSouhaits.Csv, service, utilisateurs, ct))
            .WithName("ExporterSouhaitsCsv")
            .WithSummary("Liste d'envies en CSV, ouvrable dans un tableur.")
            .ExcludeFromDescription();

        return routes;
    }

    /// <summary>
    /// Produit le fichier d'export et le renvoie en <b>téléchargement</b>.
    /// </summary>
    /// <remarks>
    /// Le nom de fichier passé à <c>Results.File</c> pose un <c>Content-Disposition:
    /// attachment</c> : le navigateur enregistre au lieu d'afficher, y compris sur mobile où un
    /// .txt s'ouvrirait sinon dans l'onglet. C'est ce qui rend l'export réellement
    /// « emportable ».
    /// <para>
    /// Le fichier est produit ici plutôt que côté client : le client Blazor n'a pas la liste
    /// complète sous la main, et un export généré par le serveur reste correct même si l'écran
    /// affiche une vue filtrée.
    /// </para>
    /// </remarks>
    private static async Task<IResult> ExporterAsync(
        FormatExportSouhaits format,
        IServiceSouhaits service,
        IFournisseurUtilisateur utilisateurs,
        CancellationToken ct)
    {
        var utilisateur = utilisateurs.Obtenir();
        var souhaits = await service.ListerAsync(utilisateur.Identifiant, ct);
        var maintenant = DateTime.Now;

        var contenu = ExportSouhaits.Produire(
            format, souhaits, utilisateur.Affichage, maintenant);

        // Le BOM n'est mis QUE sur le CSV, où il est indispensable : sans lui, Excel lit le
        // fichier en codage hérité et affiche « Ã‰mile Zola ». Le .txt s'en passe — il est fait
        // pour être lu tel quel, et certains lecteurs simples affichent le BOM comme un
        // caractère parasite en tête de la première ligne.
        var corps = Encoding.UTF8.GetBytes(contenu);
        var octets = format == FormatExportSouhaits.Csv
            ? [.. ExportSouhaits.Bom, .. corps]
            : corps;

        return Results.File(
            octets,
            ExportSouhaits.TypeMime(format),
            ExportSouhaits.NomFichier(format, maintenant));
    }

    /// <summary>Bibliographie d'un auteur du catalogue, d'après la BnF.</summary>
    public static IEndpointRouteBuilder MapBibliographieEndpoints(this IEndpointRouteBuilder routes)
    {
        routes.MapGet("/api/auteurs/{id:int}/bibliographie", async (
                int id,
                IServiceBibliographie service,
                IFournisseurUtilisateur utilisateurs,
                CancellationToken ct) =>
            {
                var bibliographie = await service.ObtenirAsync(
                    id, utilisateurs.Obtenir().Identifiant, ct);

                return bibliographie is null ? Results.NotFound() : Results.Ok(bibliographie);
            })
            .WithName("BibliographieAuteur")
            .WithTags("Auteurs")
            .WithSummary("Ce que la BnF connaît de cet auteur, confronté à l'étagère.")
            .WithDescription(
                "« Possédé » vaut pour tout le foyer, « souhaité » seulement pour l'appelant. "
                + "Le rapprochement se fait par titre normalisé : il peut manquer un livre "
                + "possédé sous un autre titre, d'où le fait qu'aucune ligne ne soit masquée.")
            .Produces<BibliographieDto>()
            .Produces(StatusCodes.Status404NotFound);

        return routes;
    }
}
