using MaBibli.Api.Services.Identite;
using MaBibli.Api.Services.Series;
using MaBibli.Shared.Dtos;

namespace MaBibli.Api.Endpoints;

public static class SeriesEndpoints
{
    /// <summary>
    /// Sagas, cycles et séries : ce qui se lit dans un ordre, et ce qui manque pour le lire.
    /// </summary>
    /// <remarks>
    /// ⚠️ <b>Aucun point d'entrée n'accepte ni ne rend d'identité</b>, à une exception près :
    /// la mise en envies. Les séries sont <b>communes au foyer</b>, comme le catalogue et les
    /// prêts — l'ordre de lecture d'une saga ne dépend pas de qui regarde. La liste d'envies,
    /// elle, reste personnelle, et c'est le serveur qui sait à qui il parle.
    /// <para>
    /// La lecture est <b>unique</b> : <c>GET /api/series</c> rend tout l'arbre à plat. Le détail
    /// d'une série s'y lit sans second appel, ce qui donne un seul instantané hors-ligne à
    /// tenir à jour plutôt qu'un par série consultée.
    /// </para>
    /// </remarks>
    public static IEndpointRouteBuilder MapSeriesEndpoints(this IEndpointRouteBuilder routes)
    {
        var groupe = routes.MapGroup("/api/series").WithTags("Séries");

        groupe.MapGet("/", async (IServiceSeries service, CancellationToken ct) =>
                Results.Ok(await service.ListerAsync(ct)))
            .WithName("ListerSeries")
            .WithSummary("Toutes les séries, à plat, chacune avec ses tomes dans l'ordre de lecture.")
            .WithDescription(
                "Le client rebâtit l'arbre à partir de « serieParenteId ». Un tome sans "
                + "« livreId » est un tome qu'on ne possède pas : c'est ce que l'écran montre.")
            .Produces<IReadOnlyList<SerieDto>>();

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

                return resultat.Erreur is not null
                    ? Results.BadRequest(new { erreur = resultat.Erreur })
                    : Results.Created($"/api/series/{resultat.Serie!.Id}", resultat.Serie);
            })
            .WithName("CreerSerie")
            .WithSummary("Crée une série. « AjoutePar » est déterminé par le serveur, et n'est qu'une trace.")
            .Produces<SerieDto>(StatusCodes.Status201Created)
            .Produces(StatusCodes.Status400BadRequest);

        groupe.MapPut("/{id:int}", async (
                int id, EnregistrementSerie saisie, IServiceSeries service, CancellationToken ct) =>
                Repondre(await service.ModifierAsync(id, saisie, ct)))
            .WithName("ModifierSerie")
            .WithSummary("Renomme la série, ou la range dans un cycle.")
            .Produces<SerieDto>()
            .Produces(StatusCodes.Status400BadRequest)
            .Produces(StatusCodes.Status404NotFound);

        groupe.MapDelete("/{id:int}", async (int id, IServiceSeries service, CancellationToken ct) =>
                await service.SupprimerAsync(id, ct) ? Results.NoContent() : Results.NotFound())
            .WithName("SupprimerSerie")
            .WithSummary("Supprime la série. Ses sous-séries remontent à la racine, elles ne sont pas détruites.")
            .Produces(StatusCodes.Status204NoContent)
            .Produces(StatusCodes.Status404NotFound);

        groupe.MapPost("/{id:int}/elements", async (
                int id, AjoutElementSerie saisie, IServiceSeries service, CancellationToken ct) =>
                Repondre(await service.AjouterElementAsync(id, saisie, ct)))
            .WithName("AjouterElementSerie")
            .WithSummary("Ajoute un tome en fin de série : un livre du catalogue, ou un simple titre.")
            .Produces<SerieDto>()
            .Produces(StatusCodes.Status400BadRequest)
            .Produces(StatusCodes.Status404NotFound);

        groupe.MapPut("/{id:int}/ordre", async (
                int id, OrdreElementsSerie ordre, IServiceSeries service, CancellationToken ct) =>
                Repondre(await service.ReordonnerAsync(id, ordre.Ids, ct)))
            .WithName("ReordonnerSerie")
            .WithSummary("Fixe l'ordre de lecture — la liste entière des tomes, dans l'ordre voulu.")
            .Produces<SerieDto>()
            .Produces(StatusCodes.Status404NotFound);

        groupe.MapPut("/{id:int}/sous-series/ordre", async (
                int id, OrdreSeries ordre, IServiceSeries service, CancellationToken ct) =>
                Repondre(await service.ReordonnerSousSeriesAsync(id, ordre.Ids, ct)))
            .WithName("ReordonnerSousSeries")
            .WithSummary("Fixe l'ordre de lecture des séries directement contenues dans un cycle.")
            .Produces<SerieDto>()
            .Produces(StatusCodes.Status404NotFound);

        groupe.MapPut("/elements/{elementId:int}/livre", async (
                int elementId,
                RattachementLivre rattachement,
                IServiceSeries service,
                CancellationToken ct) =>
                Repondre(await service.RattacherLivreAsync(elementId, rattachement.LivreId, ct)))
            .WithName("RattacherLivreSerie")
            .WithSummary("Rattache un livre possédé à une place, ou l'en détache (« livreId » nul).")
            .WithDescription(
                "Détacher laisse un trou NOMMÉ : le titre du tome est conservé, il ne dépend "
                + "pas de l'exemplaire.")
            .Produces<SerieDto>()
            .Produces(StatusCodes.Status400BadRequest)
            .Produces(StatusCodes.Status404NotFound);

        groupe.MapDelete("/elements/{elementId:int}", async (
                int elementId, IServiceSeries service, CancellationToken ct) =>
                await service.RetirerElementAsync(elementId, ct)
                    ? Results.NoContent()
                    : Results.NotFound())
            .WithName("RetirerElementSerie")
            .WithSummary("Retire une place de la série. Le livre, lui, reste au catalogue.")
            .Produces(StatusCodes.Status204NoContent)
            .Produces(StatusCodes.Status404NotFound);

        groupe.MapPost("/elements/{elementId:int}/souhait", async (
                int elementId,
                IServiceSeries service,
                IFournisseurUtilisateur utilisateurs,
                CancellationToken ct) =>
            {
                // ⚠️ Le seul endroit où une série (commune) touche à une liste d'envies
                // (personnelle). L'envie créée est celle de l'appelant, jamais celle du foyer.
                var resultat = await service.MettreEnEnviesAsync(
                    elementId, utilisateurs.Obtenir().Identifiant, ct);

                return resultat.Erreur is not null
                    ? Results.BadRequest(new { erreur = resultat.Erreur })
                    : Results.Ok(resultat.Souhait);
            })
            .WithName("SouhaiterTomeManquant")
            .WithSummary("Met un tome manquant dans la liste d'envies de l'appelant.")
            .Produces<SouhaitDto>()
            .Produces(StatusCodes.Status400BadRequest);

        return routes;
    }

    private static IResult Repondre(ResultatSerie resultat)
    {
        if (resultat.EstIntrouvable)
        {
            return Results.NotFound();
        }

        return resultat.Erreur is not null
            ? Results.BadRequest(new { erreur = resultat.Erreur })
            : Results.Ok(resultat.Serie);
    }
}

/// <summary>Charge utile du rattachement d'un livre à une place. <c>null</c> détache.</summary>
public record RattachementLivre
{
    public int? LivreId { get; set; }
}
