using LiteCqrs;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using TeleWave.Application.Common.Interfaces;
using TeleWave.Application.Common.Models;
using TeleWave.Application.Streaming;
using TeleWave.Domain.Broadcast;
using TeleWave.Domain.Programming;

namespace TeleWave.Application.Broadcast.Bumpers;

public sealed class RenderBumperPreviewCommandHandler(
    IAppDbContext dbContext,
    IBumperRenderer renderer,
    IBumperTemplateStorage storage,
    IImageStore imageStore,
    IOptions<BumperOptions> bumperOptions,
    IOptions<StreamingOptions> streamingOptions
) : ICommandHandler<RenderBumperPreviewCommand, Result>
{
    private readonly BumperOptions _bumper = bumperOptions.Value;
    private readonly int _segmentSeconds = Math.Max(1, streamingOptions.Value.SegmentSeconds);

    /// <summary>Длительность заставки без загруженного звука (сек) — как в генераторе.</summary>
    private const int DefaultBumperDurationSeconds = 8;

    public async Task<Result> Handle(
        RenderBumperPreviewCommand query,
        CancellationToken cancellationToken
    )
    {
        var channel = await dbContext
            .Channels.AsNoTracking()
            .Include(c => c.BumperTemplates)
                .ThenInclude(t => t.Variants)
            .AsSplitQuery()
            .FirstOrDefaultAsync(c => c.Id == query.ChannelId, cancellationToken);
        if (channel is null)
            return Result.Failure(ChannelErrors.NotFound);

        var template = channel.BumperTemplates.FirstOrDefault(t => t.Id == query.TemplateId);
        if (template is null)
            return Result.Failure(ChannelErrors.BumperTemplateNotFound);

        var (fromName, toName) = await SampleNamesAsync(channel, cancellationToken);
        var backgroundPath = await ResolveBackgroundPathAsync(template, cancellationToken);
        var seconds = template.AudioDurationSeconds is { } d and > 0
            ? d
            : DefaultBumperDurationSeconds;
        var aligned = (int)(
            Math.Ceiling(Math.Max(_segmentSeconds, seconds) / _segmentSeconds) * _segmentSeconds
        );
        var audioPath = storage.AudioPath(template.Id, template.AudioExtension);

        // Постер зависит от конкретного «следующего» шоу — в превью его не подставляем.
        var inputs = new BumperSpecInputs(
            fromName,
            toName,
            audioPath,
            PosterAbsolutePath: null,
            backgroundPath
        );

        // Рендерим каждый подблок в свой ассет-превью (id по подблоку).
        foreach (var variant in template.Variants.OrderBy(v => v.Position))
        {
            var spec = BumperSpecFactory.Build(
                _bumper,
                channel.BumperFont,
                template,
                variant,
                aligned,
                inputs
            );
            await renderer.RenderAsync(BumperPreview.AssetId(variant.Id), spec, cancellationToken);
        }

        return Result.Success();
    }

    /// <summary>Путь к фон-картинке блока в общем реестре или null, если она не привязана.</summary>
    private async Task<string?> ResolveBackgroundPathAsync(
        BumperTemplate template,
        CancellationToken cancellationToken
    )
    {
        if (template.BackgroundImageId is not { } imageId)
            return null;

        var extension = await dbContext
            .Images.AsNoTracking()
            .Where(i => i.Id == imageId)
            .Select(i => i.FileExtension)
            .FirstOrDefaultAsync(cancellationToken);

        return extension is null ? null : imageStore.ResolvePath(imageId, extension);
    }

    /// <summary>
    /// Примерные названия «из/в» для превью. Берём шоу из групп, на которые ссылаются слоты канала:
    /// так превью показывает реальные названия этого канала, а не случайные из библиотеки.
    /// </summary>
    private async Task<(string From, string To)> SampleNamesAsync(
        Channel channel,
        CancellationToken cancellationToken
    )
    {
        var names = await (
            from slot in dbContext.Slots.AsNoTracking()
            join layer in dbContext.GridLayers.AsNoTracking() on slot.LayerId equals layer.Id
            join item in dbContext.GroupItems.AsNoTracking() on slot.GroupId equals item.GroupId
            join show in dbContext.Shows.AsNoTracking() on item.ElementId equals show.Id
            where
                layer.TemplateId == channel.TemplateId && item.ElementKind == GroupElementKind.Show
            select show.Name
        )
            .Distinct()
            .Take(2)
            .ToListAsync(cancellationToken);

        return (
            names.ElementAtOrDefault(0) ?? "Первое шоу",
            names.ElementAtOrDefault(1) ?? "Второе шоу"
        );
    }
}
