"""Regression Guard - Distinguish real regressions from flakiness.

Prevents the self-healer from generating fixes that mask real bugs
introduced by code changes. Uses historical test results and git
context to classify failures as regression vs. flaky.
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import Any, Dict, List, Optional


@dataclass
class RegressionCheckResult:
    """Result of a regression vs. flakiness check."""

    is_regression: bool
    confidence: float  # 0.0 - 1.0
    reason: str
    signals: Dict[str, Any]


class RegressionGuard:
    """Guard that checks whether a test failure is a regression or flaky.

    Algorithm:
    1. If test passed with the SAME git commit before -> flaky (0.95)
    2. If test file or SUT changed in recent commits AND pass rate
       dropped from >95% to <50% -> regression (0.85)
    3. If failure signature is brand new (never seen before) -> lean
       regression (+0.15 confidence)
    4. Otherwise -> flaky
    """

    def __init__(self, db_get_recent_results=None, db_get_failure_signatures=None):
        self._get_recent_results = db_get_recent_results
        self._get_failure_signatures = db_get_failure_signatures

    async def check(
        self,
        test_name: str,
        test_file: str,
        branch: str,
        git_commit: Optional[str],
        project_id: Optional[str],
        failure_signature: Optional[str] = None,
    ) -> RegressionCheckResult:
        """Check if a test failure looks like a regression or flakiness.

        Args:
            test_name: Fully qualified test name
            test_file: Path to the test file
            branch: Git branch name
            git_commit: Current git commit SHA
            project_id: Project ID for scoping queries
            failure_signature: Current failure signature string

        Returns:
            RegressionCheckResult with classification and confidence
        """
        signals: Dict[str, Any] = {}

        # Fetch recent test results
        recent_results = await self._fetch_recent_results(
            test_name, project_id
        )
        signals["recent_results_count"] = len(recent_results)

        # Signal 1: Same commit previously passed
        if git_commit and recent_results:
            same_commit_passes = [
                r for r in recent_results
                if r.get("git_commit") == git_commit
                and r.get("status") == "PASSED"
            ]
            if same_commit_passes:
                return RegressionCheckResult(
                    is_regression=False,
                    confidence=0.95,
                    reason="Test passed with same commit before - likely flaky",
                    signals={
                        **signals,
                        "same_commit_passes": len(same_commit_passes),
                        "signal": "same_commit_passed",
                    },
                )

        # Signal 2: Pass rate drop correlated with code changes
        if recent_results and len(recent_results) >= 6:
            old_results = recent_results[5:]  # Older results
            new_results = recent_results[:5]  # Recent results

            old_pass_rate = self._pass_rate(old_results)
            new_pass_rate = self._pass_rate(new_results)

            signals["old_pass_rate"] = old_pass_rate
            signals["new_pass_rate"] = new_pass_rate

            if old_pass_rate > 0.95 and new_pass_rate < 0.50:
                confidence = 0.85
                # Check if failure signature is new
                if failure_signature:
                    known = await self._is_known_signature(
                        test_name, project_id, failure_signature
                    )
                    if not known:
                        confidence = min(1.0, confidence + 0.15)
                        signals["new_signature"] = True

                return RegressionCheckResult(
                    is_regression=True,
                    confidence=confidence,
                    reason=(
                        f"Pass rate dropped from {old_pass_rate:.0%} to "
                        f"{new_pass_rate:.0%} - likely regression"
                    ),
                    signals={
                        **signals,
                        "signal": "pass_rate_drop",
                    },
                )

        # Signal 3: Brand new failure signature with no history
        if failure_signature and recent_results:
            known = await self._is_known_signature(
                test_name, project_id, failure_signature
            )
            if not known:
                # New signature suggests regression but with lower confidence
                base_confidence = 0.60
                # If we have history and it was mostly passing, lean more towards regression
                if recent_results:
                    overall_pass_rate = self._pass_rate(recent_results)
                    if overall_pass_rate > 0.90:
                        base_confidence = 0.75

                signals["new_signature"] = True
                signals["overall_pass_rate"] = self._pass_rate(recent_results)

                return RegressionCheckResult(
                    is_regression=True,
                    confidence=base_confidence,
                    reason="New failure signature on previously stable test",
                    signals={
                        **signals,
                        "signal": "new_signature",
                    },
                )

        # Signal 4: No history at all - can't determine
        if not recent_results:
            return RegressionCheckResult(
                is_regression=False,
                confidence=0.50,
                reason="No test history available - defaulting to flaky",
                signals={
                    **signals,
                    "signal": "no_history",
                },
            )

        # Default: Assume flaky
        return RegressionCheckResult(
            is_regression=False,
            confidence=0.70,
            reason="Test has mixed history - likely flaky",
            signals={
                **signals,
                "signal": "mixed_history",
                "overall_pass_rate": self._pass_rate(recent_results),
            },
        )

    async def _fetch_recent_results(
        self,
        test_name: str,
        project_id: Optional[str],
    ) -> List[Dict[str, Any]]:
        """Fetch recent test results, ordered newest first."""
        if self._get_recent_results:
            return await self._get_recent_results(test_name, project_id)

        import asyncio
        from api.db import get_recent_test_results
        return await asyncio.to_thread(get_recent_test_results, test_name, project_id)

    async def _is_known_signature(
        self,
        test_name: str,
        project_id: Optional[str],
        signature: str,
    ) -> bool:
        """Check if a failure signature has been seen before."""
        if self._get_failure_signatures:
            signatures = await self._get_failure_signatures(
                test_name, project_id
            )
        else:
            import asyncio
            from api.db import get_test_failure_signatures
            signatures = await asyncio.to_thread(get_test_failure_signatures, test_name, project_id)

        return signature in signatures

    @staticmethod
    def _pass_rate(results: List[Dict[str, Any]]) -> float:
        """Calculate pass rate from a list of run results."""
        if not results:
            return 0.0
        passed = sum(
            1 for r in results if r.get("status") == "PASSED"
        )
        return passed / len(results)
