using JSMR.Application.Common.Search;
using JSMR.Application.Enums;
using JSMR.Application.VoiceWorks.Queries.Search;
using JSMR.Domain.Enums;
using JSMR.UI.Blazor.Enums;
using Microsoft.AspNetCore.WebUtilities;
using System.Globalization;

namespace JSMR.UI.Blazor.Filters;

public abstract record FilterStateBase<TSearchRequest> : IFilterState<TSearchRequest>
{
    public abstract QueryParameters ToQuery();
    public abstract TSearchRequest ToSearchRequest();
    //public static abstract IFilterState<TSearchRequest> FromQuery(string query);
}

public sealed record VoiceWorkFilterState
{
    // Core
    public string? Keywords { get; init; }
    public Locale Locale { get; init; } = Locale.English;

    // Statuses
    public SaleStatus? SaleStatus { get; init; }
    public CircleStatus? CircleStatus { get; init; }
    public TagStatus? TagStatus { get; init; }
    public CreatorStatus? CreatorStatus { get; init; }

    // Multi-selects
    public int[] TagIds { get; init; } = Array.Empty<int>();
    public bool IncludeAllTags { get; init; }
    public int[] CreatorIds { get; init; } = Array.Empty<int>();
    public bool IncludeAllCreators { get; init; }
    public Language[] SupportedLanguages { get; init; } = Array.Empty<Language>();
    public AgeRating[] AgeRatings { get; init; } = Array.Empty<AgeRating>();

    // Misc flags
    public bool ShowFavorite { get; init; }
    public bool ShowInvalid { get; init; }

    // Dates (use yyyy-MM-dd in URL)
    public DateOnly? ReleaseDateStart { get; init; }
    public DateOnly? ReleaseDateEnd { get; init; }

    // Sorting & paging
    public VoiceWorkSort Sort { get; init; } = VoiceWorkSort.ReleaseDateNewToOld;
    public int PageNumber { get; init; } = 1;
    public int PageSize { get; init; } = 100;

    // ---- Query <-> State helpers ----
    public Dictionary<string, string?> ToQuery()
    {
        Dictionary<string, string?> query = [];

        void Set(string key, string? value, bool includeWhenEmpty = false)
        {
            if (includeWhenEmpty || !string.IsNullOrWhiteSpace(value))
                query[key] = value;
        }

        string Join<T>(IEnumerable<T> xs) => string.Join(",", xs);

        Set("keywords", Keywords);

        if (Locale != Locale.English)
            Set("locale", Locale.ToString());

        if (SaleStatus is not null)
            Set("sale", SaleStatus.ToString());

        if (CircleStatus is not null)
            Set("circle", CircleStatus.ToString());

        if (TagStatus is not null)
            Set("tag", TagStatus.ToString());

        if (CreatorStatus is not null)
            Set("creator", CreatorStatus.ToString());

        if (TagIds.Length > 0)
            Set("tagIds", Join(TagIds));

        if (IncludeAllTags)
            Set("allTags", "1");

        if (CreatorIds.Length > 0)
            Set("creatorIds", Join(CreatorIds));

        if (IncludeAllCreators)
            Set("allCreators", "1");

        if (SupportedLanguages.Length > 0)
            Set("langs", Join(SupportedLanguages));

        if (AgeRatings.Length > 0)
            Set("ages", Join(AgeRatings));

        if (ShowFavorite)
            Set("fav", "1");

        if (ShowInvalid)
            Set("invalid", "1");

        if (ReleaseDateStart is not null)
            Set("rs", ReleaseDateStart.Value.ToString("yyyy-MM-dd"));

        if (ReleaseDateEnd is not null)
            Set("re", ReleaseDateEnd.Value.ToString("yyyy-MM-dd"));

        if (Sort != VoiceWorkSort.ReleaseDateNewToOld)
            Set("sort", Sort.ToString());

        if (PageNumber != 1)
            Set("pageNumber", PageNumber.ToString(CultureInfo.InvariantCulture));

        if (PageSize != 100)
            Set("pageSize", PageSize.ToString(CultureInfo.InvariantCulture));

        return query;
    }

