using JSMR.Application.Common.Search;
using JSMR.Application.VoiceWorks.Queries.Search;
using JSMR.Domain.Enums;
using JSMR.Infrastructure.Data;
using JSMR.Infrastructure.Data.Repositories.VoiceWorks;
using Shouldly;
using SortDirection = JSMR.Application.Common.Search.SortDirection;

namespace JSMR.Tests.Search.VoiceWork;

public class VoiceWorkSearchProviderTests(VoiceWorkSearchProviderFixture fixture) : IClassFixture<VoiceWorkSearchProviderFixture>
{
    private async Task<SearchResult<VoiceWorkSearchResult>> SearchAsync(SearchOptions<VoiceWorkSearchCriteria, VoiceWorkSortField> options)
    {
        AppDbContext context = fixture.DbContext!;
        MySqlVoiceWorkFullTextSearch fullTextSearch = new();
        VoiceWorkSearchProvider provider = new(context, fullTextSearch);

        return await provider.SearchAsync(options, TestContext.Current.CancellationToken);
    }

    [Fact]
    public async Task Filter_Default()
    {
        var options = new SearchOptions<VoiceWorkSearchCriteria, VoiceWorkSortField>()
        {
            Criteria = new()
            {
                SaleStatus = SaleStatus.Available,
                CircleStatus = CircleStatus.NotBlacklisted
            }
        };

        var result = await SearchAsync(options);

        result.Items.Length.ShouldBe(2);
        result.TotalItems.ShouldBe(2);
        result.Items.ShouldAllBe(item => item.SalesDate != null);
        result.Items.ShouldNotContain(item => item.ExpectedDate != null);
    }

    [Fact]
    public async Task Filter_Upcoming_Favorite()
    {
        var options = new SearchOptions<VoiceWorkSearchCriteria, VoiceWorkSortField>()
        {
            Criteria = new()
            {
                SaleStatus = SaleStatus.Upcoming,
                CircleStatus = CircleStatus.Favorited
            }
        };

        var result = await SearchAsync(options);

        result.Items.Length.ShouldBe(1);
        result.TotalItems.ShouldBe(1);
        result.Items.ShouldAllBe(item => item.ExpectedDate != null);
        result.Items.ShouldNotContain(item => item.SalesDate != null);
    }

    [Fact]
    public async Task Filter_Availble_Blacklisted()
    {
        var options = new SearchOptions<VoiceWorkSearchCriteria, VoiceWorkSortField>()
        {
            Criteria = new()
            {
                SaleStatus = SaleStatus.Available,
                CircleStatus = CircleStatus.Blacklisted
            }
        };

        var result = await SearchAsync(options);

        result.Items.Length.ShouldBe(1);
        result.TotalItems.ShouldBe(1);
        result.Items.ShouldAllBe(item => item.SalesDate != null);
        result.Items.ShouldNotContain(item => item.ExpectedDate != null);
    }

    [Fact]
    public async Task Filter_Keywords_Basic()
    {
        var options = new SearchOptions<VoiceWorkSearchCriteria, VoiceWorkSortField>()
        {
            Criteria = new()
            {
                Keywords = "ASMR"
            }
        };

        var result = await SearchAsync(options);

        result.Items.Length.ShouldBe(3);
        result.TotalItems.ShouldBe(3);
        result.Items.ShouldAllBe(item => item.Tags.Any(tag => tag.Name == "ASMR") || item.ProductName.Contains("ASMR") || (item.Description ?? string.Empty).Contains("ASMR"));
    }

    [Fact]
    public async Task Filter_Keywords_Not_Good()
    {
        var options = new SearchOptions<VoiceWorkSearchCriteria, VoiceWorkSortField>()
        {
            Criteria = new()
            {
                Keywords = "ASMR -Good"
            }
        };

        var result = await SearchAsync(options);

        result.Items.Length.ShouldBe(2);
        result.TotalItems.ShouldBe(2);
        result.Items.ShouldAllBe(item => item.Tags.Any(tag => tag.Name == "ASMR") || item.ProductName.Contains("ASMR") || (item.Description ?? string.Empty).Contains("ASMR"));
        result.Items.ShouldAllBe(item => !item.Tags.Any(tag => tag.Name == "Good") || !item.ProductName.Contains("Good") || !(item.Description ?? string.Empty).Contains("Good"));
    }

