"""Analyze incoming request content to determine task tier and required capabilities.

The analyzer inspects the user messages, system prompt, and metadata to classify
the task into a `TaskTier` and extract required `Capability` flags.  This drives
the router's model selection — cheap models for trivial work, Opus only when needed.

Strategy hierarchy (checked in order):
  1. Explicit header  `X-Task-Tier: complex`  — user override
  2. Explicit model   `model: opus-4.6`       — passthrough
  3. Content signals  keywords, length, structure analysis
"""

from __future__ import annotations

import re
from dataclasses import dataclass, field

from proxym.providers import Capability, TaskTier

# ── Signal patterns ──────────────────────────────────────────

# Patterns that indicate DEEP reasoning / complex multi-file work
_COMPLEX_PATTERNS: list[re.Pattern] = [
    re.compile(p, re.IGNORECASE) for p in [
        r"refactor\s+(the\s+)?(entire|whole|complete|full)\s+",
        r"re-?architect",
        r"migrat(e|ion)\s+(from|to)\s+",
        r"design\s+(a\s+)?(system|architecture|schema)",
        r"multi[- ]?file\s+(change|edit|refactor)",
        r"(complex|hard|tricky|subtle)\s+(bug|issue|problem|error)",
        r"(performance|memory)\s+(optim|profil|bottleneck)",
        r"security\s+(audit|review|vuln)",
        r"(implement|build|create)\s+(a\s+)?(complete|full|entire)\s+",
        r"(10|20|50|100)\+?\s+files?",
        r"across\s+(multiple|many|all)\s+(files|modules|packages)",
    ]
]

# Patterns for standard implementation tasks
_STANDARD_PATTERNS: list[re.Pattern] = [
    re.compile(p, re.IGNORECASE) for p in [
        r"(implement|write|create|add)\s+(a\s+)?(function|class|module|endpoint|test|component)",
        r"(fix|resolve|debug)\s+(this|the|a)\s+",
        r"(update|modify|change|edit)\s+",
        r"(add|remove|rename)\s+(a\s+)?(field|column|method|property)",
        r"(write|generate)\s+(unit\s+)?tests?\s+",
        r"(explain|review)\s+(this|the)\s+(code|function|class)",
        r"code\s+review",
    ]
]

# Patterns for trivial/operational tasks
_TRIVIAL_PATTERNS: list[re.Pattern] = [
    re.compile(p, re.IGNORECASE) for p in [
        r"^(what|how|why|when|where|who|which)\s+",
        r"(show|list|print|display|get)\s+",
        r"(rename|format|lint|prettify)",
        r"(autocomplete|suggest|complete)\s+",
        r"(translate|convert)\s+(this|the)\s+(to|into)\s+",
        r"(simple|quick|small|tiny|minor)\s+(fix|change|edit|update)",
        r"^(fix|add)\s+(a\s+)?(typo|import|comma|semicolon)",
    ]
]

# Thinking/reasoning signals
_THINKING_PATTERNS: list[re.Pattern] = [
    re.compile(p, re.IGNORECASE) for p in [
        r"(think|reason|analyze|consider)\s+(about|through|carefully)",
        r"(step[- ]by[- ]step|chain[- ]of[- ]thought)",
        r"(why\s+does|what\s+causes|root\s+cause)",
        r"(trade[- ]?offs?|pros?\s+and\s+cons?)",
        r"(compare|evaluate|assess)\s+(different|multiple|various)",
        r"(debug|diagnos|troubleshoot)\s+.{30,}",  # long debug descriptions
    ]
]

# Vision signals
_VISION_PATTERNS: list[re.Pattern] = [
    re.compile(p, re.IGNORECASE) for p in [
        r"(image|screenshot|picture|photo|diagram|ui|mockup)",
        r"(look at|see|view|analyze)\s+(this|the)\s+(image|screenshot)",
    ]
]


@dataclass
class AnalysisResult:
    """Result of content analysis."""

    tier: TaskTier
    capabilities: frozenset[Capability]
    confidence: float = 0.8
    reasoning: str = ""
    estimated_output_tokens: int = 2000
    signals: list[str] = field(default_factory=list)


