"""
Issue #124 (ENH-007): Turn complexity classification for tiered model routing.

Classifies conversation turn complexity as SIMPLE or COMPLEX using heuristic
signals to guide model selection. Simple turns route to smaller/cheaper models;
complex turns route to larger/more capable models.
"""

import re
import logging
from enum import Enum
from dataclasses import dataclass, field
from typing import Optional

logger = logging.getLogger(__name__)


class ComplexityLevel(Enum):
    """Classification of turn complexity for model routing."""
    SIMPLE = "simple"
    COMPLEX = "complex"


@dataclass
class ClassificationResult:
    """Result of complexity classification."""
    level: ComplexityLevel
    confidence: float  # 0.0–1.0
    reason: str  # Human-readable explanation
    signals: dict  # Breakdown of signals detected


# ---------------------------------------------------------------------------
# Heuristic signal sets
# ---------------------------------------------------------------------------

# Keywords that signal complex reasoning / decisions
COMPLEX_KEYWORDS: frozenset[str] = frozenset({
    # Decision signals
    "decided", "decision", "chose", "chosen", "selected", "opted",
    "weighed", "weighing", "trade-off", "tradeoff", "trade off",
    "pros and cons", "advantages", "disadvantages",
    # Reasoning signals
    "because", "therefore", "consequently", "reasoning", "rationale",
    "analysis", "evaluated", "considering", "considerations",
    "implications", "impact", "strategy", "strategic",
    # Complex entity signals
    "budget", "timeline", "deadline", "milestone", "architecture",
    "framework", "implementation", "deployment", "migrate", "migration",
    # Comparison / evaluation signals
    "compared", "comparison", "versus", "alternative",
    "optimal", "prioritize", "priority",
})

# Regex patterns that signal simple / routine content
SIMPLE_PATTERNS: list[str] = [
    r"^(hi|hello|hey|thanks|thank you|bye|goodbye|ok|okay|yes|no|sure)\b",
    r"^(what is|who is|where is|when is|how is)\b",
    r"^(my name is|i am|i'm)\b",
    r"^(i like|i prefer|i want|i need)\s+\w+$",
]

# Subordinate-clause markers that suggest multi-hop reasoning
CLAUSE_MARKERS: frozenset[str] = frozenset({
    "although", "however", "nevertheless", "whereas", "while",
    "despite", "moreover", "furthermore", "in contrast",
})


class TurnComplexityClassifier:
    """
    Classifies conversation / prompt text complexity for tiered model routing.

    Uses lightweight heuristic signals:
      1. Decision / reasoning keyword density
      2. Message length (word count)
      3. Sentence count
      4. Simple-pattern detection (greetings, single-entity statements)
      5. Numeric content density (budgets, timelines, metrics)
      6. Subordinate-clause markers

    The aggregated score determines SIMPLE vs COMPLEX.  All thresholds are
    configurable so callers can tune the classifier per domain.
    """

    def __init__(
        self,
        length_threshold: int = 200,
        keyword_threshold: int = 2,
        sentence_threshold: int = 4,
    ):
        """
        Args:
            length_threshold: Word count above which length adds to complexity score.
            keyword_threshold: Minimum complex keywords for a keyword-based boost.
            sentence_threshold: Sentence count above which adds to complexity score.
        """
        self.length_threshold = length_threshold
        self.keyword_threshold = keyword_threshold
        self.sentence_threshold = sentence_threshold
        self._simple_patterns = [re.compile(p, re.IGNORECASE) for p in SIMPLE_PATTERNS]

    # ------------------------------------------------------------------
    # Public API
    # ------------------------------------------------------------------

    def classify(self, text: str) -> ClassificationResult:
        """
        Classify *text* complexity.

        Args:
            text: Conversation text or extraction prompt to classify.

        Returns:
            ClassificationResult with level, confidence, reason, and signals.
        """
        signals: dict = {}
        score = 0.0  # positive → complex, negative → simple

        text_lower = text.lower()
        words = text_lower.split()
        word_count = len(words)

        # ── Signal 1: Complex keyword density ──────────────────────────
        keywords_found = [kw for kw in COMPLEX_KEYWORDS if kw in text_lower]
        signals["complex_keywords"] = keywords_found
        keyword_count = len(keywords_found)

        if keyword_count >= self.keyword_threshold:
            score += min(keyword_count * 0.15, 0.5)

        # ── Signal 2: Message length ───────────────────────────────────
        signals["word_count"] = word_count
        if word_count > self.length_threshold:
            score += 0.2
        elif word_count < 20:
            score -= 0.3

        # ── Signal 3: Sentence count ──────────────────────────────────
        sentences = [s.strip() for s in re.split(r"[.!?]+", text) if s.strip()]
        sentence_count = len(sentences)
        signals["sentence_count"] = sentence_count

        if sentence_count >= self.sentence_threshold:
            score += 0.15

        # ── Signal 4: Simple patterns (greetings, basic Qs) ──────────
        for pattern in self._simple_patterns:
            if pattern.search(text):
                score -= 0.3
                signals["simple_pattern"] = True
                break

        # ── Signal 5: Numeric content (budgets, timelines) ───────────
        numbers = re.findall(r"\b\d[\d,.]*\b", text)
        if len(numbers) >= 3:
            score += 0.1
            signals["numeric_count"] = len(numbers)

        # ── Signal 6: Subordinate-clause markers ─────────────────────
        clause_count = sum(1 for m in CLAUSE_MARKERS if m in text_lower)
        if clause_count >= 1:
            score += 0.1
            signals["clause_markers"] = clause_count

        # ── Determine classification ─────────────────────────────────
        if score >= 0.2:
            level = ComplexityLevel.COMPLEX
            confidence = min(0.5 + score, 1.0)
        else:
            level = ComplexityLevel.SIMPLE
            confidence = min(0.5 + abs(score), 1.0)

        reason = self._build_reason(level, signals, score)

        logger.debug(
            "Complexity: %s (score=%.2f, conf=%.2f)", level.value, score, confidence
        )

        return ClassificationResult(
            level=level,
            confidence=confidence,
            reason=reason,
            signals=signals,
        )

    # ------------------------------------------------------------------
    # Internals
    # ------------------------------------------------------------------

    def _build_reason(
        self, level: ComplexityLevel, signals: dict, score: float
    ) -> str:
        parts: list[str] = []
        if signals.get("complex_keywords"):
            top = ", ".join(signals["complex_keywords"][:3])
            parts.append(f"keywords: {top}")
        if signals.get("word_count", 0) > self.length_threshold:
            parts.append(f"long ({signals['word_count']} words)")
        if signals.get("simple_pattern"):
            parts.append("simple pattern match")
        if signals.get("word_count", 0) < 20:
            parts.append("short message")
        if signals.get("clause_markers", 0) >= 1:
            parts.append(f"clause markers: {signals['clause_markers']}")
        detail = "; ".join(parts) if parts else "balanced signals"
        return f"{level.value} (score={score:.2f}): {detail}"
