"""Text processing utilities for normalization."""

from __future__ import annotations

import hashlib
import logging
import re
import unicodedata
from typing import NamedTuple

logger = logging.getLogger(__name__)


class NormalizationResult(NamedTuple):
    """Result of text normalization."""

    normalized: str
    language: str
    word_count: int
    char_count: int


class TextProcessor:
    """Service for text normalization and processing."""

    # Common emoji ranges
    EMOJI_PATTERN = re.compile(
        "["
        "\U0001F600-\U0001F64F"  # emoticons
        "\U0001F300-\U0001F5FF"  # symbols & pictographs
        "\U0001F680-\U0001F6FF"  # transport & map symbols
        "\U0001F1E0-\U0001F1FF"  # flags
        "\U00002702-\U000027B0"  # dingbats
        "\U000024C2-\U0001F251"  # enclosed characters
        "]+",
        flags=re.UNICODE,
    )

    # Control characters (except newlines and tabs we want to normalize)
    CONTROL_CHAR_PATTERN = re.compile(r"[\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\x9f]")

    # Multiple whitespace
    MULTI_WHITESPACE_PATTERN = re.compile(r"\s+")

    # URL pattern
    URL_PATTERN = re.compile(
        r"https?://[^\s<>\"{}|\\^`\[\]]+|www\.[^\s<>\"{}|\\^`\[\]]+"
    )

    def __init__(self):
        self._langdetect_available = False
        try:
            from langdetect import detect, DetectorFactory

            # Make detection deterministic
            DetectorFactory.seed = 0
            self._langdetect_available = True
        except ImportError:
            logger.warning("langdetect not available, defaulting to 'en' for all text")

    def normalize(self, text: str) -> NormalizationResult:
        """
        Normalize text for classification.

        Steps:
        1. Remove control characters
        2. Normalize Unicode (NFC)
        3. Lowercase
        4. Normalize whitespace (collapse multiple spaces, trim)
        5. Standardize emoji (keep but normalize)
        6. Detect language

        Args:
            text: Original review text

        Returns:
            NormalizationResult with normalized text and metadata
        """
        if not text:
            return NormalizationResult(
                normalized="",
                language="en",
                word_count=0,
                char_count=0,
            )

        # Step 1: Remove control characters
        normalized = self.CONTROL_CHAR_PATTERN.sub("", text)

        # Step 2: Unicode normalization (NFC - composed form)
        normalized = unicodedata.normalize("NFC", normalized)

        # Step 3: Lowercase
        normalized = normalized.lower()

        # Step 4: Normalize whitespace
        normalized = self.MULTI_WHITESPACE_PATTERN.sub(" ", normalized)
        normalized = normalized.strip()

        # Detect language on original text (before lowercasing can help)
        language = self.detect_language(text)

        # Calculate metrics
        word_count = len(normalized.split()) if normalized else 0
        char_count = len(normalized)

        return NormalizationResult(
            normalized=normalized,
            language=language,
            word_count=word_count,
            char_count=char_count,
        )

    def detect_language(self, text: str) -> str:
        """
        Detect the language of the text.

        Args:
            text: Text to analyze

        Returns:
            ISO 639-1 language code (e.g., 'en', 'es', 'fr')
        """
        if not text or not self._langdetect_available:
            return "en"

        try:
            from langdetect import detect

            # Need reasonable length for detection
            sample = text[:1000] if len(text) > 1000 else text
            return detect(sample)
        except Exception as e:
            logger.debug(f"Language detection failed: {e}")
            return "en"

    def generate_content_hash(self, text_normalized: str) -> str:
        """
        Generate a SHA256 hash of normalized text for deduplication.

        Args:
            text_normalized: Normalized text

        Returns:
            64-character hex string
        """
        return hashlib.sha256(text_normalized.encode("utf-8")).hexdigest()

    def has_control_characters(self, text: str) -> bool:
        """Check if text contains control characters."""
        return bool(self.CONTROL_CHAR_PATTERN.search(text))

    def extract_urls(self, text: str) -> list[str]:
        """Extract URLs from text."""
        return self.URL_PATTERN.findall(text)

    def count_emoji(self, text: str) -> int:
        """Count emoji in text."""
        return len(self.EMOJI_PATTERN.findall(text))

    def is_empty_or_trivial(self, text: str | None, min_chars: int = 3) -> bool:
        """
        Check if text is empty or trivially short.

        Args:
            text: Text to check
            min_chars: Minimum meaningful character count

        Returns:
            True if text should be skipped
        """
        if not text:
            return True
        stripped = text.strip()
        if not stripped:
            return True
        if len(stripped) < min_chars:
            return True
        return False

    def clean_for_llm(self, text: str) -> str:
        """
        Clean text for LLM input.

        Similar to normalize but preserves case and some formatting
        for better LLM understanding.

        Args:
            text: Original text

        Returns:
            Cleaned text suitable for LLM input
        """
        if not text:
            return ""

        # Remove control characters
        cleaned = self.CONTROL_CHAR_PATTERN.sub("", text)

        # Unicode normalization
        cleaned = unicodedata.normalize("NFC", cleaned)

        # Normalize whitespace but preserve single newlines for paragraphs
        cleaned = re.sub(r"[^\S\n]+", " ", cleaned)  # Collapse horizontal space
        cleaned = re.sub(r"\n{3,}", "\n\n", cleaned)  # Max 2 consecutive newlines
        cleaned = cleaned.strip()

        return cleaned