    [Fact]
    public async Task Filter_Keywords_Dreams_And_Amazing_Or_Favorite()
    {
        var options = new SearchOptions<VoiceWorkSearchCriteria, VoiceWorkSortField>()
        {
            Criteria = new()
            {
                Keywords = "Dreams + (Amazing|Favorite)"
            }
        };

        var result = await SearchAsync(options);

        result.Items.Length.ShouldBe(2);
        result.TotalItems.ShouldBe(2);

        result.Items
            .OrderBy(item => item.ProductId)
            .Select(item => item.ProductId)
            .ShouldBe(["RJ0000002", "RJ0000005"]);
    }

    [Fact]
    public async Task Filter_Keywords_Phrase_Search()
    {
        var options = new SearchOptions<VoiceWorkSearchCriteria, VoiceWorkSortField>()
        {
            Criteria = new()
            {
                Keywords = "\"All Your Favorite\""
            }
        };

        var result = await SearchAsync(options);

        result.Items.Length.ShouldBe(1);
        result.TotalItems.ShouldBe(1);
        result.Items.ShouldAllBe(item => (item.Description ?? string.Empty).Contains("All Your Favorite", StringComparison.OrdinalIgnoreCase));
    }

    [Fact]
    public async Task Filter_Tags_Favorite_Exclude_Blacklisted()
    {
        var options = new SearchOptions<VoiceWorkSearchCriteria, VoiceWorkSortField>()
        {
            Criteria = new()
            {
                TagStatus = TagStatus.FavoriteExcludeBlacklist
            }
        };

        var result = await SearchAsync(options);

        result.Items.Length.ShouldBe(1);
        result.TotalItems.ShouldBe(1);

        result.Items
           .OrderBy(item => item.ProductId)
           .Select(item => item.ProductId)
           .ShouldBe(["RJ0000002"]);
    }

    [Fact]
    public async Task Filter_Tags_Favorite_Include_Blacklisted()
    {
        var options = new SearchOptions<VoiceWorkSearchCriteria, VoiceWorkSortField>()
        {
            Criteria = new()
            {
                TagStatus = TagStatus.FavoriteIncludeBlacklist
            }
        };

        var result = await SearchAsync(options);

        result.Items.Length.ShouldBe(2);
        result.TotalItems.ShouldBe(2);

        result.Items
           .OrderBy(item => item.ProductId)
           .Select(item => item.ProductId)
           .ShouldBe(["RJ0000002", "RJ0000003"]);
    }

    [Fact]
    public async Task Filter_Tags_Not_Blacklisted()
    {
        var options = new SearchOptions<VoiceWorkSearchCriteria, VoiceWorkSortField>()
        {
            Criteria = new()
            {
                TagStatus = TagStatus.NotBlacklisted
            }
        };

        var result = await SearchAsync(options);

        result.Items.Length.ShouldBe(4);
        result.TotalItems.ShouldBe(4);

        result.Items
           .OrderBy(item => item.ProductId)
           .Select(item => item.ProductId)
           .ShouldBe(["RJ0000001", "RJ0000002", "RJ0000004", "RJ0000005"]);
    }

    [Fact]
    public async Task Filter_Tags_Blacklisted()
    {
        var options = new SearchOptions<VoiceWorkSearchCriteria, VoiceWorkSortField>()
        {
            Criteria = new()
            {
                TagStatus = TagStatus.Blacklisted
            }
        };

        var result = await SearchAsync(options);

        result.Items.Length.ShouldBe(1);
        result.TotalItems.ShouldBe(1);

        result.Items
           .OrderBy(item => item.ProductId)
           .Select(item => item.ProductId)
           .ShouldBe(["RJ0000003"]);
    }

    [Fact]
    public async Task Filter_TagIds_Or()
    {
        var options = new SearchOptions<VoiceWorkSearchCriteria, VoiceWorkSortField>()
        {
            Criteria = new()
            {
                TagIds = [1,2],
                IncludeAllTags = false
            }
        };

        var result = await SearchAsync(options);

        result.Items.Length.ShouldBe(2);
        result.TotalItems.ShouldBe(2);

        result.Items
           .OrderBy(item => item.ProductId)
           .Select(item => item.ProductId)
           .ShouldBe(["RJ0000001", "RJ0000002"]);
    }

