"""
Prompt analyzer for attribution.

Analyzes prompt text to find lines/sections that likely influenced
agent behavior using keyword matching and confidence scoring.
"""

from __future__ import annotations

import re
from typing import Any, Dict, List

from llmhq_releaseops.attribution.models import Influence
from llmhq_releaseops.attribution.utils.text_search import (
    extract_context_lines,
    extract_keywords,
    find_keyword_in_lines,
    truncate_snippet,
)

# Patterns that negate a directive (case-insensitive)
_NEGATION_PATTERNS = [
    re.compile(r"\bnever\b", re.IGNORECASE),
    re.compile(r"\bdo\s+not\b", re.IGNORECASE),
    re.compile(r"\bdon'?t\b", re.IGNORECASE),
    re.compile(r"\bmust\s+not\b", re.IGNORECASE),
    re.compile(r"\bshould\s+not\b", re.IGNORECASE),
    re.compile(r"\bshouldn'?t\b", re.IGNORECASE),
    re.compile(r"\bprohibited\b", re.IGNORECASE),
    re.compile(r"\bforbidden\b", re.IGNORECASE),
    re.compile(r"\bnot\s+allowed\b", re.IGNORECASE),
    re.compile(r"\bunder\s+no\s+circumstances\b", re.IGNORECASE),
]


def _line_has_negation(line: str) -> bool:
    """Check if a line contains negation words/phrases."""
    return any(p.search(line) for p in _NEGATION_PATTERNS)


class PromptAnalyzer:
    """Analyzes prompt text to find lines that influenced agent behavior."""

    def analyze(
        self,
        action: str,
        prompt_content: str,
        prompt_ref: str = "",
        trace_context: Dict[str, Any] = None,
    ) -> List[Influence]:
        """
        Find prompt lines that likely influenced the given action.

        Args:
            action: What agent did (e.g., "issued_refund(amount=150)").
            prompt_content: Full prompt text.
            prompt_ref: Artifact reference for the prompt.
            trace_context: Additional context from trace.

        Returns:
            List of Influence objects, sorted by confidence.
        """
        keywords = self._extract_keywords(action)
        if not keywords:
            return []

        matches = find_keyword_in_lines(prompt_content, keywords)
        if not matches:
            return []

        influences = []
        for line_num, line_text, matched_kws in matches:
            is_negated = _line_has_negation(line_text)
            confidence, confidence_reason = self._compute_confidence(
                matched_kws, line_text, action,
            )
            context = extract_context_lines(prompt_content, line_num)
            snippet = truncate_snippet(line_text.strip())

            influences.append(Influence(
                artifact_type="prompt",
                artifact_ref=prompt_ref,
                location=f"line {line_num}",
                content_snippet=snippet,
                confidence=confidence,
                reasoning=self._build_reasoning(matched_kws, line_text, is_negated),
                keywords_matched=matched_kws,
                is_negated=is_negated,
                confidence_reason=confidence_reason,
            ))

        return sorted(influences, key=lambda i: i.confidence, reverse=True)

    def _extract_keywords(self, action: str) -> List[str]:
        """Extract meaningful keywords from action string."""
        keywords = extract_keywords(action)

        # Also extract numbers (amounts, thresholds)
        numbers = re.findall(r"\d+(?:\.\d+)?", action)
        keywords.extend(numbers)

        return keywords

    def _compute_confidence(
        self, matched_keywords: List[str], line_text: str, action: str
    ) -> tuple:
        """Apply calibrated confidence scoring algorithm.

        Returns:
            (confidence, confidence_reason) tuple.

        Calibration rules:
            HIGH (>=0.8): multi-word keyword match AND verb/action description match
            MEDIUM (0.5-0.79): keyword match with context (numbers or multiple keywords)
            LOW (<0.5): single keyword match only
        """
        reasons = []

        # Start: single keyword match = LOW
        confidence = 0.4
        reasons.append(f"keyword match ({', '.join(matched_keywords[:3])})")

        multi_keyword = len(matched_keywords) >= 2
        if multi_keyword:
            confidence += 0.15
            reasons.append("multi-keyword match")

        # Check for verb/action description match
        action_verbs = extract_keywords(action.split("(")[0] if "(" in action else action)
        line_lower = line_text.lower()
        verb_match = any(v in line_lower for v in action_verbs)
        if verb_match:
            confidence += 0.15
            reasons.append("action verb match")

        # Check for number match
        action_numbers = set(re.findall(r"\d+", action))
        line_numbers = set(re.findall(r"\d+", line_text))
        number_match = bool(action_numbers & line_numbers)
        if number_match:
            confidence += 0.2
            reasons.append(f"numeric match ({action_numbers & line_numbers})")

        # HIGH requires both multi-keyword AND verb/number match
        if confidence >= 0.8 and not (multi_keyword or number_match):
            confidence = 0.75  # Demote: single keyword + verb alone isn't HIGH

        confidence = min(confidence, 0.95)
        reason = "; ".join(reasons)
        return confidence, reason

    def _build_reasoning(
        self, matched_keywords: List[str], line_text: str, is_negated: bool
    ) -> str:
        """Build human-readable reasoning for the influence."""
        kw_str = ", ".join(f"'{k}'" for k in matched_keywords[:5])
        base = f"Keywords {kw_str} found in prompt line"
        if is_negated:
            base += " (CONTRADICTS: line prohibits this action)"
        return base
