using LiteCqrs;
using TeleWave.Api.Common;
using TeleWave.Application.Programming.Planning.ApplyTemplate;
using TeleWave.Application.Programming.Planning.Diff;
using TeleWave.Application.Programming.Planning.Preview;
using TeleWave.Application.Programming.Templates;
using TeleWave.Application.Programming.Templates.CopyTemplate;
using TeleWave.Application.Programming.Templates.CreateSlot;
using TeleWave.Application.Programming.Templates.CreateTemplate;
using TeleWave.Application.Programming.Templates.DeleteSlot;
using TeleWave.Application.Programming.Templates.Generate;
using TeleWave.Application.Programming.Templates.GetTemplate;
using TeleWave.Application.Programming.Templates.Layers;
using TeleWave.Application.Programming.Templates.Reset;
using TeleWave.Application.Programming.Templates.Restore;
using TeleWave.Application.Programming.Templates.Transfer;
using TeleWave.Application.Programming.Templates.UpdateSlot;
using TeleWave.Application.Programming.Templates.Validate;
using TeleWave.Infrastructure.Identity;

namespace TeleWave.Api.Endpoints;

/// <summary>
/// Сетка канала: шаблон, слои, слоты. Правка ничего не двигает в эфире — она помечает шаблон
/// изменённым, а хвост пересобирается отдельной командой применения.
/// </summary>
public static class TemplateEndpoints
{
    public static IEndpointRouteBuilder MapTemplateEndpoints(this IEndpointRouteBuilder app)
    {
        var admin = app.MapGroup("/api/admin")
            .WithTags("Admin.Templates")
            .RequireAuthorization(policy => policy.RequireRole(RoleNames.Admin));

        admin
            .MapGet("/channels/{channelId:guid}/template", GetTemplate)
            .Produces<ScheduleTemplateDto>();
        // Завести сетку каналу, у которого её нет (напр. пережившему снос старой ротации).
        admin
            .MapPost("/channels/{channelId:guid}/template", CreateTemplate)
            .Produces<CreatedIdResponse>(StatusCodes.Status201Created);
        admin
            .MapPut("/templates/{templateId:guid}", UpdateTemplate)
            .Produces(StatusCodes.Status204NoContent);

        // Применение правил к эфиру — отдельным действием: правка слотов эфир не двигает.
        admin
            .MapPost("/channels/{channelId:guid}/template/apply", ApplyTemplate)
            .Produces<ApplyResultDto>();
        // Откат правок к состоянию, по которому идёт эфир. Ленту не трогает — она и так по нему.
        admin
            .MapPost("/channels/{channelId:guid}/template/restore", RestoreTemplate)
            .Produces<RestoreTemplateResultDto>();
        // Сброс сетки вместе с собранным по ней будущим эфиром. Прошлое и идущая запись остаются.
        admin
            .MapPost("/channels/{channelId:guid}/template/reset", ResetTemplate)
            .Produces<ResetTemplateResultDto>();
        // Предпросмотр — тот же генератор, но без записи и без продвижения курсоров.
        admin
            .MapGet("/channels/{channelId:guid}/template/preview", PreviewTemplate)
            .Produces<SchedulePreviewDto>();
        // Проверки по правилам — только по шаблону, без прогона генератора.
        admin
            .MapGet("/channels/{channelId:guid}/template/issues", ValidateTemplate)
            .Produces<IReadOnlyList<TemplateIssueDto>>();
        // Что изменится в эфире, если применить прямо сейчас.
        admin
            .MapGet("/channels/{channelId:guid}/template/diff", DiffTemplate)
            .Produces<ScheduleDiffDto>();
        admin
            .MapPost(
                "/channels/{channelId:guid}/template/copy-to/{targetChannelId:guid}",
                CopyTemplate
            )
            .Produces<CopyTemplateResultDto>();

        // Автосборка сетки: каталог профилей, план по выбранному и его создание. План считается
        // одним и тем же кодом, поэтому предпросмотр показывает ровно то, что создаст кнопка.
        admin
            .MapGet("/grid-profiles", ListGridProfiles)
            .Produces<IReadOnlyList<GridProfileDto>>();
        admin
            .MapGet("/channels/{channelId:guid}/template/grid-plan", PreviewGrid)
            .Produces<GridPlanDto>();
        admin
            .MapPost("/channels/{channelId:guid}/template/generate", GenerateGrid)
            .Produces<GenerateGridResultDto>();

        // Обмен конфигурацией: выгрузка файлом, загрузка чужого файла и сборка запроса к ИИ.
        // Запрос никуда не отправляется — админ копирует его в свою модель и приносит ответ назад.
        admin
            .MapGet("/channels/{channelId:guid}/template/export", ExportGrid)
            .Produces<GridConfig>();
        admin
            .MapPost("/channels/{channelId:guid}/template/import", ImportGrid)
            .Produces<GridImportResultDto>();
        admin
            .MapPost("/channels/{channelId:guid}/template/ai-prompt", BuildPrompt)
            .Produces<GridPromptDto>();

        admin
            .MapPost("/templates/{templateId:guid}/layers", CreateLayer)
            .Produces<CreatedIdResponse>(StatusCodes.Status201Created);
        admin
            .MapPut("/layers/{layerId:guid}", UpdateLayer)
            .Produces(StatusCodes.Status204NoContent);
        admin
            .MapDelete("/layers/{layerId:guid}", DeleteLayer)
            .Produces(StatusCodes.Status204NoContent);

        admin
            .MapPost("/layers/{layerId:guid}/slots", CreateSlot)
            .Produces<CreatedIdResponse>(StatusCodes.Status201Created);
        admin.MapPut("/slots/{slotId:guid}", UpdateSlot).Produces(StatusCodes.Status204NoContent);
        admin
            .MapDelete("/slots/{slotId:guid}", DeleteSlot)
            .Produces(StatusCodes.Status204NoContent);

        return app;
    }

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

