"""Score and rank fixes by quality metrics.

Ranks fixes based on multiple factors including test results,
root cause verification, minimal changes, and absence of anti-patterns.
"""

from __future__ import annotations

import re
from dataclasses import dataclass
from typing import TYPE_CHECKING, List, Optional

if TYPE_CHECKING:
    from autodebug.pipeline.repair_pipeline import PipelineFix


@dataclass
class RankingWeights:
    """Weights for different ranking factors."""

    test_passed: float = 40.0
    root_cause_verified: float = 25.0
    type_checks: float = 15.0
    minimal_change: float = 10.0
    no_symptom_suppression: float = 10.0

    @classmethod
    def default(cls) -> RankingWeights:
        """Get default weights."""
        return cls()

    @classmethod
    def test_focused(cls) -> RankingWeights:
        """Weights that prioritize test passing."""
        return cls(
            test_passed=60.0,
            root_cause_verified=15.0,
            type_checks=10.0,
            minimal_change=10.0,
            no_symptom_suppression=5.0,
        )

    @classmethod
    def quality_focused(cls) -> RankingWeights:
        """Weights that prioritize code quality."""
        return cls(
            test_passed=30.0,
            root_cause_verified=30.0,
            type_checks=20.0,
            minimal_change=10.0,
            no_symptom_suppression=10.0,
        )


