using Microsoft.Extensions.Logging.Abstractions;
using NSubstitute;
using TeleWave.Application.Common.Interfaces;
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.Metadata;

/// <summary>
/// Массовое обогащение метаданными. Главное свойство — осторожность: чужой постер и чужой рейтинг
/// у шоу в эфире хуже, чем непроставленные метаданные.
/// </summary>
public class EnrichShowsTests
{
    [Fact]
    public async Task Applies_WhenTitleMatchesExactly()
    {
        var fixture = new TestDb();
        var show = Show.Create("Гравити Фолз", ShowKind.Series);
        show.SetOriginalName("Gravity Falls");
        await SeedAsync(fixture, show);

        var provider = ProviderWith([
            new MetadataCandidate("42", "Gravity Falls", 2012, null, null, ShowKind.Series),
        ]);
        provider
            .GetShowAsync("42", Arg.Any<ShowKind>(), Arg.Any<CancellationToken>())
            .Returns(new ShowMetadata("42", "Gravity Falls", 2012, "desc", null, [], null));

        var result = await RunAsync(fixture, provider, show.Id);

        Assert.Equal(1, result.Applied);
        Assert.Empty(result.Skipped);
    }

    [Fact]
    public async Task Skips_WhenSeveralCandidatesShareTheTitle()
    {
        // Два одноимённых — угадывать нельзя: у них разные постеры и разные рейтинги.
        var fixture = new TestDb();
        var show = Show.Create("Gravity Falls", ShowKind.Series);
        await SeedAsync(fixture, show);

        var provider = ProviderWith([
            new MetadataCandidate("1", "Gravity Falls", null, null, null, ShowKind.Series),
            new MetadataCandidate("2", "Gravity Falls", null, null, null, ShowKind.Series),
        ]);

        var result = await RunAsync(fixture, provider, show.Id);

        Assert.Equal(0, result.Applied);
        Assert.Single(result.Skipped);
    }

    [Fact]
    public async Task Skips_WhenOnlyLooseMatchFound()
    {
        var fixture = new TestDb();
        var show = Show.Create("Пилот", ShowKind.Series);
        await SeedAsync(fixture, show);

        var provider = ProviderWith([
            new MetadataCandidate("7", "Совсем другое шоу", null, null, null, ShowKind.Series),
        ]);

        var result = await RunAsync(fixture, provider, show.Id);

        Assert.Equal(0, result.Applied);
        Assert.Single(result.Skipped);
    }

    [Fact]
    public async Task Skips_AlreadyLinked()
    {
        // Ручной выбор источника массовая операция переписывать не должна.
        var fixture = new TestDb();
        var show = Show.Create("Gravity Falls", ShowKind.Series);
        show.ApplyMetadata("tmdb", "999", null, null, null);
        await SeedAsync(fixture, show);

        var provider = ProviderWith([
            new MetadataCandidate("42", "Gravity Falls", null, null, null, ShowKind.Series),
        ]);

        var result = await RunAsync(fixture, provider, show.Id);

        Assert.Equal(0, result.Applied);
        Assert.Single(result.Skipped);
    }

    private static async Task SeedAsync(TestDb fixture, Show show)
    {
        await using var seed = fixture.New();
        seed.Shows.Add(show);
        await seed.SaveChangesAsync(CancellationToken.None);
    }

    private static IMetadataProvider ProviderWith(IReadOnlyList<MetadataCandidate> candidates)
    {
        var provider = Substitute.For<IMetadataProvider>();
        provider
            .SearchShowsAsync(Arg.Any<string>(), Arg.Any<ShowKind>(), Arg.Any<CancellationToken>())
            .Returns(candidates);
        return provider;
    }

    private static async Task<EnrichShowsResultDto> RunAsync(
        TestDb fixture,
        IMetadataProvider provider,
        Guid showId
    )
    {
        var resolver = Substitute.For<IMetadataProviderResolver>();
        resolver.Resolve("tmdb").Returns(provider);

        await using var db = fixture.New();
        var applier = new ShowMetadataApplier(
            db,
            resolver,
            Substitute.For<IImageDownloader>(),
            Substitute.For<IImageStore>(),
            new GenreMatcher(db),
            NullLogger<ShowMetadataApplier>.Instance
        );

        var result = await new EnrichShowsMetadataCommandHandler(db, resolver, applier).Handle(
            new EnrichShowsMetadataCommand([showId], "tmdb"),
            CancellationToken.None
        );

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