    [Fact]
    public async Task Filter_TagIds_And()
    {
        var options = new SearchOptions<VoiceWorkSearchCriteria, VoiceWorkSortField>()
        {
            Criteria = new()
            {
                TagIds = [1, 2],
                IncludeAllTags = true
            }
        };

        var result = await SearchAsync(options);

        result.Items.Length.ShouldBe(1);
        result.TotalItems.ShouldBe(1);

        result.Items
           .OrderBy(item => item.ProductId)
           .Select(item => item.ProductId)
           .ShouldBe(["RJ0000001"]);
    }

    [Fact]
    public async Task Filter_Creators_Favorite_Exclude_Blacklisted()
    {
        var options = new SearchOptions<VoiceWorkSearchCriteria, VoiceWorkSortField>()
        {
            Criteria = new()
            {
                CreatorStatus = CreatorStatus.FavoriteExcludeBlacklist
            }
        };

        var result = await SearchAsync(options);

        result.Items.Length.ShouldBe(2);
        result.TotalItems.ShouldBe(2);

        result.Items
           .OrderBy(item => item.ProductId)
           .Select(item => item.ProductId)
           .ShouldBe(["RJ0000002", "RJ0000005"]);
    }

    [Fact]
    public async Task Filter_Creators_Favorite_Include_Blacklisted()
    {
        var options = new SearchOptions<VoiceWorkSearchCriteria, VoiceWorkSortField>()
        {
            Criteria = new()
            {
                CreatorStatus = CreatorStatus.FavoriteIncludeBlacklist
            }
        };

        var result = await SearchAsync(options);

        result.Items.Length.ShouldBe(3);
        result.TotalItems.ShouldBe(3);

        result.Items
           .OrderBy(item => item.ProductId)
           .Select(item => item.ProductId)
           .ShouldBe(["RJ0000002", "RJ0000003", "RJ0000005"]);
    }

    [Fact]
    public async Task Filter_Creators_Not_Blacklisted()
    {
        var options = new SearchOptions<VoiceWorkSearchCriteria, VoiceWorkSortField>()
        {
            Criteria = new()
            {
                CreatorStatus = CreatorStatus.NotBlacklisted
            }
        };

        var result = await SearchAsync(options);

        result.Items.Length.ShouldBe(4);
        result.TotalItems.ShouldBe(4);

        result.Items
           .OrderBy(item => item.ProductId)
           .Select(item => item.ProductId)
           .ShouldBe(["RJ0000001", "RJ0000002", "RJ0000004", "RJ0000005"]);
    }

    [Fact]
    public async Task Filter_Creators_Blacklisted()
    {
        var options = new SearchOptions<VoiceWorkSearchCriteria, VoiceWorkSortField>()
        {
            Criteria = new()
            {
                CreatorStatus = CreatorStatus.Blacklisted
            }
        };

        var result = await SearchAsync(options);

        result.Items.Length.ShouldBe(1);
        result.TotalItems.ShouldBe(1);

        result.Items
           .OrderBy(item => item.ProductId)
           .Select(item => item.ProductId)
           .ShouldBe(["RJ0000003"]);
    }

    [Fact]
    public async Task Filter_CreatorIds_Or()
    {
        var options = new SearchOptions<VoiceWorkSearchCriteria, VoiceWorkSortField>()
        {
            Criteria = new()
            {
                CreatorIds = [1, 4],
                IncludeAllCreators = false
            }
        };

        var result = await SearchAsync(options);

        result.Items.Length.ShouldBe(3);
        result.TotalItems.ShouldBe(3);

        result.Items
           .OrderBy(item => item.ProductId)
           .Select(item => item.ProductId)
           .ShouldBe(["RJ0000002", "RJ0000003", "RJ0000005"]);
    }

    [Fact]
    public async Task Filter_CreatorIds_And()
    {
        var options = new SearchOptions<VoiceWorkSearchCriteria, VoiceWorkSortField>()
        {
            Criteria = new()
            {
                CreatorIds = [1, 4],
                IncludeAllCreators = true
            }
        };

        var result = await SearchAsync(options);

        result.Items.Length.ShouldBe(1);
        result.TotalItems.ShouldBe(1);

        result.Items
           .OrderBy(item => item.ProductId)
           .Select(item => item.ProductId)
           .ShouldBe(["RJ0000005"]);
    }

