# src/fmatch/preprocessing/rules/text_normalizers.py
from __future__ import annotations
import unicodedata as ud
import re
import html
import phonenumbers
from urllib.parse import urlparse, parse_qs, urlencode, urlunparse
import os

# Unicode ranges for script detection
_RE_CJK = re.compile(r"[\u3400-\u9FFF\uF900-\uFAFF\u3040-\u30FF\uAC00-\uD7AF]")
_RE_CYR = re.compile(r"[\u0400-\u04FF]")
# Future expansion: Arabic and Hebrew
_RE_ARABIC = re.compile(r"[\u0600-\u06FF]")
_RE_HEBREW = re.compile(r"[\u0590-\u05FF]")

# Cache globalization feature flag at module import (performance optimization)
GLOBALIZATION_V1 = os.getenv("GLOBALIZATION_V1_ENABLED", "true").lower() == "true"

# Existing HTML/punctuation patterns
_RE_SCRIPT_TAGS = re.compile(r"<script[^>]*>.*?</script>", re.DOTALL | re.IGNORECASE)
_RE_STYLE_TAGS = re.compile(r"<style[^>]*>.*?</style>", re.DOTALL | re.IGNORECASE)
_RE_HTML_TAGS = re.compile(r"<[^>]+>")
_RE_TRAILING_PUNCT = re.compile(r"\s*[,\.\-]+\s*$")
_RE_LEADING_PUNCT = re.compile(r"^\s*[,\.\-]+\s*")
_RE_NON_DIGITS = re.compile(r"\D")


def _strip_accents_latin(s: str) -> str:
    """Remove diacritics from Latin text only."""
    nkfd = ud.normalize("NFKD", s)
    return "".join(ch for ch in nkfd if ud.category(ch) != "Mn")


def normalize_text(s: str) -> str:
    """
    Script-aware text normalization that preserves non-Latin scripts.

    - CJK/Cyrillic/Arabic/Hebrew: preserved with casefold
    - Latin: accents stripped, casefolded
    - All: NFKC normalized, trimmed

    This replaces unidecode to prevent data loss for Eastern scripts.
    Can be disabled via GLOBALIZATION_V1_ENABLED=false for backward compatibility.

    Args:
        s: Input text

    Returns:
        Normalized text preserving script integrity
    """
    if not s:
        return ""

    # Check cached feature flag - fallback if disabled
    if not GLOBALIZATION_V1:
        # Still normalize shape to avoid surprises
        return ud.normalize("NFKC", s).strip().lower()

    # Normalize to NFKC form and strip whitespace
    s = ud.normalize("NFKC", s).strip()

    # If any non-Latin script present, preserve it (only casefold)
    if (
        _RE_CJK.search(s)
        or _RE_CYR.search(s)
        or _RE_ARABIC.search(s)
        or _RE_HEBREW.search(s)
    ):
        return s.casefold()

    # Latin text: strip accents and casefold
    return _strip_accents_latin(s).casefold()


def is_cjk(text: str) -> bool:
    """Check if text contains CJK characters."""
    return bool(_RE_CJK.search(text)) if text else False


def is_cyrillic(text: str) -> bool:
    """Check if text contains Cyrillic characters."""
    return bool(_RE_CYR.search(text)) if text else False


# Pre-compile company suffixes for better performance
_COMPILED_COMPANY_SUFFIXES = {}


def _compile_company_suffixes():
    """Pre-compile all company suffix patterns once at module load"""
    for lang, patterns in TextNormalizers.COMPANY_SUFFIXES.items():
        _COMPILED_COMPANY_SUFFIXES[lang] = [
            (re.compile(pattern, re.IGNORECASE), replacement)
            for pattern, replacement in patterns
        ]


