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

namespace TeleWave.Infrastructure.Media;

/// <summary>Захваченный на транскод ассет: расширение нужно ffmpeg и уже не требует БД.</summary>
internal sealed record MediaTranscodeJob(Guid AssetId, string Extension);

/// <summary>
/// Обработчик медиа: прогоняет ассеты через ffmpeg, до <see cref="MediaOptions.MaxParallelTranscodes"/>
/// файлов одновременно. Захват работы, устойчивость к рестарту и параллелизм — в
/// <see cref="MediaClaimingBackgroundService{TJob}"/>; здесь только сам транскод.
/// </summary>
internal sealed class MediaProcessingBackgroundService(
    IMediaProcessingQueue queue,
    IServiceScopeFactory scopeFactory,
    MediaPathResolver paths,
    IMediaProcessor processor,
    IOptions<StorageOptions> storageOptions,
    IOptions<MediaOptions> mediaOptions,
    ILogger<MediaProcessingBackgroundService> logger
) : MediaClaimingBackgroundService<MediaTranscodeJob>(scopeFactory, logger)
{
    private readonly StorageOptions _storage = storageOptions.Value;

    // Generated-ассеты (ТВ-заставки) обслуживает BumperRenderBackgroundService — их не берём.
    protected override bool HandlesGenerated => false;

    protected override int MaxParallel => Math.Max(1, mediaOptions.Value.MaxParallelTranscodes);

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

    protected override void OnStarting() => paths.EnsureDirectories();

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

    protected override MediaTranscodeJob ToJob(MediaAsset asset) =>
        new(asset.Id, asset.OriginalExtension);

    protected override async Task ProcessAsync(
        MediaTranscodeJob job,
        CancellationToken cancellationToken
    )
    {
        try
        {
            var result = await processor.ProcessAsync(
                job.AssetId,
                job.Extension,
                cancellationToken
            );
            await WithAssetAsync(
                job.AssetId,
                asset =>
                    asset.MarkReady(
                        new MediaReadyInfo(
                            result.Duration,
                            result.SegmentSeconds,
                            result.SegmentCount,
                            result.Width,
                            result.Height,
                            result.VideoCodec,
                            result.AudioCodec,
                            result.RelativePath
                        )
                    ),
                cancellationToken
            );

            if (!_storage.KeepOriginals)
                DeleteOriginal(job.AssetId, job.Extension);

            logger.LogInformation(
                "Ассет {AssetId} обработан: {Segments} сегментов, {Seconds:0.#}с",
                job.AssetId,
                result.SegmentCount,
                result.Duration.TotalSeconds
            );
        }
        catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
        {
            throw;
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "Обработка ассета {AssetId} провалилась", job.AssetId);
            await FailAsync(job.AssetId, ex.Message, CancellationToken.None);
        }
    }

    private void DeleteOriginal(Guid assetId, string extension)
    {
        var original = paths.OriginalPath(assetId, extension);
        if (File.Exists(original))
            File.Delete(original);
    }
}
