using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging.Abstractions;
using NSubstitute;
using TeleWave.Application.Common.Interfaces;
using TeleWave.Application.Library.Collections.AddShowsToCollection;
using TeleWave.Application.Library.Genres;
using TeleWave.Application.Metadata;
using TeleWave.Application.Metadata.EnrichShows;
using TeleWave.Application.Tests.Support;
using TeleWave.Domain.Library;
using Xunit;

namespace TeleWave.Application.Tests.Library;

/// <summary>Массовые операции над отмеченными шоу: раскладка по франшизам и обогащение.</summary>
public class AddShowsToCollectionTests
{
    [Fact]
    public async Task AddsToExistingCollection_SkippingThoseAlreadyIn()
    {
        var fixture = new TestDb();
        var inside = Show.Create("Уже внутри", ShowKind.Single);
        var outside = Show.Create("Ещё нет", ShowKind.Single);
        var collection = Collection.Create("Франшиза");
        collection.AddShow(inside.Id);

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

        await using var db = fixture.New();
        var result = await new AddShowsToCollectionCommandHandler(db).Handle(
            new AddShowsToCollectionCommand([inside.Id, outside.Id], collection.Id),
            CancellationToken.None
        );
        await db.SaveChangesAsync(CancellationToken.None);

        Assert.True(result.IsSuccess);
        // Повторное добавление молча пропускается: падать из-за одного повтора бессмысленно.
        Assert.Equal(1, result.Value.Added);
        Assert.Equal("Франшиза", result.Value.Name);

        await using var check = fixture.New();
        Assert.Equal(2, check.Collections.Include(c => c.Items).Single().Items.Count);
    }

    [Fact]
    public async Task Fails_WhenCollectionIsGone()
    {
        var fixture = new TestDb();
        var show = Show.Create("Шоу", ShowKind.Single);
        await using (var seed = fixture.New())
        {
            seed.Shows.Add(show);
            await seed.SaveChangesAsync(CancellationToken.None);
        }

        await using var db = fixture.New();
        var result = await new AddShowsToCollectionCommandHandler(db).Handle(
            new AddShowsToCollectionCommand([show.Id], Guid.NewGuid()),
            CancellationToken.None
        );

        Assert.False(result.IsSuccess);
    }

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

        var result = await new AddShowsToCollectionCommandHandler(db).Handle(
            new AddShowsToCollectionCommand([Guid.NewGuid()], NewCollectionName: "Франшиза"),
            CancellationToken.None
        );

        Assert.False(result.IsSuccess);
    }

    [Fact]
    public async Task Enrich_FailsWhenProviderUnavailable()
    {
        var fixture = new TestDb();
        await using var db = fixture.New();
        var resolver = Substitute.For<IMetadataProviderResolver>();
        // Явно: NSubstitute сам подставляет заглушку вместо интерфейсного null, и без этого
        // «источник не настроен» не воспроизводится.
        resolver.Resolve(Arg.Any<string>()).Returns((IMetadataProvider?)null);

        var result = await new EnrichShowsMetadataCommandHandler(
            db,
            resolver,
            new ShowMetadataApplier(
                db,
                resolver,
                Substitute.For<IImageDownloader>(),
                Substitute.For<IImageStore>(),
                new GenreMatcher(db),
                NullLogger<ShowMetadataApplier>.Instance
            )
        ).Handle(new EnrichShowsMetadataCommand([Guid.NewGuid()], "tmdb"), CancellationToken.None);

        Assert.False(result.IsSuccess);
    }

    [Fact]
    public async Task Enrich_IgnoresInterstitials()
    {
        // Ролики метаданными не обогащаются: у них нет ни года, ни постера.
        var fixture = new TestDb();
        var clip = Show.Create("Ролик", ShowKind.Interstitial);
        await using (var seed = fixture.New())
        {
            seed.Shows.Add(clip);
            await seed.SaveChangesAsync(CancellationToken.None);
        }

        var provider = Substitute.For<IMetadataProvider>();
        var resolver = Substitute.For<IMetadataProviderResolver>();
        resolver.Resolve("tmdb").Returns(provider);

        await using var db = fixture.New();
        var result = await new EnrichShowsMetadataCommandHandler(
            db,
            resolver,
            new ShowMetadataApplier(
                db,
                resolver,
                Substitute.For<IImageDownloader>(),
                Substitute.For<IImageStore>(),
                new GenreMatcher(db),
                NullLogger<ShowMetadataApplier>.Instance
            )
        ).Handle(new EnrichShowsMetadataCommand([clip.Id], "tmdb"), CancellationToken.None);

        Assert.True(result.IsSuccess);
        Assert.Equal(0, result.Value.Applied);
        Assert.Empty(result.Value.Skipped);
        await provider
            .DidNotReceive()
            .SearchShowsAsync(Arg.Any<string>(), Arg.Any<ShowKind>(), Arg.Any<CancellationToken>());
    }
}
