using Microsoft.Extensions.Options;
using TeleWave.Application.Tests.TestSupport;
using TeleWave.Domain.Library;
using TeleWave.Infrastructure.Metadata;
using Xunit;

namespace TeleWave.Application.Tests.Metadata;

/// <summary>
/// Разбор ответов OMDb. Особенности источника, ради которых тесты и написаны: «N/A» вместо
/// отсутствующего значения, флаг <c>Response</c> вместо кода ошибки и жанры одной строкой.
/// </summary>
public class OmdbMetadataProviderTests
{
    private static OmdbMetadataProvider Provider(FakeHttpClientFactory http) =>
        new(
            http,
            Options.Create(new MetadataOptions { Omdb = new OmdbOptions { ApiKey = "key" } })
        );

    [Fact]
    public async Task SearchShows_KeepsOnlyShowsWithId()
    {
        var http = FakeHttpClientFactory.Json(
            """
            {"Search":[
              {"imdbID":"tt1","Title":"Сериал","Year":"2001–2005","Type":"series","Poster":"http://p/1.jpg"},
              {"imdbID":"tt2","Title":"Серия","Type":"episode"},
              {"Title":"Без идентификатора","Type":"movie"},
              {"imdbID":"tt3","Title":"Фильм","Year":"N/A","Type":"movie","Poster":"N/A"}
            ]}
            """
        );

        var found = await Provider(http)
            .SearchShowsAsync("сериал", ShowKind.Series, CancellationToken.None);

        Assert.Contains("type=series", http.LastUrl);
        Assert.Equal(2, found.Count);
        Assert.Equal("tt1", found[0].ExternalId);
        Assert.Equal(2001, found[0].Year);
        Assert.Equal("http://p/1.jpg", found[0].PosterUrl);
        Assert.Equal(ShowKind.Series, found[0].Kind);
        // Отдельные серии просачиваются в выдачу и шоу не годятся, запись без imdbID — тоже.
        Assert.Equal("tt3", found[1].ExternalId);
        Assert.Equal(ShowKind.Single, found[1].Kind);
        Assert.Null(found[1].Year);
        Assert.Null(found[1].PosterUrl);
    }

    [Fact]
    public async Task SearchShows_Single_AsksForMovies()
    {
        var http = FakeHttpClientFactory.Json("{}");

        var found = await Provider(http)
            .SearchShowsAsync("фильм", ShowKind.Single, CancellationToken.None);

        Assert.Contains("type=movie", http.LastUrl);
        Assert.Empty(found);
    }

    [Fact]
    public async Task GetShow_ReadsRatingAndSplitsGenres()
    {
        var http = FakeHttpClientFactory.Json(
            """
            {"Response":"True","Title":"Сериал","Year":"2001–2005","Plot":"описание",
             "Poster":"http://p/1.jpg","Genre":"Action, Sci-Fi","Rated":"TV-14"}
            """
        );

        var show = await Provider(http)
            .GetShowAsync("tt1", ShowKind.Series, CancellationToken.None);

        Assert.Contains("i=tt1", http.LastUrl);
        Assert.NotNull(show);
        Assert.Equal("tt1", show.ExternalId);
        Assert.Equal("Сериал", show.Title);
        Assert.Equal(2001, show.Year);
        Assert.Equal("описание", show.Overview);
        Assert.Equal(["Action", "Sci-Fi"], show.Genres);
        Assert.Equal("TV-14", show.ContentRating);
    }

    [Fact]
    public async Task GetShow_TreatsNotAvailableAsMissing()
    {
        var http = FakeHttpClientFactory.Json(
            """{"Response":"True","Title":"N/A","Plot":"N/A","Poster":"N/A","Genre":"N/A","Rated":"N/A"}"""
        );

        var show = await Provider(http)
            .GetShowAsync("tt1", ShowKind.Single, CancellationToken.None);

        Assert.NotNull(show);
        Assert.Equal("—", show.Title);
        Assert.Null(show.Overview);
        Assert.Null(show.PosterUrl);
        Assert.Null(show.ContentRating);
        Assert.Empty(show.Genres!);
    }

