using TeleWave.Application.Programming.Groups;
using TeleWave.Application.Programming.Templates;
using TeleWave.Application.Programming.Templates.GetTemplate;
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 GetTemplateTests
{
    private static readonly DateTimeOffset T0 = new(2026, 1, 1, 0, 0, 0, TimeSpan.Zero);

    [Fact]
    public async Task ReturnsLayersByPriority_WithGroupNamesAndState()
    {
        var fixture = new TestDb();
        var group = Group.Create("Кино");
        var channel = Channel.Create("Первый", "first", T0);
        channel.UpdateTimeSettings(1, 180, new TimeOnly(6, 0));

        var template = ScheduleTemplate.Create(channel.Id, "Сетка");
        channel.SetTemplate(template.Id);
        var top = template.AddLayer("Выходные", 20);
        var slot = top.AddSlot("Кино в прайм", new TimeOnly(20, 0), 120, Daypart.Prime, weekday: 6);
        slot.UpdateContent(
            new SlotContent(
                slot.Title,
                SlotKind.Content,
                group.Id,
                new SlotStrategy(SlotStrategyType.RandomWithCooldown, CooldownDays: 14).ToJson(),
                null,
                SlotBlockMode.Count,
                1,
                OverflowPolicy.ContinueNext
            )
        );
        template.MarkChanged();

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

        await using var db = fixture.New();
        var result = await new GetChannelTemplateQueryHandler(db).Handle(
            new GetChannelTemplateQuery(channel.Id),
            CancellationToken.None
        );

        Assert.True(result.IsSuccess);
        var dto = result.Value;

        // Слои идут сверху вниз: первым тот, что перекрывает остальные.
        Assert.Equal(["Выходные", "Фон"], dto.Layers.Select(l => l.Name));
        Assert.Equal(new TimeOnly(6, 0), dto.DayStartTime);
        Assert.Equal(180, dto.UtcOffsetMinutes);

        var stored = Assert.Single(dto.Layers[0].Slots);
        // Имя группы приезжает вместе со слотом: инспектор показывает его без второго запроса.
        Assert.Equal("Кино", stored.GroupName);
        Assert.Equal(6, stored.Weekday);
        Assert.Equal(SlotStrategyType.RandomWithCooldown, stored.Strategy!.Type);
        Assert.Equal(14, stored.Strategy.CooldownDays);

        // Правка эфир не двигает — она поднимает ревизию, и плашка об этом видна в шапке канала.
        Assert.True(dto.HasPendingChanges);
        Assert.Equal(1, dto.Revision);
        Assert.Equal(0, dto.AppliedRevision);
        // Сетку ни разу не применяли — откатывать не к чему, и кнопки быть не должно.
        Assert.False(dto.CanRestore);
    }

    [Fact]
    public async Task CanRestore_AfterFirstApply()
    {
        var fixture = new TestDb();
        var channel = Channel.Create("Первый", "first", T0);
        var template = ScheduleTemplate.Create(channel.Id, "Сетка");
        channel.SetTemplate(template.Id);
        template.MarkApplied(TemplateSnapshot.Capture(template).ToJson());

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

        await using var db = fixture.New();
        var result = await new GetChannelTemplateQueryHandler(db).Handle(
            new GetChannelTemplateQuery(channel.Id),
            CancellationToken.None
        );

        Assert.True(result.Value.CanRestore);
        Assert.False(result.Value.HasPendingChanges);
    }

    [Fact]
    public async Task Fails_WhenChannelOrTemplateIsMissing()
    {
        var fixture = new TestDb();
        var channel = Channel.Create("Без сетки", "no-grid", T0);

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

        await using var db = fixture.New();
        var handler = new GetChannelTemplateQueryHandler(db);

        var noChannel = await handler.Handle(
            new GetChannelTemplateQuery(Guid.NewGuid()),
            CancellationToken.None
        );
        var noTemplate = await handler.Handle(
            new GetChannelTemplateQuery(channel.Id),
            CancellationToken.None
        );

        Assert.False(noChannel.IsSuccess);
        Assert.False(noTemplate.IsSuccess);
    }

    [Fact]
    public async Task ElementResolver_ExpandsCollectionIntoItsParts()
    {
        // Коллекция в группе — одна позиция, но эфира она даёт столько же, сколько все её части.
        var fixture = new TestDb();
        var film = Show.Create("Часть 1", ShowKind.Single);
        film.SetAudience(ShowAudience.Pg13);
        var sequel = Show.Create("Часть 2", ShowKind.Single);
        sequel.SetAudience(ShowAudience.R);
        var assets = new List<MediaAsset>();

        foreach (var show in new[] { film, sequel })
        {
            var asset = MediaAsset.Register($"{show.Name}.mkv", ".mkv", MediaSource.Upload);
            asset.MarkProcessing();
            asset.MarkReady(
                new MediaReadyInfo(
                    TimeSpan.FromMinutes(90),
                    6,
                    900,
                    1920,
                    1080,
                    "h264",
                    "aac",
                    $"assets/{show.Name}"
                )
            );
            assets.Add(asset);
            show.AddEpisode(asset.Id);
        }

        var collection = Collection.Create("Франшиза");
        collection.AddShow(film.Id);
        collection.AddShow(sequel.Id);

        await using (var seed = fixture.New())
        {
            seed.MediaAssets.AddRange(assets);
            seed.Shows.AddRange(film, sequel);
            seed.Collections.Add(collection);
            await seed.SaveChangesAsync(CancellationToken.None);
        }

        await using var db = fixture.New();
        var info = await new GroupElementResolver(db).ResolveAsync(
            [(GroupElementKind.Collection, collection.Id), (GroupElementKind.Show, film.Id)],
            CancellationToken.None
        );

        var franchise = info[(GroupElementKind.Collection, collection.Id)];
        Assert.Equal("Франшиза", franchise.Name);
        Assert.Equal(2, franchise.UnitCount);
        Assert.Equal(TimeSpan.FromMinutes(180), franchise.TotalDuration);
        // Рейтинг франшизы — самый строгий среди частей: по нему её отсеивают в детское время.
        Assert.Equal(ShowAudience.R, franchise.Audience);

        var single = info[(GroupElementKind.Show, film.Id)];
        Assert.Equal(1, single.UnitCount);
        Assert.Equal(ShowKind.Single, single.ShowKind);
    }
}