    [Fact]
    public async Task Sort_By_Release_Date_Ascending()
    {
        var options = new SearchOptions<VoiceWorkSearchCriteria, VoiceWorkSortField>()
        {
            SortOptions =
            [
                new(VoiceWorkSortField.AnyReleaseDate, SortDirection.Ascending),
                new(VoiceWorkSortField.ProductId, SortDirection.Ascending)
            ]
        };

        var result = await SearchAsync(options);

        result.Items
           .Select(item => item.ProductId)
           .ShouldBe(["RJ0000001", "RJ0000004", "RJ0000003", "RJ0000002", "RJ0000005"]);
    }

    [Fact]
    public async Task Sort_By_Release_Date_Descending()
    {
        var options = new SearchOptions<VoiceWorkSearchCriteria, VoiceWorkSortField>()
        {
            SortOptions =
            [
                new(VoiceWorkSortField.AnyReleaseDate, SortDirection.Descending),
                new(VoiceWorkSortField.ProductId, SortDirection.Ascending)
            ]
        };

        var result = await SearchAsync(options);

        result.Items
           .Select(item => item.ProductId)
           .ShouldBe(["RJ0000005", "RJ0000002", "RJ0000003", "RJ0000001", "RJ0000004"]);
    }

    [Fact]
    public async Task Sort_By_Downloads_Ascending()
    {
        var options = new SearchOptions<VoiceWorkSearchCriteria, VoiceWorkSortField>()
        {
            SortOptions =
            [
                new(VoiceWorkSortField.Downloads, SortDirection.Ascending),
                new(VoiceWorkSortField.ProductId, SortDirection.Ascending)
            ]
        };

        var result = await SearchAsync(options);

        result.Items
           .Select(item => item.ProductId)
           .ShouldBe(["RJ0000004", "RJ0000005", "RJ0000003", "RJ0000001", "RJ0000002"]);
    }

    [Fact]
    public async Task Sort_By_Downloads_Descending()
    {
        var options = new SearchOptions<VoiceWorkSearchCriteria, VoiceWorkSortField>()
        {
            SortOptions =
            [
                new(VoiceWorkSortField.Downloads, SortDirection.Descending),
                new(VoiceWorkSortField.ProductId, SortDirection.Ascending)
            ]
        };

        var result = await SearchAsync(options);

        result.Items
           .Select(item => item.ProductId)
           .ShouldBe(["RJ0000002", "RJ0000001", "RJ0000003", "RJ0000004", "RJ0000005"]);
    }

    [Fact]
    public async Task Sort_By_WishlistCount_Ascending()
    {
        var options = new SearchOptions<VoiceWorkSearchCriteria, VoiceWorkSortField>()
        {
            SortOptions =
            [
                new(VoiceWorkSortField.WishlistCount, SortDirection.Ascending)
            ]
        };

        var result = await SearchAsync(options);

        result.Items
           .Select(item => item.ProductId)
           .ShouldBe(["RJ0000003", "RJ0000004", "RJ0000001", "RJ0000005", "RJ0000002"]);
    }

    [Fact]
    public async Task Sort_By_WishlistCount_Descending()
    {
        var options = new SearchOptions<VoiceWorkSearchCriteria, VoiceWorkSortField>()
        {
            SortOptions =
            [
                new(VoiceWorkSortField.WishlistCount, SortDirection.Descending)
            ]
        };

        var result = await SearchAsync(options);

        result.Items
           .Select(item => item.ProductId)
           .ShouldBe(["RJ0000002", "RJ0000005", "RJ0000001", "RJ0000004", "RJ0000003"]);
    }

    [Fact]
    public async Task Sort_By_StarRating_Ascending()
    {
        var options = new SearchOptions<VoiceWorkSearchCriteria, VoiceWorkSortField>()
        {
            SortOptions =
            [
                new(VoiceWorkSortField.StarRating, SortDirection.Ascending),
                new(VoiceWorkSortField.ProductId, SortDirection.Ascending)
            ]
        };

        var result = await SearchAsync(options);

        result.Items
           .Select(item => item.ProductId)
           .ShouldBe(["RJ0000004", "RJ0000005", "RJ0000003", "RJ0000001", "RJ0000002"]);
    }

