"""Verify that fixes address root causes rather than suppressing symptoms.

Combines fast heuristic checks with optional LLM verification to
ensure fixes actually solve problems rather than hiding them.
"""

from __future__ import annotations

import logging
import re
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Any, List, Optional

logger = logging.getLogger(__name__)

if TYPE_CHECKING:
    from autodebug.validation.symptom_detector import SymptomDetector


@dataclass
class HeuristicResult:
    """Result from heuristic root cause checks."""

    issues: List[str] = field(default_factory=list)
    suggestions: List[str] = field(default_factory=list)
    addresses_root_cause: bool = True
    confidence: float = 0.5
    definitive: bool = False  # If True, no need for LLM verification

    def to_verification(
        self,
        confidence_modifier: float = 1.0,
    ) -> RootCauseVerification:
        """Convert to RootCauseVerification."""
        return RootCauseVerification(
            addresses_root_cause=self.addresses_root_cause,
            confidence=self.confidence * confidence_modifier,
            reasoning="; ".join(self.issues) if self.issues else "Heuristic checks passed",
            issues=self.issues,
            suggestions=self.suggestions,
        )


@dataclass
class RootCauseVerification:
    """Result of root cause verification."""

    addresses_root_cause: bool
    confidence: float  # 0.0 to 1.0
    reasoning: str
    issues: List[str] = field(default_factory=list)
    suggestions: List[str] = field(default_factory=list)

    def __str__(self) -> str:
        status = "ADDRESSES" if self.addresses_root_cause else "DOES NOT ADDRESS"
        return f"{status} root cause (confidence: {self.confidence:.0%}): {self.reasoning}"


