using ErsatzTV.Application.Search.Queries;
using ErsatzTV.Core.Api.Search;
using ErsatzTV.Core.Domain;
using ErsatzTV.Infrastructure.Data;
using ErsatzTV.Tests.Support;
using LanguageExt;
using NUnit.Framework;
using Shouldly;

namespace ErsatzTV.Tests.Application.Search;

[TestFixture]
public class GetSearchFieldValuesHandlerTests
{
    private InMemoryTvContext _db = null!;

    [SetUp]
    public async Task SetUp() => _db = await InMemoryTvContext.CreateAsync();

    [TearDown]
    public async Task TearDown() => await _db.DisposeAsync();

    [Test]
    public async Task Returns_Distinct_Whole_Values_For_Genre_With_Prefix_And_Limit()
    {
        await using (TvContext context = _db.CreateContext())
        {
            context.Set<Genre>().AddRange(
                new Genre { Name = "Action" },
                new Genre { Name = "Adventure" },
                new Genre { Name = "Animation" },
                new Genre { Name = "Comedy" });
            await context.SaveChangesAsync();
        }

        var handler = new GetSearchFieldValuesHandler(_db.Factory);

        Option<SearchFieldValuesResponseModel> result = await handler.Handle(
            new GetSearchFieldValues("genre", "A", 50),
            CancellationToken.None);

        result.IsSome.ShouldBeTrue();
        result.IfSome(r => r.Values.ShouldBe(new List<string> { "Action", "Adventure", "Animation" }));

        Option<SearchFieldValuesResponseModel> limited = await handler.Handle(
            new GetSearchFieldValues("genre", "A", 2),
            CancellationToken.None);

        limited.IsSome.ShouldBeTrue();
        limited.IfSome(r => r.Values.ShouldBe(new List<string> { "Action", "Adventure" }));
    }

    [Test]
    public async Task Splits_Content_Rating_On_Slash()
    {
        await using (TvContext context = _db.CreateContext())
        {
            context.MovieMetadata.Add(new MovieMetadata
            {
                MetadataKind = MetadataKind.External,
                DateAdded = DateTime.UtcNow,
                DateUpdated = DateTime.UtcNow,
                ContentRating = "PG-13/TV-14"
            });
            await context.SaveChangesAsync();
        }

        var handler = new GetSearchFieldValuesHandler(_db.Factory);

        Option<SearchFieldValuesResponseModel> result = await handler.Handle(
            new GetSearchFieldValues("content_rating", string.Empty, 50),
            CancellationToken.None);

        result.IsSome.ShouldBeTrue();
        result.IfSome(r => r.Values.ShouldBe(new List<string> { "PG-13", "TV-14" }));
    }

    [Test]
    public async Task Returns_MediaItemState_Enum_Names_Without_Seeding()
    {
        var handler = new GetSearchFieldValuesHandler(_db.Factory);

        Option<SearchFieldValuesResponseModel> result = await handler.Handle(
            new GetSearchFieldValues("state", string.Empty, 50),
            CancellationToken.None);

        result.IsSome.ShouldBeTrue();
        result.IfSome(r => r.Values.ShouldBe(
            new List<string> { "FileNotFound", "Normal", "RemoteOnly", "Unavailable" }));
    }

    [Test]
    public async Task Returns_NotFound_For_Excluded_Text_Field_Title()
    {
        var handler = new GetSearchFieldValuesHandler(_db.Factory);

        Option<SearchFieldValuesResponseModel> result = await handler.Handle(
            new GetSearchFieldValues("title", string.Empty, 50),
            CancellationToken.None);

        result.IsNone.ShouldBeTrue();
    }

    [Test]
    public async Task Returns_NotFound_For_NonText_Field()
    {
        var handler = new GetSearchFieldValuesHandler(_db.Factory);

        // "minutes" is a "number" field in SearchFieldCatalog, not "text"
        Option<SearchFieldValuesResponseModel> result = await handler.Handle(
            new GetSearchFieldValues("minutes", string.Empty, 50),
            CancellationToken.None);

        result.IsNone.ShouldBeTrue();
    }

    [Test]
    public async Task Returns_NotFound_For_Unknown_Field()
    {
        var handler = new GetSearchFieldValuesHandler(_db.Factory);

        Option<SearchFieldValuesResponseModel> result = await handler.Handle(
            new GetSearchFieldValues("nope", string.Empty, 50),
            CancellationToken.None);

        result.IsNone.ShouldBeTrue();
    }

    [Test]
    public async Task Matches_Case_Insensitive_Prefix()
    {
        await using (TvContext context = _db.CreateContext())
        {
            context.Set<Genre>().AddRange(
                new Genre { Name = "Action" },
                new Genre { Name = "Comedy" });
            await context.SaveChangesAsync();
        }

        var handler = new GetSearchFieldValuesHandler(_db.Factory);

        Option<SearchFieldValuesResponseModel> result = await handler.Handle(
            new GetSearchFieldValues("genre", "a", 50),
            CancellationToken.None);

        result.IsSome.ShouldBeTrue();
        result.IfSome(r => r.Values.ShouldBe(new List<string> { "Action" }));
    }

    [Test]
    public async Task Excludes_Network_And_Country_Tags_From_Tag_Field_And_Routes_Network_Tags_To_Network_Field()
    {
        await using (TvContext context = _db.CreateContext())
        {
            context.Set<Tag>().AddRange(
                new Tag { Name = "PlainTag" },
                new Tag { Name = "HBO", ExternalTypeId = Tag.PlexNetworkTypeId },
                new Tag { Name = "USA", ExternalTypeId = Tag.NfoCountryTypeId });
            await context.SaveChangesAsync();
        }

        var handler = new GetSearchFieldValuesHandler(_db.Factory);

        Option<SearchFieldValuesResponseModel> tagResult = await handler.Handle(
            new GetSearchFieldValues("tag", string.Empty, 50),
            CancellationToken.None);

        tagResult.IsSome.ShouldBeTrue();
        tagResult.IfSome(r => r.Values.ShouldBe(new List<string> { "PlainTag" }));

        Option<SearchFieldValuesResponseModel> networkResult = await handler.Handle(
            new GetSearchFieldValues("network", string.Empty, 50),
            CancellationToken.None);

        networkResult.IsSome.ShouldBeTrue();
        networkResult.IfSome(r => r.Values.ShouldBe(new List<string> { "HBO" }));
    }

    [Test]
    public async Task Dedupes_Repeated_Values()
    {
        await using (TvContext context = _db.CreateContext())
        {
            context.Set<Genre>().AddRange(
                new Genre { Name = "Action" },
                new Genre { Name = "Action" });
            await context.SaveChangesAsync();
        }

        var handler = new GetSearchFieldValuesHandler(_db.Factory);

        Option<SearchFieldValuesResponseModel> result = await handler.Handle(
            new GetSearchFieldValues("genre", string.Empty, 50),
            CancellationToken.None);

        result.IsSome.ShouldBeTrue();
        result.IfSome(r => r.Values.ShouldBe(new List<string> { "Action" }));
    }
}
