using TeleWave.Application.Programming.Templates;
using TeleWave.Application.Programming.Templates.CreateSlot;
using TeleWave.Application.Programming.Templates.Layers;
using TeleWave.Application.Tests.Support;
using TeleWave.Domain.Broadcast;
using TeleWave.Domain.Library;
using TeleWave.Domain.Media;
using TeleWave.Domain.Programming;
using Xunit;

namespace TeleWave.Application.Tests.Programming;

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

    private sealed record World(TestDb Db, Guid LayerId, Guid TemplateId, Guid Ads, Guid Content);

    private static Show Ready(string name, ShowKind kind, List<MediaAsset> assets, int seconds)
    {
        var show = Show.Create(name, kind);
        var asset = MediaAsset.Register($"{name}.mkv", ".mkv", MediaSource.Upload);
        asset.MarkProcessing();
        asset.MarkReady(
            new MediaReadyInfo(
                TimeSpan.FromSeconds(seconds),
                6,
                1000,
                1920,
                1080,
                "h264",
                "aac",
                $"assets/{name}"
            )
        );
        assets.Add(asset);
        show.AddEpisode(asset.Id);
        return show;
    }

    private static async Task<World> SeedAsync()
    {
        var fixture = new TestDb();
        var assets = new List<MediaAsset>();
        var shows = new List<Show>();

        var ads = Group.Create("Реклама");
        for (var i = 0; i < 3; i++)
        {
            var clip = Ready($"Ролик {i}", ShowKind.Interstitial, assets, 20);
            shows.Add(clip);
            ads.AddElement(GroupElementKind.Show, clip.Id);
        }

        var series = Ready("Сериал", ShowKind.Series, assets, 1320);
        shows.Add(series);
        var content = Group.Create("Сериалы");
        content.AddElement(GroupElementKind.Show, series.Id);

        var channel = Channel.Create("Первый", "one", T0);
        var template = ScheduleTemplate.Create(channel.Id, "Сетка");
        var layer = template.AddLayer("Основная", 10);
        channel.SetTemplate(template.Id);

        await using var seed = fixture.New();
        seed.MediaAssets.AddRange(assets);
        seed.Shows.AddRange(shows);
        seed.Groups.AddRange([ads, content]);
        seed.Channels.Add(channel);
        seed.ScheduleTemplates.Add(template);
        await seed.SaveChangesAsync(CancellationToken.None);

        return new World(fixture, layer.Id, template.Id, ads.Id, content.Id);
    }

    private static SlotInput Input(Guid groupId) =>
        new(
            "Блок",
            null,
            new TimeOnly(20, 0),
            60,
            Daypart.Prime,
            SlotKind.Content,
            groupId,
            null,
            null,
            SlotBlockMode.FillSlot,
            1,
            OverflowPolicy.ContinueNext,
            false,
            30,
            null,
            null,
            null
        );

    [Fact]
    public async Task Slot_WithInterstitialGroup_IsRejected()
    {
        var world = await SeedAsync();
        await using var db = world.Db.New();

        var result = await new CreateSlotCommandHandler(GroupServices.Slots(db)).Handle(
            new CreateSlotCommand(world.LayerId, Input(world.Ads)),
            CancellationToken.None
        );

        Assert.False(result.IsSuccess);
        Assert.Equal(TemplateErrors.InterstitialGroupInSlot, result.Error);
    }

    [Fact]
    public async Task Slot_WithContentGroup_IsAccepted()
    {
        // Обратная сторона запрета: обычная группа обязана проходить, иначе признак «ролики»
        // ловил бы всё подряд.
        var world = await SeedAsync();
        await using var db = world.Db.New();

        var result = await new CreateSlotCommandHandler(GroupServices.Slots(db)).Handle(
            new CreateSlotCommand(world.LayerId, Input(world.Content)),
            CancellationToken.None
        );

        Assert.True(result.IsSuccess);
    }

    [Fact]
    public async Task FallbackGroup_OfInterstitials_IsRejected()
    {
        // Аварийная группа закрывает каждую паузу эфира — из роликов она превращает в рекламу
        // остаток каждого слота.
        var world = await SeedAsync();
        await using var db = world.Db.New();

        var result = await new UpdateTemplateCommandHandler(
            db,
            GroupServices.Interstitials(db)
        ).Handle(
            new UpdateTemplateCommand(world.TemplateId, "Сетка", world.Ads, null, null),
            CancellationToken.None
        );

        Assert.False(result.IsSuccess);
        Assert.Equal(TemplateErrors.InterstitialFallbackGroup, result.Error);
    }
}
