using Microsoft.EntityFrameworkCore;
using TeleWave.Application.Library.Collections;
using TeleWave.Application.Library.Collections.FillCollectionPosters;
using TeleWave.Application.Tests.Support;
using TeleWave.Domain.Images;
using TeleWave.Domain.Library;
using Xunit;

namespace TeleWave.Application.Tests.Library;

/// <summary>
/// Постер коллекции по её частям. Правило простое и объяснимое: обложка первой части — франшиза
/// узнаётся именно по ней, а коллекция без картинки теряется в списке.
/// </summary>
public class CollectionPosterTests
{
    private static Show WithPoster(string name, Guid? imageId)
    {
        var show = Show.Create(name, ShowKind.Single);
        if (imageId is { } id)
            show.ApplyMetadata("tmdb", name, null, 1990, id);
        return show;
    }

    private static async Task<int> FillAsync(TestDb fixture, params Guid[] ids)
    {
        await using var db = fixture.New();
        var result = await new FillCollectionPostersCommandHandler(
            db,
            new CollectionPosterPicker(db)
        ).Handle(
            new FillCollectionPostersCommand(ids.Length > 0 ? ids : null),
            CancellationToken.None
        );

        Assert.True(result.IsSuccess);
        await db.SaveChangesAsync(CancellationToken.None);
        return result.Value;
    }

    [Fact]
    public async Task TakesPosterOfTheFirstPart()
    {
        var fixture = new TestDb();
        var poster = Image.Create(ImageCategory.ShowPoster, ".jpg", "Терминатор");
        var second = Image.Create(ImageCategory.ShowPoster, ".jpg", "Терминатор 2");

        var first = WithPoster("Терминатор", poster.Id);
        var sequel = WithPoster("Терминатор 2", second.Id);
        var collection = Collection.Create("Терминатор");
        collection.AddShow(first.Id);
        collection.AddShow(sequel.Id);

        await using (var seed = fixture.New())
        {
            seed.Images.AddRange(poster, second);
            seed.Shows.AddRange(first, sequel);
            seed.Collections.Add(collection);
            await seed.SaveChangesAsync(CancellationToken.None);
        }

        Assert.Equal(1, await FillAsync(fixture));

        await using var db = fixture.New();
        var stored = await db.Collections.SingleAsync(CancellationToken.None);
        Assert.Equal(poster.Id, stored.PosterImageId);
    }

    [Fact]
    public async Task SkipsFirstPartWithoutPoster()
    {
        // У первой части постера нет — берём следующую с обложкой, а не оставляем коллекцию пустой.
        var fixture = new TestDb();
        var poster = Image.Create(ImageCategory.ShowPoster, ".jpg", "Вторая часть");

        var first = WithPoster("Первая часть", null);
        var second = WithPoster("Вторая часть", poster.Id);
        var collection = Collection.Create("Цикл");
        collection.AddShow(first.Id);
        collection.AddShow(second.Id);

        await using (var seed = fixture.New())
        {
            seed.Images.Add(poster);
            seed.Shows.AddRange(first, second);
            seed.Collections.Add(collection);
            await seed.SaveChangesAsync(CancellationToken.None);
        }

        Assert.Equal(1, await FillAsync(fixture));

        await using var db = fixture.New();
        Assert.Equal(
            poster.Id,
            (await db.Collections.SingleAsync(CancellationToken.None)).PosterImageId
        );
    }

    [Fact]
    public async Task KeepsPosterChosenByHand()
    {
        // Выбор человека автоматика не перебивает: он мог поставить кадр, а не обложку первой части.
        var fixture = new TestDb();
        var chosen = Image.Create(ImageCategory.ShowPoster, ".jpg", "Выбранный");
        var showPoster = Image.Create(ImageCategory.ShowPoster, ".jpg", "Часть");

        var show = WithPoster("Часть", showPoster.Id);
        var collection = Collection.Create("Цикл");
        collection.AddShow(show.Id);
        collection.SetPosterImage(chosen.Id);

        await using (var seed = fixture.New())
        {
            seed.Images.AddRange(chosen, showPoster);
            seed.Shows.Add(show);
            seed.Collections.Add(collection);
            await seed.SaveChangesAsync(CancellationToken.None);
        }

        Assert.Equal(0, await FillAsync(fixture));

        await using var db = fixture.New();
        Assert.Equal(
            chosen.Id,
            (await db.Collections.SingleAsync(CancellationToken.None)).PosterImageId
        );
    }

    [Fact]
    public async Task LeavesCollectionWithoutPostersAlone()
    {
        // Ни у одной части нет обложки — ставить нечего, и это не ошибка.
        var fixture = new TestDb();
        var show = WithPoster("Часть", null);
        var collection = Collection.Create("Цикл");
        collection.AddShow(show.Id);

        await using (var seed = fixture.New())
        {
            seed.Shows.Add(show);
            seed.Collections.Add(collection);
            await seed.SaveChangesAsync(CancellationToken.None);
        }

        Assert.Equal(0, await FillAsync(fixture));

        await using var db = fixture.New();
        Assert.Null((await db.Collections.SingleAsync(CancellationToken.None)).PosterImageId);
    }

    [Fact]
    public async Task FillsOnlyRequestedCollections()
    {
        var fixture = new TestDb();
        var poster = Image.Create(ImageCategory.ShowPoster, ".jpg", "Часть");
        var show = WithPoster("Часть", poster.Id);

        var wanted = Collection.Create("Нужная");
        wanted.AddShow(show.Id);
        var other = Collection.Create("Другая");
        other.AddShow(show.Id);

        await using (var seed = fixture.New())
        {
            seed.Images.Add(poster);
            seed.Shows.Add(show);
            seed.Collections.AddRange(wanted, other);
            await seed.SaveChangesAsync(CancellationToken.None);
        }

        Assert.Equal(1, await FillAsync(fixture, wanted.Id));

        await using var db = fixture.New();
        Assert.NotNull((await db.Collections.SingleAsync(c => c.Id == wanted.Id)).PosterImageId);
        Assert.Null((await db.Collections.SingleAsync(c => c.Id == other.Id)).PosterImageId);
    }
}