class RootCauseHeuristics:
    """Fast heuristic checks for common root cause patterns."""

    def check(
        self,
        root_cause: str,
        fix_diff: str,
        exception_type: str,
        exception_message: str = "",
    ) -> HeuristicResult:
        """Run heuristic checks for root cause addressing.

        Args:
            root_cause: Stated root cause of the issue
            fix_diff: The unified diff of the fix
            exception_type: Type of exception being fixed
            exception_message: Exception message

        Returns:
            HeuristicResult with issues and confidence
        """
        issues: List[str] = []
        suggestions: List[str] = []

        # Extract added code
        added_code = self._extract_added_code(fix_diff)
        touched_files = self._extract_touched_files(fix_diff)
        referenced_files = self._extract_referenced_files(root_cause + " " + exception_message)

        # Check 1: Bare try/except is usually symptom suppression
        if self._is_bare_except(added_code):
            issues.append("Fix uses bare try/except - likely suppresses symptoms")
            suggestions.append("Catch specific exceptions and handle them appropriately")

        # Check 2: Catch and pass is symptom suppression
        if self._is_catch_and_pass(added_code):
            issues.append("Fix catches exception and does nothing - symptom suppression")
            suggestions.append("Handle the exception or allow it to propagate")

        # Check 3: Exception type specific checks
        if exception_type == "KeyError":
            if not self._addresses_key_issue(added_code, root_cause):
                issues.append("KeyError fix doesn't properly address missing key")
                suggestions.append("Use .get() with proper None handling or validate key existence")

        elif exception_type == "TypeError" and "NoneType" in exception_message:
            if not self._addresses_none_source(added_code):
                issues.append("Fix handles None but doesn't prevent it at source")
                suggestions.append("Validate data at source to prevent None propagation")

        elif exception_type == "AttributeError":
            if self._is_hasattr_bandaid(added_code):
                issues.append("Using hasattr() as a bandaid may hide type issues")
                suggestions.append("Fix the source of the incorrect type or validate earlier")

        elif exception_type == "IndexError":
            if not self._addresses_index_issue(added_code):
                issues.append("IndexError fix doesn't validate list bounds properly")
                suggestions.append("Check list length before access or handle empty lists explicitly")

        # Check 4: Overly broad exception handling
        if self._has_broad_exception_handler(added_code):
            issues.append("Catching overly broad exception type (Exception/BaseException)")
            suggestions.append("Catch specific exceptions that you expect and can handle")

        # Check 5: Fix touches relevant files
        if referenced_files and not (touched_files & referenced_files):
            issues.append("Fix does not touch file mentioned in root cause/exception")
            suggestions.append("Apply fix in the file where the root cause originates")

        # Determine result
        addresses_root_cause = len(issues) == 0
        definitive = any(
            "symptom suppression" in issue.lower() or "does nothing" in issue.lower()
            for issue in issues
        )

        # Calculate confidence
        if definitive:
            confidence = 0.9
        elif len(issues) > 0:
            confidence = 0.7
        else:
            # All heuristic checks passed cleanly with no issues found.
            # Use 0.85 so that the fallback path (0.85 * 0.8 = 0.68) still
            # reflects meaningful confidence rather than falling far below
            # the min_confidence threshold used by SelfHealConfig.
            confidence = 0.85

        return HeuristicResult(
            issues=issues,
            suggestions=suggestions,
            addresses_root_cause=addresses_root_cause,
            confidence=confidence,
            definitive=definitive,
        )

    def _extract_added_code(self, diff: str) -> str:
        """Extract added lines from unified diff."""
        lines = []
        for line in diff.split("\n"):
            if line.startswith("+") and not line.startswith("+++"):
                lines.append(line[1:])
        return "\n".join(lines)

    def _is_bare_except(self, code: str) -> bool:
        """Check if code has bare except clause."""
        # Match 'except:' not followed by specific exception
        pattern = r"except\s*:(?!\s*\w)"
        return bool(re.search(pattern, code))

    def _is_catch_and_pass(self, code: str) -> bool:
        """Check if code catches exception and does nothing."""
        patterns = [
            r"except\s*.*?:\s*pass\s*(?:#.*)?$",
            r"except\s*.*?:\s*\.\.\.(?:#.*)?$",
            r"except\s*.*?:\s*#.*\n\s*pass",
        ]
        return any(re.search(p, code, re.MULTILINE) for p in patterns)

    def _addresses_key_issue(self, code: str, root_cause: str) -> bool:
        """Check if KeyError fix properly addresses missing key."""
        # Good patterns: .get() with handling, key existence check, validation
        good_patterns = [
            r"\.get\s*\([^)]+\)\s*(?:is|==|!=|if|\?|or|and)",  # .get() with handling
            r"if\s+['\"]?\w+['\"]?\s+(?:not\s+)?in\s+",  # key in dict
            r"if\s+\w+\s*\.get\s*\([^)]+\)",  # if x.get()
            r"raise\s+(?:KeyError|ValueError)",  # Explicit re-raise with context
        ]
        return any(re.search(p, code) for p in good_patterns)

    def _addresses_none_source(self, code: str) -> bool:
        """Check if None-related fix prevents None at source."""
        good_patterns = [
            r"if\s+\w+\s+is\s+not\s+None",  # Explicit None check
            r"if\s+\w+\s*:",  # Truthy check
            r"assert\s+\w+\s+is\s+not\s+None",  # Assertion
            r"\w+\s*=\s*\w+\s+or\s+\w+",  # Default value with 'or'
            r"raise\s+(?:TypeError|ValueError)",  # Explicit error
        ]
        return any(re.search(p, code) for p in good_patterns)

    def _is_hasattr_bandaid(self, code: str) -> bool:
        """Check if hasattr is used as a bandaid without proper handling."""
        # hasattr + access without proper else handling
        pattern = r"if\s+hasattr\s*\([^)]+\)\s*:(?:(?!else).)*$"
        return bool(re.search(pattern, code, re.MULTILINE | re.DOTALL))

    def _addresses_index_issue(self, code: str) -> bool:
        """Check if IndexError fix properly handles bounds."""
        good_patterns = [
            r"if\s+len\s*\(\w+\)",  # Length check
            r"if\s+\w+\s*:",  # Non-empty check
            r"\w+\s*\[\s*:\s*\d+\s*\]",  # Slicing (won't raise IndexError)
            r"try:.*except\s+IndexError.*raise",  # Re-raise pattern
        ]
        return any(re.search(p, code, re.MULTILINE | re.DOTALL) for p in good_patterns)

    def _has_broad_exception_handler(self, code: str) -> bool:
        """Check for overly broad exception handlers."""
        # Match except Exception/BaseException without re-raising
        pattern = r"except\s+(?:Exception|BaseException)\s*(?:as\s+\w+)?:\s*(?!.*raise)"
        return bool(re.search(pattern, code, re.MULTILINE))

    def _extract_touched_files(self, diff: str) -> set[str]:
        files = set()
        for line in diff.split("\n"):
            if line.startswith("+++ b/"):
                files.add(line[6:].strip())
        return files

    def _extract_referenced_files(self, text: str) -> set[str]:
        return set(re.findall(r"[\w/\\.-]+\\.py", text))


