using LiteCqrs;
using TeleWave.Api.Common;
using TeleWave.Application.Broadcast;
using TeleWave.Application.Broadcast.CreateChannel;
using TeleWave.Application.Broadcast.GetChannel;
using TeleWave.Application.Broadcast.GetSchedule;
using TeleWave.Application.Broadcast.ListChannels;
using TeleWave.Application.Broadcast.UpdateChannelSettings;
using TeleWave.Application.Broadcast.UpdateChannelTime;
using TeleWave.Application.Broadcast.UpdateViewerSettings;
using TeleWave.Application.Programming.Planning.DebugExport;
using TeleWave.Application.Programming.Planning.Trace;
using TeleWave.Domain.Broadcast;
using TeleWave.Infrastructure.Identity;

namespace TeleWave.Api.Endpoints;

/// <summary>
/// Админ-эндпоинты канала: создание, список, настройки, время и чтение расписания. Заставки
/// и стыки общие для всех каналов и живут своими разделами (<c>BumperEndpoints</c>,
/// <c>JunctionEndpoints</c>); что и когда идёт в эфире, задаёт шаблон сетки (<c>TemplateEndpoints</c>).
/// </summary>
public static class ChannelEndpoints
{
    public static IEndpointRouteBuilder MapChannelEndpoints(this IEndpointRouteBuilder app)
    {
        var admin = app.MapGroup("/api/admin/channels")
            .WithTags("Admin.Channels")
            .RequireAuthorization(policy => policy.RequireRole(RoleNames.Admin));

        admin.MapPost("", CreateChannel).Produces<CreatedIdResponse>(StatusCodes.Status201Created);
        admin.MapGet("", ListChannels).Produces<IReadOnlyList<ChannelSummaryDto>>();
        admin.MapGet("/{id:guid}", GetChannel).Produces<ChannelDto>();
        admin
            .MapPut("/{id:guid}/settings", UpdateSettings)
            .Produces(StatusCodes.Status204NoContent);
        admin.MapPut("/{id:guid}/time", UpdateTime).Produces(StatusCodes.Status204NoContent);

        admin
            .MapGet("/{id:guid}/schedule", GetSchedule)
            .Produces<IReadOnlyList<ScheduleEntryDto>>();
        admin
            .MapPut("/{id:guid}/viewer", UpdateViewerSettings)
            .Produces(StatusCodes.Status204NoContent);

        // «Почему это здесь»: цепочка происхождения записи, записанная в момент генерации.
        admin.MapGet("/entries/{entryId:guid}/trace", GetEntryTrace).Produces<EntryTraceDto>();

        // Отладочный дамп: вход планировщика и результат одним архивом.
        admin.MapPost("/{id:guid}/debug-export", ExportDebug).Produces(StatusCodes.Status200OK);

        return app;
    }

    /// <summary>
    /// Отдаёт архив со снимком канала. POST, а не GET: команда оставляет копию дампа на сервере,
    /// и кэшировать такой ответ никому не следует.
    /// </summary>
    private static async Task<IResult> ExportDebug(
        Guid id,
        ISender sender,
        CancellationToken cancellationToken
    )
    {
        var result = await sender.Send(new ExportChannelDebugCommand(id), cancellationToken);
        if (!result.IsSuccess)
            return result.ToHttpResult();

        return Results.File(result.Value.Content, "application/zip", result.Value.FileName);
    }

    private static async Task<IResult> UpdateViewerSettings(
        Guid id,
        UpdateViewerSettingsBody body,
        ISender sender,
        CancellationToken cancellationToken
    )
    {
        var result = await sender.Send(
            new UpdateViewerSettingsCommand(
                id,
                body.LogoImageId,
                body.LogoCorner,
                body.LogoOpacity,
                body.ShowClock,
                body.AnalogFilterStrength
            ),
            cancellationToken
        );
        return result.ToHttpResult();
    }

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

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

    private static async Task<IResult> ListChannels(
        ISender sender,
        CancellationToken cancellationToken
    )
    {
        var result = await sender.Send(new ListChannelsQuery(), cancellationToken);
        return Results.Ok(result);
    }

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

    private static async Task<IResult> UpdateTime(
        Guid id,
        UpdateChannelTimeBody body,
        ISender sender,
        CancellationToken cancellationToken
    )
    {
        var result = await sender.Send(
            new UpdateChannelTimeCommand(id, body.Number, body.UtcOffsetMinutes, body.DayStartTime),
            cancellationToken
        );
        return result.ToHttpResult();
    }

    private static async Task<IResult> UpdateSettings(
        Guid id,
        UpdateChannelSettingsBody body,
        ISender sender,
        CancellationToken cancellationToken
    )
    {
        var result = await sender.Send(
            new UpdateChannelSettingsCommand(id, body.Name, body.IsEnabled, body.FillerAssetId),
            cancellationToken
        );
        return result.ToHttpResult();
    }

    private static async Task<IResult> GetSchedule(
        Guid id,
        DateTimeOffset? from,
        DateTimeOffset? to,
        ISender sender,
        CancellationToken cancellationToken
    )
    {
        var fromUtc = from ?? DateTimeOffset.UtcNow;
        var toUtc = to ?? fromUtc.AddDays(1);
        var result = await sender.Send(
            new GetChannelScheduleQuery(id, fromUtc, toUtc),
            cancellationToken
        );
        return result.ToHttpResult();
    }
}

/// <summary>Номер канала и его время: смещение от UTC и начало вещательных суток.</summary>
public sealed record UpdateChannelTimeBody(
    int? Number,
    int UtcOffsetMinutes,
    TimeOnly DayStartTime
);

public sealed record UpdateChannelSettingsBody(string Name, bool IsEnabled, Guid? FillerAssetId);

/// <summary>Оверлеи и аналоговый фильтр — как канал выглядит у зрителя (см. 6.8).</summary>
public sealed record UpdateViewerSettingsBody(
    Guid? LogoImageId,
    LogoCorner LogoCorner,
    double LogoOpacity,
    bool ShowClock,
    double AnalogFilterStrength
);
