using System.Text.RegularExpressions;
using Predictalytics.Domain.Enums;

namespace Predictalytics.Infrastructure.Helpers;

public static class MarketCategoryMapper
{
    /// <summary>
    /// Classification rules, first match wins. Two matching modes per category:
    /// - Substrings: long, unambiguous fragments matched anywhere ("politic", "bitcoin").
    /// - Words: short/ambiguous tokens matched only on word boundaries, so "eth"
    ///   cannot hit inside "whether" and "pop" cannot hit inside "popular".
    /// </summary>
    private static readonly (MarketCategory Category, string[] Substrings, string[] Words)[] Rules =
    {
        (MarketCategory.Politics,
            new[] { "politic", "election", "president", "senat", "congress", "parliament", "impeach",
                    "referendum", "governor", "minister", "chancellor", "nominee", "supreme court",
                    "white house", "ballot", "veto", "coalition", "legislation", "mayor" },
            new[] { "trump", "biden", "harris", "vance", "scotus", "gop", "dnc", "rnc", "poll", "polls", "vote", "votes" }),

        (MarketCategory.Crypto,
            new[] { "crypto", "bitcoin", "ethereum", "solana", "dogecoin", "blockchain", "stablecoin",
                    "binance", "coinbase", "airdrop", "halving", "memecoin", "altcoin", "defi", "satoshi" },
            new[] { "btc", "eth", "sol", "xrp", "doge", "nft", "bnb", "ada", "usdt", "usdc" }),

        (MarketCategory.Sports,
            new[] { "sport", "soccer", "tennis", "basketball", "baseball", "football", "hockey", "olympic",
                    "champions league", "premier league", "bundesliga", "la liga", "serie a", "world cup",
                    "super bowl", "grand slam", "wimbledon", "playoff", "esport", "cricket", "rugby",
                    "golf", "boxing", "marathon", "formula 1", "grand prix", "stanley cup", "world series",
                    "roland garros", "us open" },
            new[] { "nfl", "nba", "mlb", "nhl", "ufc", "mma", "f1", "fifa", "uefa", "atp", "wta", "pga", "ncaa", "epl" }),

        (MarketCategory.PopCulture,
            new[] { "culture", "movie", "oscars", "music", "album", "billboard", "box office", "netflix",
                    "spotify", "grammy", "emmy", "celebrit", "taylor swift", "mrbeast", "youtube", "tiktok",
                    "video game", "rotten tomatoes", "eurovision" },
            new[] { "pop", "gta", "oscar" }),

        (MarketCategory.Science,
            new[] { "science", "spacex", "nasa", "asteroid", "hurricane", "earthquake", "climate", "weather",
                    "temperature", "vaccine", "pandemic", "artificial intelligence", "openai", "chatgpt",
                    "quantum", "starship", "nobel", "space" },
            new[] { "ai", "agi", "gpt", "llm" }),

        (MarketCategory.GlobalNews,
            new[] { "global", "world", "news", "ceasefire", "ukraine", "russia", "israel", "gaza", "iran",
                    "taiwan", "nato", "sanction", "treaty", "invasion", "north korea", "hostage",
                    "military", "missile", "nuclear" },
            new[] { "war", "u.n." }),

        (MarketCategory.Economy,
            new[] { "econom", "finance", "business", "inflation", "recession", "interest rate", "rate cut",
                    "rate hike", "unemployment", "tariff", "treasury", "earnings", "market cap",
                    "stock price", "bankrupt", "acquisition", "merger" },
            new[] { "fed", "fomc", "cpi", "gdp", "nasdaq", "dow", "ipo" }),
    };

    private static readonly Dictionary<string, MarketCategory> CanonicalTags = new(StringComparer.OrdinalIgnoreCase)
    {
        { "politics", MarketCategory.Politics },
        { "crypto", MarketCategory.Crypto },
        { "sports", MarketCategory.Sports },
        { "pop culture", MarketCategory.PopCulture },
        { "popculture", MarketCategory.PopCulture },
        { "science", MarketCategory.Science },
        { "global news", MarketCategory.GlobalNews },
        { "globalnews", MarketCategory.GlobalNews },
        { "news", MarketCategory.GlobalNews },
        { "economy", MarketCategory.Economy },
        { "business", MarketCategory.Economy },
        { "finance", MarketCategory.Economy }
    };

    private static readonly HashSet<string> BlacklistTags = new(StringComparer.OrdinalIgnoreCase)
    {
        "hide from new",
        "hide_from_new",
        "tournament futures",
        "main election",
        "recurring",
        "exchange",
        "overall",
        "other",
        "none"
    };

    private static bool IsNoiseTag(string tag)
    {
        if (string.IsNullOrWhiteSpace(tag)) return true;
        var trimmed = tag.Trim();
        if (BlacklistTags.Contains(trimmed)) return true;
        if (int.TryParse(trimmed, out _)) return true;
        if (Regex.IsMatch(trimmed, @"^\d{4}\s+predictions$", RegexOptions.IgnoreCase)) return true;
        return false;
    }

    /// <summary>
    /// Classifies a market. The question text is a first-class signal: the Gamma
    /// /markets endpoint delivers neither a category field nor event tags, so for
    /// on-demand fetched markets the question is often the ONLY signal available.
    /// </summary>
    public static (MarketCategory Category, string Subcategory) Map(string rawCategory, string tags, string question = "")
    {
        var tagList = (tags ?? string.Empty).Split(',', StringSplitOptions.RemoveEmptyEntries)
            .Select(t => t.Trim())
            .Where(t => !string.IsNullOrEmpty(t))
            .ToList();

        if (!string.IsNullOrWhiteSpace(rawCategory))
        {
            tagList.Insert(0, rawCategory.Trim());
        }

        // F1: Check canonical tags first over the entire tag set
        foreach (var tag in tagList)
        {
            if (CanonicalTags.TryGetValue(tag, out var canonicalCategory))
            {
                return (canonicalCategory, GetSubcategory(rawCategory, tags ?? string.Empty, canonicalCategory.ToString()));
            }
        }

        // Fallback: heuristic search on rawCategory + tags + question
        var searchString = $"{rawCategory} {tags} {question}".ToLowerInvariant();
        var tokens = new HashSet<string>(Regex.Split(searchString, "[^a-z0-9.]+"));

        foreach (var rule in Rules)
        {
            if (rule.Words.Any(tokens.Contains) || rule.Substrings.Any(searchString.Contains))
                return (rule.Category, GetSubcategory(rawCategory, tags ?? string.Empty, rule.Category.ToString()));
        }

        return (MarketCategory.Other, GetSubcategory(rawCategory, tags ?? string.Empty, "Other"));
    }

    private static string GetSubcategory(string rawCategory, string tags, string categoryName)
    {
        var candidates = tags.Split(',', StringSplitOptions.RemoveEmptyEntries)
            .Select(t => t.Trim())
            .ToList();

        if (!string.IsNullOrWhiteSpace(rawCategory))
        {
            candidates.Insert(0, rawCategory.Trim());
        }

        foreach (var c in candidates)
        {
            if (IsNoiseTag(c)) continue;
            
            // F2: Skip tag if it is the category itself or matches any canonical category tag
            if (c.Equals(categoryName, StringComparison.OrdinalIgnoreCase)) continue;
            if (CanonicalTags.ContainsKey(c)) continue;

            return c;
        }

        return string.Empty;
    }
}