def analyze_request(
    messages: list[dict],
    *,
    explicit_tier: str | None = None,
    explicit_model: str | None = None,
    file_count: int = 0,
    context_tokens: int = 0,
) -> AnalysisResult:
    """Analyze a chat completion request and return routing metadata.

    Parameters
    ----------
    messages : list[dict]
        OpenAI-format messages.
    explicit_tier : str | None
        Value of ``X-Task-Tier`` header (user override).
    explicit_model : str | None
        If user requested a specific model, skip analysis.
    file_count : int
        Number of files in the attached context (from delta buffer).
    context_tokens : int
        Approximate token count of the context window.
    """
    # 1. Explicit tier override
    if explicit_tier:
        try:
            tier = TaskTier(explicit_tier.lower())
        except ValueError:
            tier = TaskTier.STANDARD
        return AnalysisResult(
            tier=tier,
            capabilities=frozenset(),
            confidence=1.0,
            reasoning=f"Explicit tier override: {explicit_tier}",
        )

    # 2. Explicit model passthrough (analyzed for caps only)
    if explicit_model:
        return AnalysisResult(
            tier=TaskTier.STANDARD,
            capabilities=frozenset(),
            confidence=1.0,
            reasoning=f"Explicit model requested: {explicit_model}",
        )

    # 3. Content-based analysis
    user_text = _extract_user_text(messages)
    system_text = _extract_system_text(messages)
    all_text = f"{system_text}\n{user_text}"
    text_len = len(user_text)

    signals: list[str] = []
    tier_scores: dict[TaskTier, float] = {t: 0.0 for t in TaskTier}
    caps: set[Capability] = set()

    # -- Pattern matching --
    for pat in _COMPLEX_PATTERNS:
        if pat.search(user_text):
            tier_scores[TaskTier.COMPLEX] += 2.0
            signals.append(f"complex_pattern: {pat.pattern[:40]}")

    for pat in _STANDARD_PATTERNS:
        if pat.search(user_text):
            tier_scores[TaskTier.STANDARD] += 1.5
            signals.append(f"standard_pattern: {pat.pattern[:40]}")

    for pat in _TRIVIAL_PATTERNS:
        if pat.search(user_text):
            tier_scores[TaskTier.TRIVIAL] += 1.5
            signals.append(f"trivial_pattern: {pat.pattern[:40]}")

    for pat in _THINKING_PATTERNS:
        if pat.search(user_text):
            tier_scores[TaskTier.DEEP_REASONING] += 2.5
            caps.add(Capability.THINKING)
            signals.append(f"thinking_pattern: {pat.pattern[:40]}")

    for pat in _VISION_PATTERNS:
        if pat.search(all_text):
            caps.add(Capability.VISION)
            signals.append("vision_detected")

    # Check for image content blocks
    for msg in messages:
        content = msg.get("content", "")
        if isinstance(content, list):
            for block in content:
                if isinstance(block, dict) and block.get("type") == "image_url":
                    caps.add(Capability.VISION)
                    signals.append("image_content_block")

    # -- Length / complexity heuristics --
    if text_len > 3000:
        tier_scores[TaskTier.STANDARD] += 1.0
        signals.append(f"long_prompt: {text_len} chars")
    if text_len > 8000:
        tier_scores[TaskTier.COMPLEX] += 1.5
        signals.append(f"very_long_prompt: {text_len} chars")

    if file_count > 5:
        tier_scores[TaskTier.COMPLEX] += 1.5
        signals.append(f"multi_file_context: {file_count}")
    elif file_count > 1:
        tier_scores[TaskTier.STANDARD] += 0.5

    if context_tokens > 100_000:
        caps.add(Capability.LONG_CONTEXT)
        tier_scores[TaskTier.COMPLEX] += 1.0
        signals.append(f"large_context: {context_tokens} tokens")

    # -- Code block detection --
    code_blocks = len(re.findall(r"```", user_text))
    if code_blocks >= 4:
        tier_scores[TaskTier.STANDARD] += 1.0
        caps.add(Capability.CODE)
        signals.append(f"code_blocks: {code_blocks // 2}")

    # Tool use signals
    if any(kw in all_text.lower() for kw in ["function call", "tool_use", "tool_call", "mcp"]):
        caps.add(Capability.TOOL_USE)
        signals.append("tool_use_detected")

    # Always add CODE capability for programming-related requests
    code_keywords = ["code", "function", "class", "module", "api", "endpoint", "test",
                     "bug", "error", "refactor", "implement", "variable", "method"]
    if any(kw in all_text.lower() for kw in code_keywords):
        caps.add(Capability.CODE)

    # -- Pick winning tier --
    if not signals:
        # Default: standard for non-trivial, operational otherwise
        tier = TaskTier.OPERATIONAL if text_len < 500 else TaskTier.STANDARD
    else:
        tier = max(tier_scores, key=lambda t: tier_scores[t])
        # If scores are close, prefer cheaper tier
        max_score = tier_scores[tier]
        if max_score < 1.0:
            tier = TaskTier.OPERATIONAL

    # Estimate output tokens
    if tier in (TaskTier.COMPLEX, TaskTier.DEEP_REASONING):
        estimated_output = 6000
    elif tier == TaskTier.STANDARD:
        estimated_output = 3000
    else:
        estimated_output = 1000

    return AnalysisResult(
        tier=tier,
        capabilities=frozenset(caps),
        confidence=min(0.95, 0.5 + tier_scores.get(tier, 0) * 0.1),
        reasoning=f"tier={tier.value}, top_signals={signals[:3]}",
        estimated_output_tokens=estimated_output,
        signals=signals,
    )


def _extract_user_text(messages: list[dict]) -> str:
    parts = []
    for msg in messages:
        if msg.get("role") != "user":
            continue
        content = msg.get("content", "")
        if isinstance(content, str):
            parts.append(content)
        elif isinstance(content, list):
            for block in content:
                if isinstance(block, dict) and block.get("type") == "text":
                    parts.append(block.get("text", ""))
    return "\n".join(parts)


def _extract_system_text(messages: list[dict]) -> str:
    for msg in messages:
        if msg.get("role") == "system":
            content = msg.get("content", "")
            return content if isinstance(content, str) else ""
    return ""