    [Fact]
    public async Task GetShow_ReturnsNull_WhenResponseIsFalse()
    {
        var http = FakeHttpClientFactory.Json(
            """{"Response":"False","Error":"Movie not found!"}"""
        );

        var show = await Provider(http)
            .GetShowAsync("tt0", ShowKind.Series, CancellationToken.None);

        Assert.Null(show);
    }

    [Fact]
    public async Task GetShow_ReturnsNull_WhenRequestFails()
    {
        var show = await Provider(FakeHttpClientFactory.Failing())
            .GetShowAsync("tt1", ShowKind.Series, CancellationToken.None);

        Assert.Null(show);
    }

    [Fact]
    public async Task GetEpisode_ReadsTitleAndReleaseDate()
    {
        var http = FakeHttpClientFactory.Json(
            """
            {"Response":"True","Title":"Серия","Plot":"о серии","Poster":"http://p/s.jpg",
             "Released":"07 Jun 2001"}
            """
        );

        var episode = await Provider(http).GetEpisodeAsync("tt1", 2, 3, CancellationToken.None);

        Assert.Contains("Season=2", http.LastUrl);
        Assert.Contains("Episode=3", http.LastUrl);
        Assert.NotNull(episode);
        Assert.Equal("Серия", episode.Title);
        Assert.Equal("http://p/s.jpg", episode.StillUrl);
        Assert.Equal(new DateOnly(2001, 6, 7), episode.AirDate);
    }

    [Fact]
    public async Task GetEpisode_ReturnsNull_WhenResponseIsFalse()
    {
        var http = FakeHttpClientFactory.Json("""{"Response":"False"}""");

        var episode = await Provider(http).GetEpisodeAsync("tt1", 9, 9, CancellationToken.None);

        Assert.Null(episode);
    }

    [Fact]
    public async Task GetSeasonEpisodeCount_CountsEpisodes()
    {
        var http = FakeHttpClientFactory.Json(
            """{"Response":"True","Episodes":[{"Episode":"1"},{"Episode":"2"}]}"""
        );

        var count = await Provider(http)
            .GetSeasonEpisodeCountAsync("tt1", 2, CancellationToken.None);

        Assert.Equal(2, count);
    }

    [Fact]
    public async Task GetSeasonEpisodeCount_ReturnsNull_WhenSeasonMissing()
    {
        var http = FakeHttpClientFactory.Json("""{"Response":"True"}""");

        var count = await Provider(http)
            .GetSeasonEpisodeCountAsync("tt1", 9, CancellationToken.None);

        Assert.Null(count);
    }

    [Fact]
    public async Task GetSeasonNumbers_NumbersSeasonsContinuously()
    {
        // Перечня сезонов OMDb не отдаёт — только их количество строкой.
        var http = FakeHttpClientFactory.Json("""{"Response":"True","totalSeasons":"3"}""");

        var seasons = await Provider(http).GetSeasonNumbersAsync("tt1", CancellationToken.None);

        Assert.Equal([1, 2, 3], seasons);
    }

    [Fact]
    public async Task GetSeasonNumbers_ReturnsEmpty_WhenTotalIsNotANumber()
    {
        var http = FakeHttpClientFactory.Json("""{"Response":"True","totalSeasons":"N/A"}""");

        var seasons = await Provider(http).GetSeasonNumbersAsync("tt1", CancellationToken.None);

        Assert.Empty(seasons);
    }

    [Fact]
    public async Task GetSeasonNumbers_ReturnsEmpty_WhenResponseIsFalse()
    {
        var http = FakeHttpClientFactory.Json("""{"Response":"False"}""");

        var seasons = await Provider(http).GetSeasonNumbersAsync("tt1", CancellationToken.None);

        Assert.Empty(seasons);
    }
}
