using LiteCqrs;
using TeleWave.Api.Common;
using TeleWave.Application.Library;
using TeleWave.Application.Library.AddEpisode;
using TeleWave.Application.Library.BulkTagShows;
using TeleWave.Application.Library.CreateShow;
using TeleWave.Application.Library.DeleteShow;
using TeleWave.Application.Library.GetShow;
using TeleWave.Application.Library.GetShowUsage;
using TeleWave.Application.Library.ListShows;
using TeleWave.Application.Library.RemoveEpisode;
using TeleWave.Application.Library.RenameShow;
using TeleWave.Application.Library.SetShowAudience;
using TeleWave.Application.Library.SetShowGenres;
using TeleWave.Application.Library.SetShowOriginalName;
using TeleWave.Application.Metadata.EnrichShows;
using TeleWave.Domain.Library;
using TeleWave.Infrastructure.Identity;

namespace TeleWave.Api.Endpoints;

public static class ShowEndpoints
{
    public static IEndpointRouteBuilder MapShowEndpoints(this IEndpointRouteBuilder app)
    {
        var admin = app.MapGroup("/api/admin/shows")
            .WithTags("Admin.Shows")
            .RequireAuthorization(policy => policy.RequireRole(RoleNames.Admin));

        admin.MapPost("", CreateShow).Produces<CreatedIdResponse>(StatusCodes.Status201Created);
        admin.MapGet("", ListShows).Produces<IReadOnlyList<ShowSummaryDto>>();
        admin.MapGet("/{id:guid}", GetShow).Produces<ShowDto>();
        admin.MapPut("/{id:guid}/name", Rename).Produces(StatusCodes.Status204NoContent);
        admin
            .MapPut("/{id:guid}/original-name", SetOriginalName)
            .Produces(StatusCodes.Status204NoContent);
        admin.MapPut("/{id:guid}/audience", SetAudience).Produces(StatusCodes.Status204NoContent);
        admin.MapPut("/{id:guid}/genres", SetGenres).Produces(StatusCodes.Status204NoContent);
        admin.MapGet("/{id:guid}/usage", GetUsage).Produces<ShowUsageDto>();
        admin.MapDelete("/{id:guid}", DeleteShow).Produces(StatusCodes.Status204NoContent);

        // Массовые операции над отмеченными шоу: разметка руками и обогащение из источника.
        admin.MapPost("/bulk/tag", BulkTag).Produces<BulkTaggedResponse>();
        admin.MapPost("/bulk/enrich", BulkEnrich).Produces<EnrichShowsResultDto>();
        admin
            .MapPost("/{id:guid}/episodes", AddEpisode)
            .Produces<CreatedIdResponse>(StatusCodes.Status201Created);
        admin
            .MapDelete("/{id:guid}/episodes/{episodeId:guid}", RemoveEpisode)
            .Produces(StatusCodes.Status204NoContent);

        return app;
    }

    private static async Task<IResult> CreateShow(
        CreateShowCommand command,
        ISender sender,
        CancellationToken cancellationToken
    )
    {
        var result = await sender.Send(command, cancellationToken);
        return result.IsSuccess
            ? Results.Created(
                $"/api/admin/shows/{result.Value}",
                new CreatedIdResponse(result.Value)
            )
            : result.ToHttpResult();
    }

    private static async Task<IResult> ListShows(
        ISender sender,
        CancellationToken cancellationToken,
        Guid? genreId = null,
        bool interstitials = false
    )
    {
        var result = await sender.Send(
            new ListShowsQuery(genreId, interstitials),
            cancellationToken
        );
        return Results.Ok(result);
    }

    private static async Task<IResult> GetShow(
        Guid id,
        ISender sender,
        CancellationToken cancellationToken
    )
    {
        var result = await sender.Send(new GetShowQuery(id), cancellationToken);
        return result.ToHttpResult();
    }

    private static async Task<IResult> Rename(
        Guid id,
        RenameShowBody body,
        ISender sender,
        CancellationToken cancellationToken
    )
    {
        var result = await sender.Send(new RenameShowCommand(id, body.Name), cancellationToken);
        return result.ToHttpResult();
    }

    private static async Task<IResult> SetOriginalName(
        Guid id,
        SetShowOriginalNameBody body,
        ISender sender,
        CancellationToken cancellationToken
    )
    {
        var result = await sender.Send(
            new SetShowOriginalNameCommand(id, body.OriginalName),
            cancellationToken
        );
        return result.ToHttpResult();
    }

