using System.Text;
using LiteCqrs;
using TeleWave.Api.Common;
using TeleWave.Application.Broadcast;
using TeleWave.Application.Broadcast.Bumpers;
using TeleWave.Application.Common.Interfaces;
using TeleWave.Domain.Broadcast;
using TeleWave.Infrastructure.Media;

namespace TeleWave.Api.Endpoints;

/// <summary>Эндпоинты ТВ-заставок канала: блоки (стиль/аудио/фон), подблоки и рендер превью.</summary>
public static partial class ChannelEndpoints
{
    private static async Task<IResult> AddBumperTemplate(
        Guid id,
        AddBumperTemplateBody body,
        ISender sender,
        CancellationToken cancellationToken
    )
    {
        var result = await sender.Send(
            new AddBumperTemplateCommand(id, body.Name),
            cancellationToken
        );
        return result.IsSuccess
            ? Results.Created($"/api/admin/channels/{id}", new CreatedIdResponse(result.Value))
            : result.ToHttpResult();
    }

    private static async Task<IResult> UpdateBumperTemplate(
        Guid id,
        Guid templateId,
        UpdateBumperTemplateBody body,
        ISender sender,
        CancellationToken cancellationToken
    )
    {
        var result = await sender.Send(
            new UpdateBumperTemplateCommand(
                id,
                templateId,
                body.Name,
                body.BackgroundColor,
                body.BackgroundColor2,
                body.AccentColor,
                body.TextColor
            ),
            cancellationToken
        );
        return result.ToHttpResult();
    }

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

    private static async Task<IResult> UploadTemplateAudio(
        [AsParameters] BumperAudioUpload upload,
        HttpRequest request,
        IBumperTemplateStorage storage,
        IAudioProbe probe,
        ISender sender,
        CancellationToken cancellationToken
    )
    {
        if (
            ResolveBumperExtension(upload.FileName, request, BumperFiles.AudioExtensions)
            is not { } ext
        )
            return ChannelErrors.InvalidBumperFile.ToProblem();

        await storage.SaveAudioAsync(upload.TemplateId, ext, request.Body, cancellationToken);

        // Длина заставки идёт по длине звука — замеряем ffprobe (при неудаче 0 → дефолтная длина).
        var path = storage.AudioPath(upload.TemplateId, ext);
        var duration = path is null
            ? null
            : await probe.TryGetDurationAsync(path, cancellationToken);

        var result = await sender.Send(
            new SetBumperTemplateAudioCommand(
                upload.Id,
                upload.TemplateId,
                ext,
                duration?.TotalSeconds ?? 0
            ),
            cancellationToken
        );
        if (!result.IsSuccess)
            await storage.DeleteAudioAsync(upload.TemplateId, cancellationToken);
        return result.ToHttpResult();
    }

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

    private static async Task<IResult> SetTemplateBackground(
        Guid id,
        Guid templateId,
        SetBumperTemplateBackgroundBody body,
        ISender sender,
        CancellationToken cancellationToken
    )
    {
        var result = await sender.Send(
            new SetBumperTemplateBackgroundCommand(id, templateId, body.ImageId),
            cancellationToken
        );
        return result.ToHttpResult();
    }

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

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

    private static async Task<IResult> UpdateBumperVariant(
        Guid id,
        Guid templateId,
        Guid variantId,
        UpdateBumperVariantBody body,
        ISender sender,
        CancellationToken cancellationToken
    )
    {
        var result = await sender.Send(
            new UpdateBumperTextVariantCommand(
                id,
                templateId,
                variantId,
                body.Name,
                body.Kind,
                body.NowLabel,
                body.NextLabel,
                body.Line1,
                body.Line2,
                body.Trigger,
                body.Weight
            ),
            cancellationToken
        );
        return result.ToHttpResult();
    }

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

    /// <summary>Синхронно рендерит пример заставки блока (несколько секунд ffmpeg).</summary>
    private static async Task<IResult> RenderPreview(
        Guid id,
        Guid templateId,
        ISender sender,
        CancellationToken cancellationToken
    )
    {
        var result = await sender.Send(
            new RenderBumperPreviewCommand(id, templateId),
            cancellationToken
        );
        return result.IsSuccess ? Results.NoContent() : result.ToHttpResult();
    }