    public static VoiceWorkFilterState FromQuery(string query)
    {
        var dict = QueryHelpers.ParseQuery(query);

        T? GetEnum<T>(string key) where T : struct, Enum
            => dict.TryGetValue(key, out var v) && Enum.TryParse<T>(v!, true, out var e) ? e : null;

        string? Get(string key) => dict.TryGetValue(key, out var v) ? v!.ToString() : null;
        int[] GetInts(string key) => Get(key)?.Split(',', StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToArray() ?? Array.Empty<int>();
        TEnum[] GetEnums<TEnum>(string key) where TEnum : struct, Enum
            => Get(key)?.Split(',', StringSplitOptions.RemoveEmptyEntries).Select(s => Enum.TryParse<TEnum>(s, true, out var e) ? e : (TEnum?)null).Where(e => e is not null).Select(e => e!.Value).ToArray()
               ?? Array.Empty<TEnum>();
        DateOnly? GetDate(string key) => DateOnly.TryParseExact(Get(key), "yyyy-MM-dd", out var d) ? d : null;
        bool Has(string key) => dict.ContainsKey(key);

        return new VoiceWorkFilterState
        {
            Keywords = Get("keywords"),
            Locale = GetEnum<Locale>("locale") ?? Locale.English,

            SaleStatus = GetEnum<SaleStatus>("sale"),
            CircleStatus = GetEnum<CircleStatus>("circle"),
            TagStatus = GetEnum<TagStatus>("tag"),
            CreatorStatus = GetEnum<CreatorStatus>("creator"),

            TagIds = GetInts("tagIds"),
            IncludeAllTags = Has("allTags"),
            CreatorIds = GetInts("creatorIds"),
            IncludeAllCreators = Has("allCreators"),
            SupportedLanguages = GetEnums<Language>("langs"),
            AgeRatings = GetEnums<AgeRating>("ages"),

            ShowFavorite = Has("fav"),
            ShowInvalid = Has("invalid"),

            ReleaseDateStart = GetDate("rs"),
            ReleaseDateEnd = GetDate("re"),

            Sort = GetEnum<VoiceWorkSort>("sort") ?? VoiceWorkSort.ReleaseDateNewToOld,
            PageNumber = int.TryParse(Get("pageNumber"), out var pageNumber) ? Math.Max(1, pageNumber) : 1,
            PageSize = int.TryParse(Get("pageSize"), out var pageSize) ? pageSize : 100
        };
    }

    public SearchVoiceWorksRequest ToSearchRequest()
    {
        return new(
            Options: new()
            {
                Criteria = new()
                {
                    Keywords = Keywords,
                    Locale = Locale,
                    SaleStatus = SaleStatus,
                    CircleStatus = CircleStatus,
                    TagStatus = TagStatus,
                    CreatorStatus = CreatorStatus,
                    TagIds = TagIds,
                    IncludeAllTags = IncludeAllTags,
                    CreatorIds = CreatorIds,
                    IncludeAllCreators = IncludeAllCreators,
                    ShowFavoriteVoiceWorks = ShowFavorite,
                    ShowInvalidVoiceWorks = ShowInvalid,
                    SupportedLanguages = SupportedLanguages,
                    AgeRatings = AgeRatings,
                    ReleaseDateStart = ReleaseDateStart,
                    ReleaseDateEnd = ReleaseDateEnd,
                    //MinDownloads = MinDownloads
                },
                SortOptions = GetSortOptions(),
                PageNumber = PageNumber,
                PageSize = PageSize
            }
        );
    }

    private SortOption<VoiceWorkSortField>[] GetSortOptions()
    {
        switch (Sort)
        {
            case VoiceWorkSort.ReleaseDateNewToOld:
                return
                [
                    new(GetReleaseDateVoiceWorkSortField(), SortDirection.Descending)
                ];
            case VoiceWorkSort.ReleaseDateOldToNew:
                return
                [
                    new(GetReleaseDateVoiceWorkSortField(), SortDirection.Ascending)
                ];
            case VoiceWorkSort.BestSelling:
                return
                [
                    new(VoiceWorkSortField.Downloads, SortDirection.Descending)
                ];
            case VoiceWorkSort.MostFavorited:
                return
                 [
                     new(VoiceWorkSortField.WishlistCount, SortDirection.Descending)
                 ];
        }

        return [];
    }

    private VoiceWorkSortField GetReleaseDateVoiceWorkSortField()
    {
        switch (SaleStatus)
        {
            case Application.VoiceWorks.Queries.Search.SaleStatus.Available:
                return VoiceWorkSortField.ReleaseDate;
            case Application.VoiceWorks.Queries.Search.SaleStatus.Upcoming:
                return VoiceWorkSortField.ExpectedReleaseDate;
            default:
                return VoiceWorkSortField.AnyReleaseDate;
        }
    }
}