using TeleWave.Application.Library.Collections.GetCollection;
using TeleWave.Application.Library.Collections.ListCollections;
using TeleWave.Application.Library.Interstitials.ListInterstitialBlocks;
using TeleWave.Application.Library.Interstitials.ListInterstitials;
using TeleWave.Application.Tests.Support;
using TeleWave.Domain.Library;
using TeleWave.Domain.Media;
using Xunit;

namespace TeleWave.Application.Tests.Library;

/// <summary>
/// Справочные списки библиотеки: коллекции и ролики. Считают они немного, но каждое число на экране
/// админа приходит отсюда — сериал внутри франшизы, длительность блока рекламы, статус обработки.
/// </summary>
public class LibraryQueriesTests
{
    /// <summary>Шоу с готовыми ассетами: без длительностей в списках нечего показывать.</summary>
    private static (Show Show, List<MediaAsset> Assets) Playable(
        string name,
        ShowKind kind,
        int episodes,
        int minutes
    )
    {
        var show = Show.Create(name, kind);
        var assets = new List<MediaAsset>();

        for (var i = 0; i < episodes; i++)
        {
            var asset = MediaAsset.Register($"{name}-{i}.mkv", ".mkv", MediaSource.Upload);
            asset.MarkProcessing();
            asset.MarkReady(
                new MediaReadyInfo(
                    TimeSpan.FromMinutes(minutes),
                    6,
                    minutes * 10,
                    1920,
                    1080,
                    "h264",
                    "aac",
                    $"assets/{name}-{i}"
                )
            );
            assets.Add(asset);
            show.AddEpisode(asset.Id);
        }

        return (show, assets);
    }

    [Fact]
    public async Task Collections_CountUnitsNotParts()
    {
        var fixture = new TestDb();
        var series = Playable("Сериал", ShowKind.Series, 10, 25);
        var film = Playable("Фильм", ShowKind.Single, 1, 100);

        var collection = Collection.Create("Франшиза", "Описание");
        collection.AddShow(film.Show.Id);
        collection.AddShow(series.Show.Id);

        await using (var seed = fixture.New())
        {
            seed.MediaAssets.AddRange([.. series.Assets, .. film.Assets]);
            seed.Shows.AddRange(series.Show, film.Show);
            seed.Collections.Add(collection);
            await seed.SaveChangesAsync(CancellationToken.None);
        }

        await using var db = fixture.New();
        var list = await new ListCollectionsQueryHandler(db).Handle(
            new ListCollectionsQuery(),
            CancellationToken.None
        );

        var summary = Assert.Single(list);
        Assert.Equal("Франшиза", summary.Name);
        Assert.Equal(2, summary.ItemCount);
        // Единиц больше, чем частей: сериал внутри коллекции разворачивается в свои серии.
        Assert.Equal(11, summary.UnitCount);

        var detail = await new GetCollectionQueryHandler(db).Handle(
            new GetCollectionQuery(collection.Id),
            CancellationToken.None
        );

        Assert.True(detail.IsSuccess);
        // Порядок частей — заданный, а не алфавитный: франшиза смотрится по порядку выхода.
        Assert.Equal(["Фильм", "Сериал"], detail.Value.Items.Select(i => i.ShowName));
        Assert.Equal(10, detail.Value.Items[1].EpisodeCount);
    }

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

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

        Assert.False(result.IsSuccess);
    }

    [Fact]
    public async Task Interstitials_ShowDurationAndStatus()
    {
        var fixture = new TestDb();
        var ready = Playable("Реклама", ShowKind.Interstitial, 1, 1);
        // Ролик без медиа: заведён, но файла к нему ещё не приложили.
        var empty = Show.Create("Пустой ролик", ShowKind.Interstitial);
        var notAClip = Playable("Кино", ShowKind.Single, 1, 100);

        await using (var seed = fixture.New())
        {
            seed.MediaAssets.AddRange([.. ready.Assets, .. notAClip.Assets]);
            seed.Shows.AddRange(ready.Show, empty, notAClip.Show);
            await seed.SaveChangesAsync(CancellationToken.None);
        }

        await using var db = fixture.New();
        var list = await new ListInterstitialsQueryHandler(db).Handle(
            new ListInterstitialsQuery(),
            CancellationToken.None
        );

        // В списке роликов только ролики — полнометражка сюда не попадает.
        Assert.Equal(2, list.Count);

        var withMedia = list.Single(i => i.Name == "Реклама");
        Assert.Equal(MediaAssetStatus.Ready, withMedia.AssetStatus);
        Assert.Equal(60, withMedia.DurationSeconds);

        var withoutMedia = list.Single(i => i.Name == "Пустой ролик");
        Assert.Null(withoutMedia.MediaAssetId);
        Assert.Null(withoutMedia.AssetStatus);
        Assert.Null(withoutMedia.DurationSeconds);
    }

    [Fact]
    public async Task Blocks_AreCollectionsMadeOfClipsOnly()
    {
        var fixture = new TestDb();
        var first = Playable("Ролик 1", ShowKind.Interstitial, 1, 1);
        var second = Playable("Ролик 2", ShowKind.Interstitial, 1, 2);
        var film = Playable("Кино", ShowKind.Single, 1, 100);

        var block = Collection.Create("Рекламный блок");
        block.AddShow(first.Show.Id);
        block.AddShow(second.Show.Id);

        // Смешанная коллекция — франшиза, а не блок: она живёт на своём экране.
        var mixed = Collection.Create("Смешанная");
        mixed.AddShow(first.Show.Id);
        mixed.AddShow(film.Show.Id);

        // Пустая коллекция блоком тоже не считается: ставить в эфир из неё нечего.
        var empty = Collection.Create("Пустая");

        await using (var seed = fixture.New())
        {
            seed.MediaAssets.AddRange([.. first.Assets, .. second.Assets, .. film.Assets]);
            seed.Shows.AddRange(first.Show, second.Show, film.Show);
            seed.Collections.AddRange(block, mixed, empty);
            await seed.SaveChangesAsync(CancellationToken.None);
        }

        await using var db = fixture.New();
        var blocks = await new ListInterstitialBlocksQueryHandler(db).Handle(
            new ListInterstitialBlocksQuery(),
            CancellationToken.None
        );

        var only = Assert.Single(blocks);
        Assert.Equal("Рекламный блок", only.Name);
        Assert.Equal(2, only.ItemCount);
        Assert.Equal(180, only.DurationSeconds);
    }
}
