using Microsoft.Extensions.Options;
using NSubstitute;
using TeleWave.Application.Broadcast;
using TeleWave.Application.Broadcast.Bumpers;
using TeleWave.Application.Common.Interfaces;
using TeleWave.Application.Streaming;
using TeleWave.Application.Tests.Support;
using TeleWave.Domain.Broadcast;
using TeleWave.Domain.Images;
using TeleWave.Domain.Library;
using TeleWave.Domain.Programming;
using TeleWave.Infrastructure.Persistence;
using Xunit;

namespace TeleWave.Application.Tests.Broadcast;

/// <summary>
/// Превью ТВ-заставки: рендерится каждый подблок блока, длительность выравнивается по длине
/// сегмента HLS, а названия «из/в» берутся из групп этого канала — иначе превью показывало бы
/// случайные шоу из библиотеки.
/// </summary>
public class BumperPreviewTests
{
    private static readonly DateTimeOffset T0 = new(2026, 1, 1, 0, 0, 0, TimeSpan.Zero);

    private static RenderBumperPreviewCommandHandler Handler(
        AppDbContext db,
        IBumperRenderer? renderer = null,
        IBumperTemplateStorage? storage = null,
        IImageStore? imageStore = null
    ) =>
        new(
            db,
            renderer ?? Substitute.For<IBumperRenderer>(),
            storage ?? Substitute.For<IBumperTemplateStorage>(),
            imageStore ?? Substitute.For<IImageStore>(),
            Options.Create(new BumperOptions()),
            Options.Create(new StreamingOptions { SegmentSeconds = 5 })
        );

    [Fact]
    public async Task Render_UnknownChannel_ReturnsNotFound()
    {
        var fixture = new TestDb();
        await using var db = fixture.New();

        var result = await Handler(db)
            .Handle(
                new RenderBumperPreviewCommand(Guid.NewGuid(), Guid.NewGuid()),
                CancellationToken.None
            );

        Assert.Equal(ChannelErrors.NotFound, result.Error);
    }

    [Fact]
    public async Task Render_UnknownTemplate_ReturnsBumperTemplateNotFound()
    {
        var fixture = new TestDb();
        var channel = Channel.Create("Первый", "one", T0);
        await using (var seed = fixture.New())
        {
            seed.Channels.Add(channel);
            await seed.SaveChangesAsync(CancellationToken.None);
        }

        await using var db = fixture.New();
        var result = await Handler(db)
            .Handle(
                new RenderBumperPreviewCommand(channel.Id, Guid.NewGuid()),
                CancellationToken.None
            );

        Assert.Equal(ChannelErrors.BumperTemplateNotFound, result.Error);
    }

    [Fact]
    public async Task Render_DrawsEveryVariant_WithChannelShowNamesAndAlignedDuration()
    {
        var fixture = new TestDb();
        var channel = Channel.Create("Первый", "one", T0);
        var template = channel.BumperTemplates[0];
        var second = template.AddVariant("Второй подблок");

        // Шоу канала: слот сетки ссылается на группу, в группе — шоу.
        var show = Show.Create("Наше шоу", ShowKind.Series);
        var group = Group.Create("Кино");
        group.AddElement(GroupElementKind.Show, show.Id);
        var grid = ScheduleTemplate.Create(channel.Id, "Сетка");
        var slot = grid.Background!.AddSlot("Круглосуточно", new TimeOnly(6, 0), 24 * 60);
        slot.UpdateContent(
            new SlotContent(
                slot.Title,
                SlotKind.Content,
                group.Id,
                null,
                null,
                SlotBlockMode.FillSlot,
                1,
                OverflowPolicy.ContinueNext
            )
        );
        channel.SetTemplate(grid.Id);

        await using (var seed = fixture.New())
        {
            seed.Channels.Add(channel);
            seed.Shows.Add(show);
            seed.Groups.Add(group);
            seed.ScheduleTemplates.Add(grid);
            await seed.SaveChangesAsync(CancellationToken.None);
        }

        var renderer = Substitute.For<IBumperRenderer>();
        await using var db = fixture.New();
        var result = await Handler(db, renderer)
            .Handle(
                new RenderBumperPreviewCommand(channel.Id, template.Id),
                CancellationToken.None
            );

        Assert.True(result.IsSuccess);
        // Оба подблока отрисованы, каждый — в свой ассет-превью.
        await renderer
            .Received(1)
            .RenderAsync(
                BumperPreview.AssetId(template.Variants[0].Id),
                Arg.Any<BumperRenderSpec>(),
                Arg.Any<CancellationToken>()
            );
        await renderer
            .Received(1)
            .RenderAsync(
                BumperPreview.AssetId(second.Id),
                Arg.Any<BumperRenderSpec>(),
                Arg.Any<CancellationToken>()
            );

        var specs = renderer
            .ReceivedCalls()
            .Select(c => c.GetArguments()[1])
            .OfType<BumperRenderSpec>()
            .ToList();
        // Своего звука у блока нет: 8 секунд по умолчанию, выровненные вверх до сегмента в 5 сек.
        Assert.All(specs, s => Assert.Equal(10, s.DurationSeconds));
        Assert.All(specs, s => Assert.Equal("Наше шоу", s.NowTitle));
        // Второго шоу у канала нет — подставляется заглушка.
        Assert.All(specs, s => Assert.Equal("Второе шоу", s.NextTitle));
        Assert.All(specs, s => Assert.Null(s.BackgroundFile));
        Assert.All(specs, s => Assert.Null(s.PosterFile));
    }

    [Fact]
    public async Task Render_WithBackgroundImage_ResolvesPathFromRegistry()
    {
        var fixture = new TestDb();
        var channel = Channel.Create("Первый", "one", T0);
        var template = channel.BumperTemplates[0];
        var image = Image.Create(ImageCategory.BumperBackground, ".png", "bg.png");
        template.SetBackgroundImage(image.Id);

        await using (var seed = fixture.New())
        {
            seed.Channels.Add(channel);
            seed.Images.Add(image);
            await seed.SaveChangesAsync(CancellationToken.None);
        }

        var renderer = Substitute.For<IBumperRenderer>();
        var imageStore = Substitute.For<IImageStore>();
        imageStore.ResolvePath(image.Id, ".png").Returns("/data/images/bg.png");

        await using var db = fixture.New();
        var result = await Handler(db, renderer, imageStore: imageStore)
            .Handle(
                new RenderBumperPreviewCommand(channel.Id, template.Id),
                CancellationToken.None
            );

        Assert.True(result.IsSuccess);
        var spec = renderer
            .ReceivedCalls()
            .Select(c => c.GetArguments()[1])
            .OfType<BumperRenderSpec>()
            .First();
        Assert.Equal("/data/images/bg.png", spec.BackgroundFile);
        // Шоу у канала нет — обе подписи заглушечные.
        Assert.Equal("Первое шоу", spec.NowTitle);
    }
}