class RootCauseVerifier:
    """Verify that fixes address root causes, not just symptoms.

    Uses fast heuristic checks first, then optionally falls back
    to LLM verification for complex cases.
    """

    def __init__(
        self,
        llm_client: Optional[Any] = None,
        use_symptom_detector: bool = True,
    ):
        """Initialize the root cause verifier.

        Args:
            llm_client: Optional LLM client for verification
            use_symptom_detector: Whether to use SymptomDetector
        """
        self.llm_client = llm_client
        self.heuristics = RootCauseHeuristics()
        self._symptom_detector = None if not use_symptom_detector else None

    @property
    def symptom_detector(self) -> SymptomDetector:
        """Get or create symptom detector."""
        if self._symptom_detector is None:
            from autodebug.validation.symptom_detector import SymptomDetector

            self._symptom_detector = SymptomDetector()
        return self._symptom_detector

    def verify(
        self,
        root_cause: str,
        fix_diff: str,
        original_code: str,
        exception_type: str,
        exception_message: str = "",
    ) -> RootCauseVerification:
        """Verify that a fix addresses the stated root cause.

        Args:
            root_cause: Stated root cause from analysis
            fix_diff: The unified diff of the fix
            original_code: Original source code
            exception_type: Type of exception being fixed
            exception_message: Exception message

        Returns:
            RootCauseVerification with analysis
        """
        # Run heuristic checks first (fast)
        heuristic_result = self.heuristics.check(
            root_cause=root_cause,
            fix_diff=fix_diff,
            exception_type=exception_type,
            exception_message=exception_message,
        )

        # Check for symptom patterns
        symptom_patterns = self.symptom_detector.detect(fix_diff)
        if symptom_patterns:
            high_severity = [p for p in symptom_patterns if p.severity == "high"]
            if high_severity:
                heuristic_result.issues.extend(
                    f"Symptom pattern: {p.description}" for p in high_severity
                )
                heuristic_result.addresses_root_cause = False
                heuristic_result.definitive = True

        # If heuristics are definitive, return without LLM
        if heuristic_result.definitive:
            return heuristic_result.to_verification()

        # If LLM available and heuristics inconclusive, use LLM
        if self.llm_client and not heuristic_result.definitive:
            return self._llm_verify(
                root_cause=root_cause,
                fix_diff=fix_diff,
                original_code=original_code,
                exception_type=exception_type,
                exception_message=exception_message,
            )

        # Fallback to heuristic result.  Apply reduced confidence modifier
        # only when heuristics found issues (ambiguous result).  When all
        # checks passed cleanly, use the full confidence value so the
        # pipeline does not reject fixes unnecessarily.
        if heuristic_result.issues:
            return heuristic_result.to_verification(confidence_modifier=0.8)
        return heuristic_result.to_verification(confidence_modifier=1.0)

    def _llm_verify(
        self,
        root_cause: str,
        fix_diff: str,
        original_code: str,
        exception_type: str,
        exception_message: str,
    ) -> RootCauseVerification:
        """Use LLM to verify fix addresses root cause.

        Args:
            root_cause: Stated root cause
            fix_diff: The fix diff
            original_code: Original code
            exception_type: Exception type
            exception_message: Exception message

        Returns:
            RootCauseVerification from LLM analysis
        """
        prompt = f"""You are verifying whether a code fix addresses the stated root cause.

## Root Cause
{root_cause}

## Exception
{exception_type}: {exception_message}

## Original Code
```python
{original_code[:2000]}
```

## Proposed Fix
```diff
{fix_diff}
```

## Verification Task

Analyze whether this fix addresses the root cause or just suppresses symptoms.

Answer these questions:
1. Does the fix prevent the root cause from occurring?
2. Does the fix handle the root cause appropriately (not just catch and ignore)?
3. Does the fix modify the right location (where root cause originates, not just where it manifests)?
4. Could this fix pass the failing test while producing incorrect behavior on other inputs? Identify any overfitting risks where the fix is too specific to the test case rather than being a general correction.

Respond with JSON:
{{
    "addresses_root_cause": true or false,
    "confidence": 0.0 to 1.0,
    "reasoning": "Your explanation",
    "issues": ["List of issues with this fix"],
    "suggestions": ["How to improve if not addressing root cause"]
}}
"""

        try:
            response = self.llm_client.generate(prompt)
            return self._parse_llm_response(response)
        except Exception as e:
            # Fail-closed on LLM error: do NOT assume the fix is valid
            logger.warning(f"Root cause verification LLM call failed: {e}")
            return RootCauseVerification(
                addresses_root_cause=False,
                confidence=0.0,
                reasoning=f"LLM verification failed ({e}), cannot confirm fix addresses root cause",
                issues=["Root cause verification could not be completed"],
                suggestions=["Re-run verification when LLM service is available"],
            )

    def _parse_llm_response(self, response: str) -> RootCauseVerification:
        """Parse LLM response into verification result.

        Args:
            response: LLM response string

        Returns:
            RootCauseVerification
        """
        import json

        try:
            # Try to extract JSON from response
            json_match = re.search(r"\{[\s\S]*\}", response)
            if json_match:
                data = json.loads(json_match.group())
                return RootCauseVerification(
                    addresses_root_cause=data.get("addresses_root_cause", True),
                    confidence=float(data.get("confidence", 0.5)),
                    reasoning=data.get("reasoning", ""),
                    issues=data.get("issues", []),
                    suggestions=data.get("suggestions", []),
                )
        except (json.JSONDecodeError, ValueError):
            pass

        # Fallback parsing
        addresses = "addresses" in response.lower() and "root cause" in response.lower()
        return RootCauseVerification(
            addresses_root_cause=addresses,
            confidence=0.4,
            reasoning="Could not parse LLM response",
            issues=[],
            suggestions=[],
        )

    def quick_check(self, fix_diff: str, exception_type: str) -> bool:
        """Quick check if a fix has obvious symptom-suppression patterns.

        Faster than full verification, useful for filtering.

        Args:
            fix_diff: The fix diff
            exception_type: Exception type

        Returns:
            True if fix passes quick check (no obvious issues)
        """
        # Check for high-severity symptom patterns
        patterns = self.symptom_detector.detect(fix_diff)
        if any(p.severity == "high" for p in patterns):
            return False

        # Check basic heuristics
        added_code = self.heuristics._extract_added_code(fix_diff)
        if self.heuristics._is_bare_except(added_code):
            return False
        if self.heuristics._is_catch_and_pass(added_code):
            return False

        return True
