"""
cortex/core/quality_gates.py — Quality Gates Engine (Issue #368).

Evaluates production readiness checks against collected metrics. Each gate
defines a metric, threshold, and action to take when the threshold is violated.

Design decisions:
    - Gates are dataclasses for serialisation and config-driven overrides.
    - GateResult contains the full context (gate definition, actual value,
      pass/fail, human message) so callers never need to re-derive.
    - DEFAULT_GATES provides sensible production defaults; users can override
      via CortexConfig in the future.
    - Graceful with empty/missing metrics: returns pass with a "no data" note
      rather than blocking on cold start.
    - No external dependencies: pure Python stdlib only.

Dependencies: cortex.core.metrics.MetricsCollector, cortex.config.CortexConfig.
Dependents: cortex.api.plugin (health endpoint).
"""
from __future__ import annotations

import time
from dataclasses import dataclass, field
from typing import Any, Dict, List, Optional, TYPE_CHECKING

if TYPE_CHECKING:
    from cortex.config import CortexConfig
    from cortex.core.metrics import MetricsCollector


# ---------------------------------------------------------------------------
# Data types
# ---------------------------------------------------------------------------

@dataclass
class QualityGate:
    """Definition of a single quality gate.

    Attributes:
        name: Human-readable gate identifier.
        metric: Name of the metric to check (must exist in MetricsCollector).
        threshold: The boundary value. Interpretation depends on the gate:
                   - For latency gates: maximum acceptable p95 value.
                   - For rate gates: minimum acceptable success rate.
                   - For overflow gates: maximum acceptable percentage.
        action: What to do when the gate fails:
                - "warn": log a warning, don't block operations.
                - "block": prevent the operation from proceeding.
                - "degrade": switch to degraded mode (reduced functionality).
        comparison: How to compare actual vs threshold:
                    - "lte": pass if actual <= threshold (latency, overflow)
                    - "gte": pass if actual >= threshold (success rates)
    """
    name: str
    metric: str
    threshold: float
    action: str = "warn"
    comparison: str = "lte"


@dataclass
class GateResult:
    """Result of evaluating a single quality gate.

    Attributes:
        gate: The QualityGate that was evaluated.
        passed: True if the metric meets the threshold requirement.
        actual_value: The metric value that was compared against the threshold.
        message: Human-readable description of the result.
    """
    gate: QualityGate
    passed: bool
    actual_value: float
    message: str


# ---------------------------------------------------------------------------
# Default gates
# ---------------------------------------------------------------------------

DEFAULT_GATES: List[QualityGate] = [
    QualityGate(
        name="retrieval_latency",
        metric="retrieval_latency_ms",
        threshold=500.0,
        action="warn",
        comparison="lte",
    ),
    QualityGate(
        name="extraction_success_rate",
        metric="extraction_success_rate",
        threshold=0.8,
        action="block",
        comparison="gte",
    ),
    QualityGate(
        name="boot_latency",
        metric="boot_latency_ms",
        threshold=2000.0,
        action="warn",
        comparison="lte",
    ),
    QualityGate(
        name="memory_staleness",
        metric="memories_stored",
        threshold=0.0,
        action="warn",
        comparison="gte",
    ),
    QualityGate(
        name="budget_overflow",
        metric="context_budget_usage_pct",
        threshold=100.0,
        action="block",
        comparison="lte",
    ),
]


# ---------------------------------------------------------------------------
# QualityGateEngine
# ---------------------------------------------------------------------------

class QualityGateEngine:
    """
    Evaluates quality gates against current metrics.

    Each gate checks a specific metric against a threshold and reports
    pass/fail with the appropriate action level.

    Usage::

        engine = QualityGateEngine(metrics=collector, config=config)
        result = await engine.check("retrieval_latency")
        all_results = await engine.check_all()
    """

    def __init__(
        self,
        metrics: "MetricsCollector",
        config: "CortexConfig",
        gates: Optional[List[QualityGate]] = None,
    ) -> None:
        self._metrics = metrics
        self._config = config
        self._gates: Dict[str, QualityGate] = {}

        for gate in (gates or DEFAULT_GATES):
            self._gates[gate.name] = gate

    async def check(self, gate_name: str) -> GateResult:
        """Evaluate a single quality gate by name.

        Args:
            gate_name: Name of the gate to check (must be a registered gate).

        Returns:
            GateResult with pass/fail status and details.

        Raises:
            KeyError: If gate_name is not a registered gate.
        """
        if gate_name not in self._gates:
            raise KeyError(f"Unknown quality gate: {gate_name}")

        gate = self._gates[gate_name]
        actual_value = await self._get_metric_value(gate)

        if actual_value is None:
            # No data yet — pass gracefully on cold start
            return GateResult(
                gate=gate,
                passed=True,
                actual_value=0.0,
                message=f"{gate.name}: no data available (cold start — passing by default)",
            )

        passed = self._evaluate(gate, actual_value)

        if passed:
            message = f"{gate.name}: OK (value={actual_value:.2f}, threshold={gate.threshold:.2f})"
        else:
            message = (
                f"{gate.name}: FAILED [{gate.action}] "
                f"(value={actual_value:.2f}, threshold={gate.threshold:.2f})"
            )

        return GateResult(
            gate=gate,
            passed=passed,
            actual_value=actual_value,
            message=message,
        )

    async def check_all(self) -> List[GateResult]:
        """Evaluate all registered quality gates.

        Returns:
            List of GateResult for every registered gate.
        """
        results = []
        for gate_name in self._gates:
            results.append(await self.check(gate_name))
        return results

    # ------------------------------------------------------------------ #
    # Internal helpers
    # ------------------------------------------------------------------ #

    async def _get_metric_value(self, gate: QualityGate) -> Optional[float]:
        """Extract the relevant metric value for a gate.

        For histogram metrics (latency), returns the p95 value.
        For counter metrics, returns the counter value.
        For gauge metrics, returns the gauge value.
        """
        summary = await self._metrics.get_summary()

        # Check histograms first (latency metrics)
        histograms = summary.get("histograms", {})
        if gate.metric in histograms:
            hist_data = histograms[gate.metric]
            if not hist_data:
                return None
            return hist_data.get("p95", hist_data.get("mean", None))

        # Check counters
        counters = summary.get("counters", {})
        if gate.metric in counters:
            val = counters[gate.metric]
            if isinstance(val, dict):
                return val.get("total", 0.0)
            return float(val)

        # Check gauges
        gauges = summary.get("gauges", {})
        if gate.metric in gauges:
            val = gauges[gate.metric]
            if isinstance(val, dict):
                return val.get("value", 0.0)
            return float(val)

        return None

    @staticmethod
    def _evaluate(gate: QualityGate, actual_value: float) -> bool:
        """Compare actual value against threshold using the gate's comparison."""
        if gate.comparison == "lte":
            return actual_value <= gate.threshold
        elif gate.comparison == "gte":
            return actual_value >= gate.threshold
        else:
            # Unknown comparison — default to pass
            return True