    private static async Task<IResult> SetAudience(
        Guid id,
        SetShowAudienceBody body,
        ISender sender,
        CancellationToken cancellationToken
    )
    {
        var result = await sender.Send(
            new SetShowAudienceCommand(id, body.Audience),
            cancellationToken
        );
        return result.ToHttpResult();
    }

    private static async Task<IResult> SetGenres(
        Guid id,
        SetShowGenresBody body,
        ISender sender,
        CancellationToken cancellationToken
    )
    {
        var result = await sender.Send(
            new SetShowGenresCommand(id, body.GenreIds, body.PrimaryGenreId),
            cancellationToken
        );
        return result.ToHttpResult();
    }

    /// <param name="withMedia">Снести заодно файлы шоу — иначе они останутся осиротевшими.</param>
    /// <param name="withSchedule">Вырезать шоу из будущего эфира вместо отказа.</param>
    private static async Task<IResult> DeleteShow(
        Guid id,
        ISender sender,
        CancellationToken cancellationToken,
        bool withMedia = false,
        bool withSchedule = false
    )
    {
        var result = await sender.Send(
            new DeleteShowCommand(id, withMedia, withSchedule),
            cancellationToken
        );
        return result.ToHttpResult();
    }

    private static async Task<IResult> AddEpisode(
        Guid id,
        AddEpisodeBody body,
        ISender sender,
        CancellationToken cancellationToken
    )
    {
        var result = await sender.Send(
            new AddEpisodeCommand(id, body.MediaAssetId),
            cancellationToken
        );
        return result.IsSuccess
            ? Results.Created($"/api/admin/shows/{id}", new CreatedIdResponse(result.Value))
            : result.ToHttpResult();
    }

    private static async Task<IResult> RemoveEpisode(
        Guid id,
        Guid episodeId,
        ISender sender,
        CancellationToken cancellationToken
    )
    {
        var result = await sender.Send(new RemoveEpisodeCommand(id, episodeId), cancellationToken);
        return result.ToHttpResult();
    }

    private static async Task<IResult> GetUsage(
        Guid id,
        ISender sender,
        CancellationToken cancellationToken
    )
    {
        var result = await sender.Send(new GetShowUsageQuery(id), cancellationToken);
        return result.ToHttpResult();
    }

    private static async Task<IResult> BulkTag(
        BulkTagShowsBody body,
        ISender sender,
        CancellationToken cancellationToken
    )
    {
        var result = await sender.Send(
            new BulkTagShowsCommand(
                body.ShowIds,
                body.GenreIds,
                body.ReplaceGenres,
                body.Audience,
                body.SetAudience
            ),
            cancellationToken
        );
        return result.IsSuccess
            ? Results.Ok(new BulkTaggedResponse(result.Value))
            : result.ToHttpResult();
    }

    private static async Task<IResult> BulkEnrich(
        BulkEnrichBody body,
        ISender sender,
        CancellationToken cancellationToken
    )
    {
        var result = await sender.Send(
            new EnrichShowsMetadataCommand(body.ShowIds, body.Provider),
            cancellationToken
        );
        return result.ToHttpResult();
    }
}

public sealed record AddEpisodeBody(Guid MediaAssetId);

/// <summary>Массовая разметка: жанры и рейтинг применяются независимо друг от друга.</summary>
public sealed record BulkTagShowsBody(
    IReadOnlyList<Guid> ShowIds,
    IReadOnlyList<Guid>? GenreIds = null,
    bool ReplaceGenres = false,
    ShowAudience? Audience = null,
    bool SetAudience = false
);

public sealed record BulkEnrichBody(IReadOnlyList<Guid> ShowIds, string Provider);

/// <summary>Скольких шоу коснулась массовая правка.</summary>
public sealed record BulkTaggedResponse(int Updated);

public sealed record RenameShowBody(string Name);

public sealed record SetShowOriginalNameBody(string? OriginalName);

/// <summary>Рейтинг шоу; null — снять проставленный.</summary>
public sealed record SetShowAudienceBody(ShowAudience? Audience);

public sealed record SetShowGenresBody(IReadOnlyList<Guid> GenreIds, Guid? PrimaryGenreId);
