using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using TeleWave.Application.Broadcast.Scheduling;
using TeleWave.Application.Common.Interfaces;
using TeleWave.Application.Streaming;
using TeleWave.Domain.Broadcast;
using TeleWave.Domain.Media;

namespace TeleWave.Application.Broadcast.Bumpers;

/// <summary>
/// Восстанавливает <see cref="BumperRenderSpec"/> по кэш-строке заставки: планировщик сохранил
/// готовые строки и ссылку на блок, а рендеру нужны ещё абсолютные пути к звуку, постеру и фону.
/// Вынесено из фонового рендерера: чтение и сборка — работа слоя приложения, воркер лишь крутит ffmpeg.
/// </summary>
public sealed class BumperSpecLoader(
    IAppDbContext dbContext,
    IBumperTemplateStorage bumperStorage,
    IImageStore imageStore,
    IMediaStorage mediaStorage,
    IOptions<BumperOptions> bumperOptions,
    IOptions<StreamingOptions> streamingOptions
)
{
    private readonly BumperOptions _bumper = bumperOptions.Value;
    private readonly int _segmentSeconds = Math.Max(1, streamingOptions.Value.SegmentSeconds);

    /// <summary>Спецификация заставки для ассета, либо null если восстановить её уже нельзя.</summary>
    public async Task<BumperRenderSpec?> LoadAsync(
        Guid assetId,
        CancellationToken cancellationToken
    )
    {
        var cache = await dbContext
            .BumperAssets.AsNoTracking()
            .Where(b => b.MediaAssetId == assetId)
            .OrderByDescending(b => b.CreatedAt)
            .FirstOrDefaultAsync(cancellationToken);
        if (cache is null)
            return null;

        var template = await dbContext
            .BumperTemplates.AsNoTracking()
            .FirstOrDefaultAsync(t => t.Id == cache.TemplateId, cancellationToken);
        if (template is null)
            return null;

        var posterPath = cache.PosterShowId is { } showId
            ? await ResolveShowPosterAsync(showId, cancellationToken)
            : null;
        var bgPath = await ResolveImagePathAsync(template.BackgroundImageId, cancellationToken);
        var (clipPath, clipSeconds) = await ResolveClipAsync(
            cache.BackgroundAssetId,
            cancellationToken
        );

        // Длину задаёт ролик, если он есть: подрезать чужую заставку под джингл значило бы обрывать
        // её на полуслове. Выравнивание по сегменту остаётся — лента этого требует от любой записи.
        var aligned = BumperDuration.Aligned(
            clipSeconds ?? BumperDuration.TemplateSeconds(template),
            _segmentSeconds
        );

        return BumperSpecFactory.Build(
            _bumper,
            template,
            aligned,
            new BumperSpecInputs(
                BumperRenderedText.FromJson(cache.RenderedLinesJson),
                bumperStorage.AudioPath(template.Id, template.AudioExtension),
                posterPath,
                bgPath,
                clipPath
            )
        );
    }

    /// <summary>Плейлист ролика-подложки и его длительность; null — ролика уже нет или он не готов.</summary>
    private async Task<(string? Path, int? Seconds)> ResolveClipAsync(
        Guid? assetId,
        CancellationToken cancellationToken
    )
    {
        if (assetId is not { } id)
            return (null, null);

        var duration = await dbContext
            .MediaAssets.AsNoTracking()
            .Where(a => a.Id == id && a.Status == MediaAssetStatus.Ready)
            .Select(a => a.Duration)
            .FirstOrDefaultAsync(cancellationToken);
        if (duration is not { } value || value <= TimeSpan.Zero)
            return (null, null);

        var path = mediaStorage.ResolvePlaylistPath(id);
        return path is null ? (null, null) : (path, (int)Math.Ceiling(value.TotalSeconds));
    }

    private async Task<string?> ResolveShowPosterAsync(
        Guid showId,
        CancellationToken cancellationToken
    )
    {
        var posterImageId = await dbContext
            .Shows.AsNoTracking()
            .Where(s => s.Id == showId && s.PosterImageId != null)
            .Select(s => s.PosterImageId)
            .FirstOrDefaultAsync(cancellationToken);
        return await ResolveImagePathAsync(posterImageId, cancellationToken);
    }

    private async Task<string?> ResolveImagePathAsync(
        Guid? imageId,
        CancellationToken cancellationToken
    )
    {
        if (imageId is not { } id)
            return null;

        var ext = await dbContext
            .Images.AsNoTracking()
            .Where(i => i.Id == id)
            .Select(i => i.FileExtension)
            .FirstOrDefaultAsync(cancellationToken);
        return ext is null ? null : imageStore.ResolvePath(id, ext);
    }
}