def is_valid_iso639(code: str) -> bool:
    """
    Check if a language code is a valid ISO 639-1 code.

    Args:
        code: Language code to validate

    Returns:
        True if valid ISO 639-1 code
    """
    # Common ISO 639-1 codes (not exhaustive but covers most)
    valid_codes = {
        "aa", "ab", "ae", "af", "ak", "am", "an", "ar", "as", "av",
        "ay", "az", "ba", "be", "bg", "bh", "bi", "bm", "bn", "bo",
        "br", "bs", "ca", "ce", "ch", "co", "cr", "cs", "cu", "cv",
        "cy", "da", "de", "dv", "dz", "ee", "el", "en", "eo", "es",
        "et", "eu", "fa", "ff", "fi", "fj", "fo", "fr", "fy", "ga",
        "gd", "gl", "gn", "gu", "gv", "ha", "he", "hi", "ho", "hr",
        "ht", "hu", "hy", "hz", "ia", "id", "ie", "ig", "ii", "ik",
        "io", "is", "it", "iu", "ja", "jv", "ka", "kg", "ki", "kj",
        "kk", "kl", "km", "kn", "ko", "kr", "ks", "ku", "kv", "kw",
        "ky", "la", "lb", "lg", "li", "ln", "lo", "lt", "lu", "lv",
        "mg", "mh", "mi", "mk", "ml", "mn", "mr", "ms", "mt", "my",
        "na", "nb", "nd", "ne", "ng", "nl", "nn", "no", "nr", "nv",
        "ny", "oc", "oj", "om", "or", "os", "pa", "pi", "pl", "ps",
        "pt", "qu", "rm", "rn", "ro", "ru", "rw", "sa", "sc", "sd",
        "se", "sg", "si", "sk", "sl", "sm", "sn", "so", "sq", "sr",
        "ss", "st", "su", "sv", "sw", "ta", "te", "tg", "th", "ti",
        "tk", "tl", "tn", "to", "tr", "ts", "tt", "tw", "ty", "ug",
        "uk", "ur", "uz", "ve", "vi", "vo", "wa", "wo", "xh", "yi",
        "yo", "za", "zh", "zu",
    }
    return code.lower() in valid_codes


def is_valid_sha256(hash_str: str) -> bool:
    """
    Check if a string is a valid SHA256 hex hash.

    Args:
        hash_str: Hash string to validate

    Returns:
        True if valid 64-character hex string
    """
    if not hash_str or len(hash_str) != 64:
        return False
    try:
        int(hash_str, 16)
        return True
    except ValueError:
        return False