    [Fact]
    public async Task Sort_By_StarRating_Descending()
    {
        var options = new SearchOptions<VoiceWorkSearchCriteria, VoiceWorkSortField>()
        {
            SortOptions =
            [
                new(VoiceWorkSortField.StarRating, SortDirection.Descending),
                new(VoiceWorkSortField.ProductId, SortDirection.Ascending)
            ]
        };

        var result = await SearchAsync(options);

        result.Items
           .Select(item => item.ProductId)
           .ShouldBe(["RJ0000002", "RJ0000001", "RJ0000003", "RJ0000004", "RJ0000005"]);
    }

    [Fact]
    public async Task Filter_Scheduled_Release_Date_Range()
    {
        var options = new SearchOptions<VoiceWorkSearchCriteria, VoiceWorkSortField>()
        {
            Criteria = new()
            {
                ScheduledReleaseDateStart = new DateOnly(2025, 1, 1),
                ScheduledReleaseDateEnd = new DateOnly(2025, 1, 13)
            }
        };

        var result = await SearchAsync(options);

        result.Items
            .OrderBy(item => item.ProductId)
            .Select(item => item.ProductId)
            .ShouldBe(["RJ0000005"]);
    }

    [Fact]
    public async Task Filter_Release_Date_Range()
    {
        var options = new SearchOptions<VoiceWorkSearchCriteria, VoiceWorkSortField>()
        {
            Criteria = new()
            {
                ReleaseDateStart = new DateOnly(2025, 1, 1),
                ReleaseDateEnd = new DateOnly(2025, 1, 2)
            }
        };

        var result = await SearchAsync(options);

        result.Items
            .OrderBy(item => item.ProductId)
            .Select(item => item.ProductId)
            .ShouldBe(["RJ0000001", "RJ0000003"]);
    }

    [Fact]
    public async Task Filter_Downloads_Range()
    {
        var options = new SearchOptions<VoiceWorkSearchCriteria, VoiceWorkSortField>()
        {
            Criteria = new()
            {
                MinDownloads = 100,
                MaxDownloads = 10000
            }
        };

        var result = await SearchAsync(options);

        result.Items
            .OrderBy(item => item.ProductId)
            .Select(item => item.ProductId)
            .ShouldBe(["RJ0000001", "RJ0000002"]);
    }

    [Fact]
    public async Task Filter_Favorite()
    {
        var options = new SearchOptions<VoiceWorkSearchCriteria, VoiceWorkSortField>()
        {
            Criteria = new()
            {
                ShowFavoriteVoiceWorks = true
            }
        };

        var result = await SearchAsync(options);

        result.Items
            .OrderBy(item => item.ProductId)
            .Select(item => item.ProductId)
            .ShouldBe(["RJ0000002"]);
    }

    [Fact]
    public async Task Filter_Language()
    {
        var options = new SearchOptions<VoiceWorkSearchCriteria, VoiceWorkSortField>()
        {
            Criteria = new()
            {
                SupportedLanguages = [Language.English]
            }
        };

        var result = await SearchAsync(options);

        result.Items
            .OrderBy(item => item.ProductId)
            .Select(item => item.ProductId)
            .ShouldBe(["RJ0000005"]);
    }

    [Fact]
    public async Task Filter_Age_Rating()
    {
        var options = new SearchOptions<VoiceWorkSearchCriteria, VoiceWorkSortField>()
        {
            Criteria = new()
            {
                AgeRatings = [AgeRating.R15]
            }
        };

        var result = await SearchAsync(options);

        result.Items
            .OrderBy(item => item.ProductId)
            .Select(item => item.ProductId)
            .ShouldBe(["RJ0000005"]);
    }

    [Fact]
    public async Task Filter_AI_Generation()
    {
        var options = new SearchOptions<VoiceWorkSearchCriteria, VoiceWorkSortField>()
        {
            Criteria = new()
            {
                AIGenerationOptions = [AIGeneration.Partial]
            }
        };

        var result = await SearchAsync(options);

        result.Items
            .OrderBy(item => item.ProductId)
            .Select(item => item.ProductId)
            .ShouldBe(["RJ0000003"]);
    }
}