using System.Globalization;
using System.Text;
using System.Text.Json;
using Backend.Contracts;
using Backend.Domain;

namespace Backend.Services;

public sealed record SeasonSubcategoryTemplateSetting(
    string Name,
    string Slug,
    int SortOrder,
    int? ViewerRangeMin,
    int? ViewerRangeMax);

public static class SeasonSubcategoryTemplateSettings
{
    private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web);
    private static readonly SeasonSubcategoryTemplateSetting[] DefaultTemplates =
    [
        new("Hidden Star", "hidden-star", 1, 1, 20),
        new("Rising Star", "rising-star", 2, 21, 60),
        new("Shining Star", "shining-star", 3, 61, null),
    ];

    public static SeasonSubcategoryTemplateSetting[] Read(Season season, IEnumerable<Category>? fallbackCategories = null)
    {
        var stored = OnlyViewerTemplates(Parse(season.SubcategoryTemplatesJson));
        if (stored.Length > 0)
        {
            return stored;
        }

        if (fallbackCategories is null)
        {
            return [];
        }

        var fallback = fallbackCategories
            .GroupBy(category => new { category.Name, category.ViewerRangeMin, category.ViewerRangeMax })
            .Where(group => group.Key.ViewerRangeMin is not null || group.Key.ViewerRangeMax is not null)
            .Select(group =>
            {
                var first = group.OrderBy(item => item.SortOrder).First();
                return Normalize(new SeasonSubcategoryTemplateSetting(
                    first.Name,
                    ExtractTemplateSlug(first.Slug, first.GroupName),
                    first.SortOrder,
                    first.ViewerRangeMin,
                    first.ViewerRangeMax));
            })
            .OrderBy(item => item.SortOrder)
            .ThenBy(item => item.Name, StringComparer.OrdinalIgnoreCase)
            .Select((item, index) => item with { SortOrder = index + 1 })
            .ToArray();

        return fallback.Length > 0 ? fallback : DefaultTemplates;
    }

    public static string Serialize(IEnumerable<SeasonSubcategoryTemplateSetting> templates) =>
        JsonSerializer.Serialize(
            templates.Select(Normalize)
                .OrderBy(item => item.SortOrder)
                .ThenBy(item => item.Name, StringComparer.OrdinalIgnoreCase),
            JsonOptions);

    public static SeasonSubcategoryTemplateSetting[] Normalize(IEnumerable<AdminSubcategoryTemplateDto>? templates) =>
        (templates ?? [])
            .Select(template => Normalize(new SeasonSubcategoryTemplateSetting(
                template.Name,
                template.Slug,
                template.SortOrder,
                template.ViewerRangeMin,
                template.ViewerRangeMax)))
            .OrderBy(item => item.SortOrder)
            .ThenBy(item => item.Name, StringComparer.OrdinalIgnoreCase)
            .Select((item, index) => item with { SortOrder = index + 1 })
            .ToArray();

    public static AdminSubcategoryTemplateDto[] ToDtos(IEnumerable<SeasonSubcategoryTemplateSetting> templates) =>
        templates
            .OrderBy(item => item.SortOrder)
            .ThenBy(item => item.Name, StringComparer.OrdinalIgnoreCase)
            .Select(item => new AdminSubcategoryTemplateDto(
                item.Name,
                item.Slug,
                item.SortOrder,
                item.ViewerRangeMin,
                item.ViewerRangeMax))
            .ToArray();

    public static bool MatchesTemplate(Category category, IEnumerable<SeasonSubcategoryTemplateSetting> templates)
    {
        var categoryTemplateSlug = ExtractTemplateSlug(category.Slug, category.GroupName);
        return templates.Any(template =>
            string.Equals(category.Name, template.Name, StringComparison.OrdinalIgnoreCase)
            || string.Equals(categoryTemplateSlug, template.Slug, StringComparison.OrdinalIgnoreCase)
            || category.Slug.EndsWith($"-{template.Slug}", StringComparison.OrdinalIgnoreCase));
    }

    private static SeasonSubcategoryTemplateSetting[] Parse(string? json)
    {
        if (string.IsNullOrWhiteSpace(json))
        {
            return [];
        }

        try
        {
            return JsonSerializer.Deserialize<SeasonSubcategoryTemplateSetting[]>(json, JsonOptions)?
                .Select(Normalize)
                .OrderBy(item => item.SortOrder)
                .ThenBy(item => item.Name, StringComparer.OrdinalIgnoreCase)
                .Select((item, index) => item with { SortOrder = index + 1 })
                .ToArray()
                ?? [];
        }
        catch (JsonException)
        {
            return [];
        }
    }

    private static SeasonSubcategoryTemplateSetting Normalize(SeasonSubcategoryTemplateSetting template)
    {
        var name = template.Name.Trim();
        var slug = Slugify(template.Slug);
        if (string.IsNullOrWhiteSpace(slug))
        {
            slug = Slugify(name);
        }

        return template with
        {
            Name = name,
            Slug = slug,
            SortOrder = Math.Clamp(template.SortOrder, 1, 99),
            ViewerRangeMin = NormalizeNullableNumber(template.ViewerRangeMin),
            ViewerRangeMax = NormalizeNullableNumber(template.ViewerRangeMax),
        };
    }

    private static int? NormalizeNullableNumber(int? value) => value is null ? null : Math.Clamp(value.Value, 0, 100000);

    private static SeasonSubcategoryTemplateSetting[] OnlyViewerTemplates(IEnumerable<SeasonSubcategoryTemplateSetting> templates)
    {
        var items = templates
            .Select(Normalize)
            .Where(item => item.ViewerRangeMin is not null || item.ViewerRangeMax is not null)
            .GroupBy(item => item.Slug, StringComparer.OrdinalIgnoreCase)
            .Select(group => group.OrderBy(item => item.SortOrder).First())
            .OrderBy(item => item.SortOrder)
            .ThenBy(item => item.Name, StringComparer.OrdinalIgnoreCase)
            .Select((item, index) => item with { SortOrder = index + 1 })
            .ToArray();

        return items.Length > 0 ? items : [];
    }

    private static string ExtractTemplateSlug(string categorySlug, string groupName)
    {
        var groupSlug = Slugify(groupName);
        var slug = categorySlug.Trim().ToLowerInvariant();
        var prefix = string.IsNullOrWhiteSpace(groupSlug) ? string.Empty : $"{groupSlug}-";
        if (!string.IsNullOrWhiteSpace(prefix) && slug.StartsWith(prefix, StringComparison.Ordinal))
        {
            return slug[prefix.Length..];
        }

        return slug;
    }

    public static string Slugify(string? value)
    {
        var normalized = (value ?? string.Empty)
            .Trim()
            .ToLowerInvariant()
            .Normalize(NormalizationForm.FormD);

        var builder = new StringBuilder(normalized.Length);
        var lastWasDash = false;

        foreach (var character in normalized)
        {
            if (CharUnicodeInfo.GetUnicodeCategory(character) == UnicodeCategory.NonSpacingMark)
            {
                continue;
            }

            if (char.IsLetterOrDigit(character))
            {
                builder.Append(character);
                lastWasDash = false;
                continue;
            }

            if (!lastWasDash && builder.Length > 0)
            {
                builder.Append('-');
                lastWasDash = true;
            }
        }

        return builder.ToString().Trim('-');
    }
}
