using TeleWave.Application.Programming.Groups;
using TeleWave.Application.Programming.Groups.FindGroupCandidates;
using TeleWave.Application.Tests.Support;
using TeleWave.Domain.Library;
using TeleWave.Domain.Media;
using TeleWave.Domain.Programming;
using Xunit;

namespace TeleWave.Application.Tests.Programming;

/// <summary>
/// Подбор позиций по правилу набора: то, что редактор группы показывает кнопкой «Подобрать».
/// Важна не только выборка, но и отметка «уже в группе» — по ней видно, что добавлять нечего.
/// </summary>
public class FindGroupCandidatesTests
{
    private static (Show Show, MediaAsset Asset) Playable(string name, int minutes)
    {
        var asset = MediaAsset.Register($"{name}.mkv", ".mkv", MediaSource.Upload);
        asset.MarkProcessing();
        asset.MarkReady(
            new MediaReadyInfo(
                TimeSpan.FromMinutes(minutes),
                6,
                minutes * 10,
                1920,
                1080,
                "h264",
                "aac",
                $"assets/{name}"
            )
        );
        var show = Show.Create(name, ShowKind.Single);
        show.AddEpisode(asset.Id);
        return (show, asset);
    }

    private static async Task<TeleWave.Application.Common.Models.Result<
        IReadOnlyList<GroupCandidateDto>
    >> FindAsync(TestDb fixture, FindGroupCandidatesQuery query)
    {
        await using var db = fixture.New();
        return await new FindGroupCandidatesQueryHandler(
            db,
            new GroupFilterMatcher(db),
            new GroupElementResolver(db)
        ).Handle(query, CancellationToken.None);
    }

    [Fact]
    public async Task MarksWhatIsAlreadyInGroup_AndFiltersByUnitDuration()
    {
        var fixture = new TestDb();
        var shortFilm = Playable("Короткий", 20);
        var longFilm = Playable("Длинный", 120);
        var inside = Playable("Уже внутри", 100);

        var group = Group.Create("Кино");
        group.SetFilter(new GroupFilter(UnitMinutesMin: 60).ToJson());
        group.AddElement(GroupElementKind.Show, inside.Show.Id);

        await using (var seed = fixture.New())
        {
            seed.MediaAssets.AddRange(shortFilm.Asset, longFilm.Asset, inside.Asset);
            seed.Shows.AddRange(shortFilm.Show, longFilm.Show, inside.Show);
            seed.Groups.Add(group);
            await seed.SaveChangesAsync(CancellationToken.None);
        }

        var result = await FindAsync(fixture, new FindGroupCandidatesQuery(group.Id));

        Assert.True(result.IsSuccess);
        // Короткий отсечён по длине единицы: правило меряет саму серию, а не сумму по шоу.
        Assert.Equal(["Длинный", "Уже внутри"], result.Value.Select(c => c.ElementName));
        Assert.False(result.Value.Single(c => c.ElementName == "Длинный").AlreadyInGroup);
        Assert.True(result.Value.Single(c => c.ElementName == "Уже внутри").AlreadyInGroup);
    }

    [Fact]
    public async Task UsesFilterFromQuery_WhenGivenExplicitly()
    {
        var fixture = new TestDb();
        var film = Playable("Кино", 100);
        // У группы своего правила нет — редактор крутит фильтр до сохранения.
        var group = Group.Create("Черновик");

        await using (var seed = fixture.New())
        {
            seed.MediaAssets.Add(film.Asset);
            seed.Shows.Add(film.Show);
            seed.Groups.Add(group);
            await seed.SaveChangesAsync(CancellationToken.None);
        }

        var withFilter = await FindAsync(
            fixture,
            new FindGroupCandidatesQuery(group.Id, new GroupFilter(ShowKinds: [ShowKind.Single]))
        );
        var withoutFilter = await FindAsync(fixture, new FindGroupCandidatesQuery(group.Id));

        Assert.Single(withFilter.Value);
        // Без правила подбирать не из чего — и это отказ, а не пустой список.
        Assert.False(withoutFilter.IsSuccess);
        Assert.Equal(GroupErrors.FilterNotSet, withoutFilter.Error);
    }

    [Fact]
    public async Task Fails_WhenGroupIsGone()
    {
        var fixture = new TestDb();

        var result = await FindAsync(fixture, new FindGroupCandidatesQuery(Guid.NewGuid()));

        Assert.False(result.IsSuccess);
        Assert.Equal(GroupErrors.NotFound, result.Error);
    }
}
