using TeleWave.Application.Library.Collections;
using TeleWave.Application.Library.Collections.Suggest;
using TeleWave.Application.Programming.Groups;
using TeleWave.Application.Programming.Groups.Suggest;
using TeleWave.Application.Tests.Support;
using TeleWave.Domain.Library;
using TeleWave.Domain.Programming;
using Xunit;

namespace TeleWave.Application.Tests.Programming;

/// <summary>
/// Отдача предложений наружу: уже созданное помечается, но из списка не пропадает — исчезнувшее
/// без следа предложение читается как сбой, а «уже есть» объясняет, почему кнопки нет.
/// </summary>
public class SuggestionQueriesTests
{
    [Fact]
    public async Task Groups_MarkExistingByName()
    {
        var fixture = new TestDb();
        var genre = Genre.Create("Мультфильм", "animation");
        var first = WithGenre(Show.Create("A", ShowKind.Series), genre);
        var second = WithGenre(Show.Create("B", ShowKind.Series), genre);
        // Регистр не важен: в слоте две «мультфильм» всё равно неразличимы.
        var existing = Group.Create("мультфильм");

        await using (var seed = fixture.New())
        {
            seed.Genres.Add(genre);
            seed.Shows.AddRange(first, second);
            seed.Groups.Add(existing);
            await seed.SaveChangesAsync(CancellationToken.None);
        }

        await using var db = fixture.New();
        var suggestions = await new SuggestGroupsQueryHandler(
            db,
            new GroupSuggestionBuilder(db, new GroupElementResolver(db))
        ).Handle(new SuggestGroupsQuery(), CancellationToken.None);

        var genreSuggestion = Assert.Single(suggestions, s => s.Kind == GroupSuggestionKind.Genre);
        Assert.True(genreSuggestion.AlreadyExists);
        Assert.Equal(2, genreSuggestion.ShowCount);
    }

    [Fact]
    public async Task Groups_MarkExistingByFilter()
    {
        // Группу могли переименовать — правило всё равно то же самое.
        var fixture = new TestDb();
        var genre = Genre.Create("Мультфильм", "animation");
        var first = WithGenre(Show.Create("A", ShowKind.Series), genre);
        var second = WithGenre(Show.Create("B", ShowKind.Series), genre);
        var renamed = Group.Create("Своё название");
        renamed.SetFilter(new GroupFilter(GenreIds: [genre.Id]).ToJson());

        await using (var seed = fixture.New())
        {
            seed.Genres.Add(genre);
            seed.Shows.AddRange(first, second);
            seed.Groups.Add(renamed);
            await seed.SaveChangesAsync(CancellationToken.None);
        }

        await using var db = fixture.New();
        var suggestions = await new SuggestGroupsQueryHandler(
            db,
            new GroupSuggestionBuilder(db, new GroupElementResolver(db))
        ).Handle(new SuggestGroupsQuery(), CancellationToken.None);

        Assert.True(
            Assert.Single(suggestions, s => s.Kind == GroupSuggestionKind.Genre).AlreadyExists
        );
    }

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

        var suggestions = await new SuggestGroupsQueryHandler(
            db,
            new GroupSuggestionBuilder(db, new GroupElementResolver(db))
        ).Handle(new SuggestGroupsQuery(), CancellationToken.None);

        Assert.Empty(suggestions);
    }

    [Fact]
    public async Task Collections_MarkExistingAndCarryParts()
    {
        var fixture = new TestDb();
        var first = Movie("Рокки", 1976);
        var second = Movie("Рокки 2", 1979);
        var existing = Collection.Create("рокки");

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

        await using var db = fixture.New();
        var suggestions = await new SuggestCollectionsQueryHandler(
            db,
            new CollectionSuggestionBuilder(db)
        ).Handle(new SuggestCollectionsQuery(), CancellationToken.None);

        var suggestion = Assert.Single(suggestions);
        Assert.True(suggestion.AlreadyExists);
        // Состав едет наружу целиком: по нему видно, промахнулась ли догадка.
        Assert.Equal([first.Id, second.Id], suggestion.Parts.Select(p => p.ShowId));
        Assert.Equal(1976, suggestion.Parts[0].Year);
    }

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

        var suggestions = await new SuggestCollectionsQueryHandler(
            db,
            new CollectionSuggestionBuilder(db)
        ).Handle(new SuggestCollectionsQuery(), CancellationToken.None);

        Assert.Empty(suggestions);
    }

    [Fact]
    public async Task Collections_CreateFromSuggestion_FailsOnUnknownKey()
    {
        var fixture = new TestDb();
        await using (var seed = fixture.New())
        {
            seed.Shows.AddRange(Movie("Рокки", 1976), Movie("Рокки 2", 1979));
            await seed.SaveChangesAsync(CancellationToken.None);
        }

        await using var db = fixture.New();
        var result = await new CreateCollectionFromSuggestionCommandHandler(
            db,
            new CollectionSuggestionBuilder(db),
            new CollectionPosterPicker(db)
        ).Handle(
            new CreateCollectionFromSuggestionCommand("name:нет-такого"),
            CancellationToken.None
        );

        Assert.False(result.IsSuccess);
    }

    [Fact]
    public async Task Collections_CreateFromSuggestion_BuildsFranchiseAndRefusesDuplicate()
    {
        var fixture = new TestDb();
        var first = Movie("Рокки", 1976);
        var second = Movie("Рокки 2", 1979);
        await using (var seed = fixture.New())
        {
            seed.Shows.AddRange(first, second);
            await seed.SaveChangesAsync(CancellationToken.None);
        }

        await using var db = fixture.New();
        var handler = new CreateCollectionFromSuggestionCommandHandler(
            db,
            new CollectionSuggestionBuilder(db),
            new CollectionPosterPicker(db)
        );

        var created = await handler.Handle(
            new CreateCollectionFromSuggestionCommand("name:рокки"),
            CancellationToken.None
        );
        Assert.True(created.IsSuccess);
        await db.SaveChangesAsync(CancellationToken.None);

        await using var check = fixture.New();
        var collection = check.Collections.Single();
        Assert.Equal("Рокки", collection.Name);

        // Повторное создание — конфликт: одноимённые франшизы неразличимы.
        var again = await handler.Handle(
            new CreateCollectionFromSuggestionCommand("name:рокки"),
            CancellationToken.None
        );
        Assert.False(again.IsSuccess);
    }

    private static Show WithGenre(Show show, Genre genre)
    {
        show.SetGenres([genre.Id]);
        return show;
    }

    private static Show Movie(string name, int year)
    {
        var show = Show.Create(name, ShowKind.Single);
        show.ApplyMetadata("manual", Guid.NewGuid().ToString("N"), null, year, null);
        return show;
    }
}
