"""CoverageGate - Enforces 85% minimum test coverage via pytest-cov."""

import json
import subprocess
from pathlib import Path

from src.gates.quality_gate import GateResult


class CoverageGate:
    """Quality gate that runs pytest with coverage measurement.

    Executes pytest with coverage and enforces 85% minimum coverage (non-negotiable).
    """

    MINIMUM_COVERAGE = 85.0

    def check(self) -> GateResult:
        """Run pytest with coverage measurement.

        Returns:
            GateResult: Result indicating if coverage meets 85% minimum
        """
        try:
            # Run pytest with coverage
            result = subprocess.run(
                [
                    "python",
                    "-m",
                    "pytest",
                    "--cov=src",
                    "--cov-report=json",
                    "--cov-report=term-missing",
                ],
                capture_output=True,
                text=True,
                check=False,  # Don't raise on non-zero exit
                timeout=300,  # 5 minute timeout
            )

            # Try to read coverage data from coverage.json
            coverage_percent = self._extract_coverage_from_json()
            if coverage_percent is None:
                # Fallback to parsing stdout
                coverage_percent = self._extract_coverage_from_output(result.stdout)

            if coverage_percent is None:
                return GateResult(
                    passed=False,
                    message="Coverage gate failed: No coverage data found",
                    details={
                        "return_code": result.returncode,
                        "stdout": result.stdout,
                        "stderr": result.stderr,
                        "error": "Could not extract coverage percentage",
                    },
                )

            # Check if coverage meets minimum threshold
            if coverage_percent >= self.MINIMUM_COVERAGE:
                return GateResult(
                    passed=True,
                    message=(
                        f"Coverage gate passed: {coverage_percent:.1f}% coverage "
                        f"(minimum: {self.MINIMUM_COVERAGE}%)"
                    ),
                    details={
                        "return_code": result.returncode,
                        "coverage_percent": coverage_percent,
                        "minimum_coverage": self.MINIMUM_COVERAGE,
                        "stdout": result.stdout,
                        "stderr": result.stderr,
                    },
                )
            else:
                return GateResult(
                    passed=False,
                    message=(
                        f"Coverage gate failed: {coverage_percent:.1f}% coverage "
                        f"below minimum {self.MINIMUM_COVERAGE}%"
                    ),
                    details={
                        "return_code": result.returncode,
                        "coverage_percent": coverage_percent,
                        "minimum_coverage": self.MINIMUM_COVERAGE,
                        "stdout": result.stdout,
                        "stderr": result.stderr,
                    },
                )

        except FileNotFoundError as e:
            return GateResult(
                passed=False,
                message="Coverage gate failed: pytest not found or not installed",
                details={"error": str(e)},
            )

        except subprocess.TimeoutExpired as e:
            return GateResult(
                passed=False,
                message=f"Coverage gate failed: pytest timed out after {e.timeout} seconds",
                details={"error": str(e), "timeout": e.timeout},
            )

        except Exception as e:
            return GateResult(
                passed=False,
                message=f"Coverage gate failed: Unexpected error: {e}",
                details={"error": str(e)},
            )

    def _extract_coverage_from_json(self) -> float | None:
        """Extract coverage percentage from coverage.json file.

        Uses absolute path resolved from current working directory and validates
        that the path is within project boundaries to prevent path traversal attacks.

        Returns:
            float | None: Coverage percentage or None if file not found
        """
        try:
            # Get absolute path from current working directory
            cwd = Path.cwd().resolve()
            coverage_file = (cwd / "coverage.json").resolve()

            # Validate that coverage file is within project directory (prevent path traversal)
            if not str(coverage_file).startswith(str(cwd)):
                return None

            if coverage_file.exists():
                with open(coverage_file) as f:
                    data = json.load(f)
                    percent = data.get("totals", {}).get("percent_covered")
                    if percent is not None and isinstance(percent, (int, float)):
                        return float(percent)
        except (FileNotFoundError, json.JSONDecodeError, KeyError, OSError):
            pass
        return None

    def _extract_coverage_from_output(self, output: str) -> float | None:
        """Extract coverage percentage from pytest output.

        Args:
            output: stdout from pytest run

        Returns:
            float | None: Coverage percentage or None if not found
        """
        # Look for "TOTAL" line with coverage percentage
        # Example: "TOTAL     150     15    90%"
        for line in output.split("\n"):
            if "TOTAL" in line and "%" in line:
                parts = line.split()
                for part in parts:
                    if "%" in part:
                        try:
                            return float(part.rstrip("%"))
                        except ValueError:
                            continue
        return None