    /// <summary>Плейлист превью подблока: переписываем ffmpeg-index.m3u8, направляя сегменты на admin-роут.</summary>
    private static IResult PreviewPlaylist(
        Guid id,
        Guid templateId,
        Guid variantId,
        MediaPathResolver paths
    )
    {
        var previewId = BumperPreview.AssetId(variantId);
        if (SegmentFiles.TryResolveExisting(paths, previewId, "index.m3u8") is not { } indexPath)
            return Results.NotFound();

        var baseUrl =
            $"/api/admin/channels/{id}/bumper/templates/{templateId}/preview/{variantId}/";
        var sb = new StringBuilder();
        foreach (var line in File.ReadLines(indexPath))
        {
            var trimmed = line.Trim();
            if (trimmed.Length == 0)
                continue;
            // Комментарии/директивы — как есть; строки-сегменты (абсолютный путь от ffmpeg) → admin-URL.
            sb.Append(trimmed.StartsWith('#') ? trimmed : baseUrl + Path.GetFileName(trimmed))
                .Append('\n');
        }

        return Results.Text(sb.ToString(), "application/vnd.apple.mpegurl");
    }

    /// <summary>
    /// Сегмент превью. Канал и блок в маршруте есть, но хендлеру не нужны: каталог превью
    /// адресуется подблоком (см. BumperPreview.AssetId), поэтому в сигнатуре их нет — незаявленные
    /// параметры маршрута просто не связываются.
    /// </summary>
    private static IResult PreviewSegment(Guid variantId, string file, MediaPathResolver paths)
    {
        if (!SegmentFiles.IsSegmentName(file))
            return Results.NotFound();

        var previewId = BumperPreview.AssetId(variantId);
        if (SegmentFiles.TryResolveExisting(paths, previewId, file) is not { } path)
            return Results.NotFound();

        return Results.File(path, "video/mp2t", enableRangeProcessing: true);
    }

    /// <summary>Проверяет расширение файла (по allowlist) и размер (Content-Length). Возвращает
    /// нормализованное расширение (с точкой, нижний регистр) или null при отказе.</summary>
    private static string? ResolveBumperExtension(
        string fileName,
        HttpRequest request,
        IReadOnlySet<string> allowedExtensions
    )
    {
        if (request.ContentLength is > BumperFiles.MaxBytes or 0 or null)
            return null;
        var ext = Path.GetExtension(fileName).ToLowerInvariant();
        return allowedExtensions.Contains(ext) ? ext : null;
    }
}

public sealed record AddBumperTemplateBody(string Name);

public sealed record UpdateBumperTemplateBody(
    string Name,
    string BackgroundColor,
    string BackgroundColor2,
    string AccentColor,
    string TextColor
);

public sealed record SetBumperTemplateBackgroundBody(Guid ImageId);

public sealed record AddBumperVariantBody(string Name);

public sealed record UpdateBumperVariantBody(
    string Name,
    BumperTextKind Kind,
    string NowLabel,
    string NextLabel,
    string Line1,
    string Line2,
    BumperTrigger Trigger,
    int Weight
);

/// <summary>
/// Адрес загружаемого звука: канал и блок из маршрута плюс имя исходного файла из query (по нему
/// проверяется расширение). Свёрнуто в один параметр — кроме него хендлеру нужны ещё запрос, два
/// сервиса, диспетчер и токен отмены, и плоским списком сигнатура перестаёт читаться.
/// </summary>
public sealed record BumperAudioUpload(Guid Id, Guid TemplateId, string FileName);

/// <summary>Ограничения на загружаемый звук блока заставки (фон-картинка — через общий реестр).</summary>
internal static class BumperFiles
{
    public const long MaxBytes = 200L * 1024 * 1024; // 200 МБ

    public static readonly IReadOnlySet<string> AudioExtensions = new HashSet<string>(
        StringComparer.OrdinalIgnoreCase
    )
    {
        ".mp3",
        ".m4a",
        ".aac",
        ".ogg",
        ".opus",
        ".wav",
    };
}