class FixRanker:
    """Ranks fixes by quality metrics.

    Calculates scores based on multiple factors and ranks fixes
    to present the best ones to users.
    """

    # Symptom suppression patterns to detect
    SYMPTOM_PATTERNS = [
        r"except\s*.*?:\s*pass\s*(?:#.*)?$",  # except: pass
        r"except\s*.*?:\s*\.\.\.\s*(?:#.*)?$",  # except: ...
        r"except\s+Exception\s*:",  # Catch-all Exception
        r"except\s+BaseException\s*:",  # Catch-all BaseException
        r"except\s*.*?:\s*return\s+None\s*$",  # except: return None
    ]

    def __init__(self, weights: Optional[RankingWeights] = None):
        """Initialize the fix ranker.

        Args:
            weights: Ranking weights to use
        """
        self.weights = weights or RankingWeights.default()

    def calculate_score(self, pipeline_fix: PipelineFix) -> float:
        """Calculate overall quality score for a fix.

        Args:
            pipeline_fix: Fix with pipeline results

        Returns:
            Quality score (higher is better)
        """
        from autodebug.pipeline.repair_pipeline import PipelineStage

        score = 0.0

        for result in pipeline_fix.results:
            if result.stage == PipelineStage.TEST and result.passed:
                score += self.weights.test_passed

            elif result.stage == PipelineStage.VERIFY:
                if result.passed:
                    score += self.weights.root_cause_verified
                # Add partial credit for high confidence
                confidence = result.details.get("confidence", 0)
                score += self.weights.root_cause_verified * confidence * 0.5

            elif result.stage == PipelineStage.VALIDATE:
                if result.details.get("type_checks", False):
                    score += self.weights.type_checks

        # Bonus for minimal changes
        score += self._score_minimal_change(pipeline_fix)

        # Penalty for symptom suppression patterns
        if self._has_symptom_suppression(pipeline_fix.fix.unified_diff):
            score -= self.weights.no_symptom_suppression

        return max(score, 0.0)

    def _score_minimal_change(self, pipeline_fix: PipelineFix) -> float:
        """Score based on how minimal the change is.

        Args:
            pipeline_fix: Fix to score

        Returns:
            Score for minimal change
        """
        diff_size = len(pipeline_fix.fix.unified_diff)
        diff_lines = pipeline_fix.fix.diff_lines

        # Prefer smaller changes
        if diff_lines <= 3:
            return self.weights.minimal_change
        elif diff_lines <= 5:
            return self.weights.minimal_change * 0.8
        elif diff_lines <= 10:
            return self.weights.minimal_change * 0.5
        elif diff_size < 500:
            return self.weights.minimal_change * 0.3

        return 0.0

    def _has_symptom_suppression(self, diff: str) -> bool:
        """Check for symptom suppression patterns.

        Args:
            diff: Unified diff string

        Returns:
            True if symptom suppression detected
        """
        # Only check added lines
        added_code = self._extract_added_code(diff)

        for pattern in self.SYMPTOM_PATTERNS:
            if re.search(pattern, added_code, re.MULTILINE):
                return True

        return False

    def _extract_added_code(self, diff: str) -> str:
        """Extract added lines from a diff.

        Args:
            diff: Unified diff string

        Returns:
            String of added code
        """
        lines = []
        for line in diff.split("\n"):
            if line.startswith("+") and not line.startswith("+++"):
                lines.append(line[1:])
        return "\n".join(lines)

    def compare_fixes(
        self,
        fix_a: PipelineFix,
        fix_b: PipelineFix,
    ) -> int:
        """Compare two fixes.

        Args:
            fix_a: First fix
            fix_b: Second fix

        Returns:
            -1 if a < b, 0 if equal, 1 if a > b
        """
        score_a = self.calculate_score(fix_a)
        score_b = self.calculate_score(fix_b)

        if score_a > score_b:
            return 1
        elif score_a < score_b:
            return -1
        return 0

    def rank_fixes(self, fixes: List[PipelineFix]) -> List[PipelineFix]:
        """Rank fixes by quality score.

        Args:
            fixes: List of fixes to rank

        Returns:
            Sorted list with best fixes first
        """
        # Calculate scores
        for fix in fixes:
            fix.final_score = self.calculate_score(fix)

        # Sort descending by score
        return sorted(fixes, key=lambda f: f.final_score, reverse=True)

    def get_score_breakdown(self, pipeline_fix: PipelineFix) -> dict:
        """Get detailed score breakdown for a fix.

        Args:
            pipeline_fix: Fix to analyze

        Returns:
            Dict with score components
        """
        from autodebug.pipeline.repair_pipeline import PipelineStage

        breakdown = {
            "test_score": 0.0,
            "verification_score": 0.0,
            "type_check_score": 0.0,
            "minimal_change_score": 0.0,
            "symptom_penalty": 0.0,
            "total": 0.0,
        }

        for result in pipeline_fix.results:
            if result.stage == PipelineStage.TEST and result.passed:
                breakdown["test_score"] = self.weights.test_passed

            elif result.stage == PipelineStage.VERIFY:
                if result.passed:
                    breakdown["verification_score"] += self.weights.root_cause_verified
                confidence = result.details.get("confidence", 0)
                breakdown["verification_score"] += (
                    self.weights.root_cause_verified * confidence * 0.5
                )

            elif result.stage == PipelineStage.VALIDATE:
                if result.details.get("type_checks", False):
                    breakdown["type_check_score"] = self.weights.type_checks

        breakdown["minimal_change_score"] = self._score_minimal_change(pipeline_fix)

        if self._has_symptom_suppression(pipeline_fix.fix.unified_diff):
            breakdown["symptom_penalty"] = -self.weights.no_symptom_suppression

        breakdown["total"] = (
            breakdown["test_score"]
            + breakdown["verification_score"]
            + breakdown["type_check_score"]
            + breakdown["minimal_change_score"]
            + breakdown["symptom_penalty"]
        )

        return breakdown

    def explain_ranking(
        self,
        fixes: List[PipelineFix],
    ) -> str:
        """Generate human-readable explanation of ranking.

        Args:
            fixes: Ranked list of fixes

        Returns:
            Explanation string
        """
        lines = ["Fix Ranking Explanation", "=" * 40]

        for i, fix in enumerate(fixes, 1):
            breakdown = self.get_score_breakdown(fix)
            lines.append(f"\n{i}. {fix.fix.strategy} fix (Score: {breakdown['total']:.1f})")

            if breakdown["test_score"] > 0:
                lines.append(f"   + Test passed: +{breakdown['test_score']:.1f}")
            if breakdown["verification_score"] > 0:
                lines.append(
                    f"   + Root cause verified: +{breakdown['verification_score']:.1f}"
                )
            if breakdown["type_check_score"] > 0:
                lines.append(f"   + Type checks pass: +{breakdown['type_check_score']:.1f}")
            if breakdown["minimal_change_score"] > 0:
                lines.append(
                    f"   + Minimal change: +{breakdown['minimal_change_score']:.1f}"
                )
            if breakdown["symptom_penalty"] < 0:
                lines.append(
                    f"   - Symptom suppression: {breakdown['symptom_penalty']:.1f}"
                )

        return "\n".join(lines)