    private static async Task<IResult> CreateTemplate(
        Guid channelId,
        ISender sender,
        CancellationToken cancellationToken
    )
    {
        var result = await sender.Send(
            new CreateChannelTemplateCommand(channelId),
            cancellationToken
        );
        return result.IsSuccess
            ? Results.Created(
                $"/api/admin/channels/{channelId}/template",
                new CreatedIdResponse(result.Value)
            )
            : result.ToHttpResult();
    }

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

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

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

    private static async Task<IResult> PreviewTemplate(
        Guid channelId,
        ISender sender,
        CancellationToken cancellationToken,
        int days = 1
    )
    {
        var result = await sender.Send(
            new PreviewScheduleQuery(channelId, days),
            cancellationToken
        );
        return result.ToHttpResult();
    }

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

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

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

    private static async Task<IResult> PreviewGrid(
        Guid channelId,
        GridProfileKind profile,
        GridGenerationMode mode,
        ISender sender,
        CancellationToken cancellationToken
    )
    {
        var result = await sender.Send(
            new PreviewGeneratedGridQuery(channelId, profile, mode),
            cancellationToken
        );
        return result.ToHttpResult();
    }

    private static async Task<IResult> GenerateGrid(
        Guid channelId,
        GenerateGridBody body,
        ISender sender,
        CancellationToken cancellationToken
    )
    {
        var result = await sender.Send(
            new GenerateGridCommand(channelId, body.Profile, body.Mode),
            cancellationToken
        );
        return result.ToHttpResult();
    }

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

    private static async Task<IResult> ImportGrid(
        Guid channelId,
        ImportGridBody body,
        ISender sender,
        CancellationToken cancellationToken
    )
    {
        var result = await sender.Send(
            new ImportGridCommand(channelId, body.Config, body.Replace),
            cancellationToken
        );
        return result.ToHttpResult();
    }

    private static async Task<IResult> BuildPrompt(
        Guid channelId,
        GridPromptBody body,
        ISender sender,
        CancellationToken cancellationToken
    )
    {
        var result = await sender.Send(
            new BuildGridPromptQuery(channelId, body.References ?? [], body.Notes),
            cancellationToken
        );
        return result.ToHttpResult();
    }

    private static async Task<IResult> CopyTemplate(
        Guid channelId,
        Guid targetChannelId,
        ISender sender,
        CancellationToken cancellationToken
    )
    {
        var result = await sender.Send(
            new CopyTemplateCommand(channelId, targetChannelId),
            cancellationToken
        );
        return result.ToHttpResult();
    }

    private static async Task<IResult> UpdateTemplate(
        Guid templateId,
        UpdateTemplateBody body,
        ISender sender,
        CancellationToken cancellationToken
    )
    {
        var result = await sender.Send(
            new UpdateTemplateCommand(
                templateId,
                body.Name,
                body.FallbackGroupId,
                body.DefaultJunctionId,
                body.Rules
            ),
            cancellationToken
        );
        return result.ToHttpResult();
    }

    private static async Task<IResult> CreateLayer(
        Guid templateId,
        CreateLayerBody body,
        ISender sender,
        CancellationToken cancellationToken
    )
    {
        var result = await sender.Send(
            new CreateLayerCommand(templateId, body.Name, body.Priority),
            cancellationToken
        );
        return result.IsSuccess
            ? Results.Created(
                $"/api/admin/layers/{result.Value}",
                new CreatedIdResponse(result.Value)
            )
            : result.ToHttpResult();
    }

    private static async Task<IResult> UpdateLayer(
        Guid layerId,
        UpdateLayerBody body,
        ISender sender,
        CancellationToken cancellationToken
    )
    {
        var result = await sender.Send(
            new UpdateLayerCommand(
                layerId,
                body.Name,
                body.Priority,
                body.Applicability,
                body.IsEnabled
            ),
            cancellationToken
        );
        return result.ToHttpResult();
    }

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

    private static async Task<IResult> CreateSlot(
        Guid layerId,
        SlotInput input,
        ISender sender,
        CancellationToken cancellationToken
    )
    {
        var result = await sender.Send(new CreateSlotCommand(layerId, input), cancellationToken);
        return result.IsSuccess
            ? Results.Created(
                $"/api/admin/slots/{result.Value}",
                new CreatedIdResponse(result.Value)
            )
            : result.ToHttpResult();
    }

    private static async Task<IResult> UpdateSlot(
        Guid slotId,
        SlotInput input,
        ISender sender,
        CancellationToken cancellationToken
    )
    {
        var result = await sender.Send(new UpdateSlotCommand(slotId, input), cancellationToken);
        return result.ToHttpResult();
    }

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

public sealed record UpdateTemplateBody(
    string Name,
    Guid? FallbackGroupId,
    Guid? DefaultJunctionId,
    PlanningRules? Rules
);

public sealed record GenerateGridBody(GridProfileKind Profile, GridGenerationMode Mode);

public sealed record ImportGridBody(GridConfig Config, bool Replace);

public sealed record GridPromptBody(IReadOnlyList<string>? References, string? Notes);

public sealed record CreateLayerBody(string Name, int Priority);

public sealed record UpdateLayerBody(
    string Name,
    int Priority,
    LayerApplicability? Applicability,
    bool IsEnabled
);