class TextNormalizers:
    """Enterprise-grade text normalization functions with globalization support"""

    # Company suffix patterns (extensible with global suffixes)
    COMPANY_SUFFIXES = {
        "en": [
            (r"\b(L\.?L\.?C\.?|llc)\b", "LLC"),
            (r"\b(Inc\.?|Incorporated|inc\.?|incorporated)\b", "Inc"),
            (r"\b(Corp\.?|Corporation|corp\.?|corporation)\b", "Corp"),
            (r"\b(Ltd\.?|Limited|ltd\.?|limited)\b", "Ltd"),
            (r"\b(Co\.?|Company|co\.?|company)\b", "Co"),
            (r"\b(P\.?L\.?C\.?|plc)\b", "PLC"),
            (r"\b(GmbH|gmbh|GMBH)\b", "GmbH"),
            (r"\b(S\.?A\.?|sa|SA)\b", "SA"),
            (r"\b(A\.?G\.?|ag|AG)\b", "AG"),
        ],
        "fr": [
            (r"\b(S\.?A\.?R\.?L\.?|sarl|SARL)\b", "SARL"),
            (r"\b(S\.?A\.?S\.?|sas|SAS)\b", "SAS"),
        ],
        "global": [
            # Eastern European
            (r"\b(ООО|оoo)\b", "ООО"),  # Russian LLC
            (r"\b(АО|ао)\b", "АО"),  # Russian JSC
            (r"\b(ПАО|пао)\b", "ПАО"),  # Russian PJSC
            # Asian
            (r"\b(株式会社|かぶしきがいしゃ)\b", "株式会社"),  # Japanese Corp
            (r"\b(有限会社|ゆうげんがいしゃ)\b", "有限会社"),  # Japanese Ltd
            (r"\b(주식회사|㈜)\b", "주식회사"),  # Korean Corp
        ],
    }

    @staticmethod
    def normalize_unicode(text: str, form: str = "NFKC") -> str:
        """
        Unicode normalization - updated to use NFKC by default.
        Use normalize_text() for script-aware normalization instead.
        """
        if not text:
            return text
        return ud.normalize(form, text)

    @staticmethod
    def smart_to_ascii(text: str) -> str:
        """
        DEPRECATED: Use normalize_text() instead.
        This method loses non-Latin data.
        """
        # For backwards compatibility, call normalize_text
        return normalize_text(text)

    @staticmethod
    def remove_html(text: str) -> str:
        """Remove HTML tags and decode entities"""
        if not text:
            return text

        # Decode HTML entities
        text = html.unescape(text)

        # Remove tags using pre-compiled patterns
        text = _RE_SCRIPT_TAGS.sub("", text)
        text = _RE_STYLE_TAGS.sub("", text)
        text = _RE_HTML_TAGS.sub(" ", text)

        # Collapse whitespace
        text = " ".join(text.split())

        return text.strip()

    @staticmethod
    def normalize_company(text: str, language: str = "en") -> str:
        """Normalize company names with language support"""
        if not text:
            return text

        original = text
        text = text.strip()

        # Apply language-specific suffixes using pre-compiled patterns
        compiled_patterns = _COMPILED_COMPANY_SUFFIXES.get(language, [])
        # Also apply global patterns
        global_patterns = _COMPILED_COMPANY_SUFFIXES.get("global", [])

        for pattern, replacement in compiled_patterns + global_patterns:
            text = pattern.sub(replacement, text)

        # Remove common punctuation patterns using pre-compiled patterns
        text = _RE_TRAILING_PUNCT.sub("", text)
        text = _RE_LEADING_PUNCT.sub("", text)

        # Normalize whitespace
        text = " ".join(text.split())

        return text if text else original

    @staticmethod
    def normalize_phone(text: str, region: str = "US") -> str:
        """
        DEPRECATED: Use phone_utils.parse_phone() for global phone support.
        Kept for backwards compatibility.
        """
        if not text:
            return text

        try:
            # Parse phone number
            parsed = phonenumbers.parse(text, region)

            # Format to E.164
            return phonenumbers.format_number(
                parsed, phonenumbers.PhoneNumberFormat.E164
            )
        except:
            # Fallback: just keep digits using pre-compiled pattern
            digits = _RE_NON_DIGITS.sub("", text)
            return digits if digits else text

    @staticmethod
    def normalize_url(text: str) -> str:
        """
        DEPRECATED: Use domain_utils.registrable_domain() for global domain support.
        Normalize URLs by removing tracking parameters and fragments.
        """
        if not text:
            return text

        try:
            # Parse URL
            parsed = urlparse(text.lower())

            # Remove common tracking parameters
            if parsed.query:
                params = parse_qs(parsed.query)
                tracking_params = {
                    "utm_source",
                    "utm_medium",
                    "utm_campaign",
                    "utm_term",
                    "utm_content",
                    "fbclid",
                    "gclid",
                    "ref",
                    "source",
                }
                cleaned_params = {
                    k: v for k, v in params.items() if k not in tracking_params
                }
                query = urlencode(cleaned_params, doseq=True)
            else:
                query = ""

            # Reconstruct without fragment
            normalized = urlunparse(
                (
                    parsed.scheme,
                    parsed.netloc,
                    parsed.path.rstrip("/"),  # Remove trailing slash
                    "",  # params
                    query,
                    "",  # fragment
                )
            )

            return normalized
        except:
            return text

    @staticmethod
    def extract_domain(text: str) -> str:
        """
        DEPRECATED: Use domain_utils.registrable_domain() for global domain support.
        Extract domain from URL or email.
        """
        if not text:
            return text

        # Email pattern
        if "@" in text:
            parts = text.lower().split("@")
            if len(parts) == 2:
                return parts[1].strip()

        # URL pattern
        try:
            parsed = urlparse(text.lower())
            if parsed.netloc:
                # Remove www prefix
                domain = parsed.netloc
                if domain.startswith("www."):
                    domain = domain[4:]
                return domain
        except:
            pass

        return text


# Pre-compile all company suffixes at module load time
_compile_company_suffixes()
