using NSubstitute;
using TeleWave.Application.Common.Interfaces;
using TeleWave.Application.Metadata.FindMissingSeasons;
using TeleWave.Application.Tests.Support;
using TeleWave.Domain.Library;
using Xunit;

namespace TeleWave.Application.Tests.Metadata;

public class FindMissingSeasonsTests
{
    [Fact]
    public async Task ReportsSeasonsWithoutAnyLoadedEpisode()
    {
        var fixture = new TestDb();
        var show = Show.Create("A", ShowKind.Series);
        show.ApplyMetadata("tmdb", "123", "desc", 2000, null);
        // Загружены сезоны 1 и 3; у источника их четыре — не хватает 2 и 4.
        var e11 = show.AddEpisode(Guid.NewGuid());
        e11.SetNumbers(1, 1);
        var e31 = show.AddEpisode(Guid.NewGuid());
        e31.SetNumbers(3, 1);

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

        var resolver = ResolverFor("123", [1, 2, 3, 4]);
        await using var db = fixture.New();
        var result = await new FindMissingSeasonsQueryHandler(db, resolver).Handle(
            new FindMissingSeasonsQuery(show.Id),
            CancellationToken.None
        );

        Assert.True(result.IsSuccess);
        Assert.Equal(4, result.Value.Expected);
        Assert.Equal([1, 3], result.Value.Loaded);
        Assert.Equal([2, 4], result.Value.Missing);
        Assert.Empty(result.Value.Unknown);
    }

    [Fact]
    public async Task ReportsLoadedSeasonsUnknownToSource()
    {
        var fixture = new TestDb();
        var show = Show.Create("A", ShowKind.Series);
        show.ApplyMetadata("tmdb", "123", null, 2000, null);
        var e91 = show.AddEpisode(Guid.NewGuid());
        e91.SetNumbers(9, 1);

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

        var resolver = ResolverFor("123", [1, 2]);
        await using var db = fixture.New();
        var result = await new FindMissingSeasonsQueryHandler(db, resolver).Handle(
            new FindMissingSeasonsQuery(show.Id),
            CancellationToken.None
        );

        Assert.True(result.IsSuccess);
        Assert.Equal([1, 2], result.Value.Missing);
        Assert.Equal([9], result.Value.Unknown);
    }

    [Fact]
    public async Task ReportsExpectedNull_WhenSourceReturnsNothing()
    {
        // Источник промолчал: ни отсутствующих, ни лишних сезонов утверждать нельзя.
        var fixture = new TestDb();
        var show = Show.Create("A", ShowKind.Series);
        show.ApplyMetadata("tmdb", "123", null, 2000, null);
        var e11 = show.AddEpisode(Guid.NewGuid());
        e11.SetNumbers(1, 1);

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

        var resolver = ResolverFor("123", []);
        await using var db = fixture.New();
        var result = await new FindMissingSeasonsQueryHandler(db, resolver).Handle(
            new FindMissingSeasonsQuery(show.Id),
            CancellationToken.None
        );

        Assert.True(result.IsSuccess);
        Assert.Null(result.Value.Expected);
        Assert.Equal([1], result.Value.Loaded);
        Assert.Empty(result.Value.Missing);
        Assert.Empty(result.Value.Unknown);
    }

    [Fact]
    public async Task Fails_ForNonSeries()
    {
        var fixture = new TestDb();
        var show = Show.Create("A", ShowKind.Single);
        show.ApplyMetadata("tmdb", "123", null, 2000, null);
        await using (var seed = fixture.New())
        {
            seed.Shows.Add(show);
            await seed.SaveChangesAsync(CancellationToken.None);
        }

        var resolver = Substitute.For<IMetadataProviderResolver>();
        await using var db = fixture.New();
        var result = await new FindMissingSeasonsQueryHandler(db, resolver).Handle(
            new FindMissingSeasonsQuery(show.Id),
            CancellationToken.None
        );

        Assert.False(result.IsSuccess);
    }

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

        var resolver = Substitute.For<IMetadataProviderResolver>();
        await using var db = fixture.New();
        var result = await new FindMissingSeasonsQueryHandler(db, resolver).Handle(
            new FindMissingSeasonsQuery(show.Id),
            CancellationToken.None
        );

        Assert.False(result.IsSuccess);
    }

    private static IMetadataProviderResolver ResolverFor(string externalId, int[] seasons)
    {
        var provider = Substitute.For<IMetadataProvider>();
        provider
            .GetSeasonNumbersAsync(externalId, Arg.Any<CancellationToken>())
            .Returns(seasons.ToList());
        var resolver = Substitute.For<IMetadataProviderResolver>();
        resolver.Resolve("tmdb").Returns(provider);
        return resolver;
    }
}
