using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using TeleWave.Application.Broadcast.Bumpers;
using TeleWave.Application.Common.Interfaces;
using TeleWave.Domain.Media;

namespace TeleWave.Infrastructure.Media;

/// <summary>Захваченная на рендер заставка: спецификация собирается уже в самой работе.</summary>
internal sealed record BumperRenderJob(Guid AssetId);

/// <summary>
/// Асинхронно рендерит ТВ-заставки расписания: планировщик лишь создаёт ассет (Source=Generated) в
/// статусе Pending и кэш-строку <see cref="Domain.Broadcast.BumperAsset"/>, а сам ffmpeg крутится здесь,
/// вне тика планировщика и его транзакции. Захват работы и устойчивость к рестарту — в
/// <see cref="MediaClaimingBackgroundService{TJob}"/>. До готовности ассета плейлист отдаёт филлер.
///
/// Рендер строго последовательный: ffmpeg заставки короткий, а параллелить его смысла нет —
/// очередь разбирается быстрее, чем планировщик успевает её пополнять.
/// </summary>
internal sealed class BumperRenderBackgroundService(
    IBumperRenderQueue queue,
    IServiceScopeFactory scopeFactory,
    IBumperRenderer renderer,
    ILogger<BumperRenderBackgroundService> logger
) : MediaClaimingBackgroundService<BumperRenderJob>(scopeFactory, logger)
{
    protected override bool HandlesGenerated => true;

    protected override string LoopErrorMessage => "Ошибка цикла рендера заставок";

    protected override ValueTask WaitForWorkAsync(CancellationToken cancellationToken) =>
        queue.WaitAsync(cancellationToken);

    protected override BumperRenderJob ToJob(MediaAsset asset) => new(asset.Id);

    protected override async Task ProcessAsync(
        BumperRenderJob job,
        CancellationToken cancellationToken
    )
    {
        try
        {
            var spec = await WithScopeAsync<BumperSpecLoader, BumperRenderSpec?>(loader =>
                loader.LoadAsync(job.AssetId, cancellationToken)
            );
            if (spec is null)
            {
                await FailAsync(
                    job.AssetId,
                    "Не удалось восстановить спецификацию заставки",
                    cancellationToken
                );
                return;
            }

            var render = await renderer.RenderAsync(job.AssetId, spec, cancellationToken);

            await WithAssetAsync(
                job.AssetId,
                asset =>
                    asset.MarkReady(
                        new MediaReadyInfo(
                            render.Duration,
                            render.SegmentSeconds,
                            render.SegmentCount,
                            render.Width,
                            render.Height,
                            "h264",
                            "aac",
                            render.RelativePath
                        )
                    ),
                cancellationToken
            );
        }
        catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
        {
            throw;
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "Рендер заставки {AssetId} провалился", job.AssetId);
            await FailAsync(job.AssetId, ex.Message, CancellationToken.None);
        }
    }
}
