"""Quality Gates — block AI deployments that fail reliability thresholds.

Analogous to SonarQube quality gates: define pass/fail criteria and enforce
them in CI/CD pipelines or at agent deployment time.

Example::

    gate = art.QualityGate(
        max_hallucination_score=20,
        min_confidence_score=70,
        max_cost_usd=0.03,
        max_latency_ms=3000,
    )

    # Check a single evaluation result
    result = art.evaluate(prompt=..., response=...)
    gate.check_evaluation(result)   # raises QualityGateError if thresholds not met

    # Check an EvaluationRecord (from instrumentation)
    gate.check_record(record)
"""
from __future__ import annotations

from dataclasses import dataclass, field
from typing import List, Optional


class QualityGateError(Exception):
    """Raised when an AI response fails one or more quality gate thresholds."""

    def __init__(self, message: str, violations: List[str]) -> None:
        super().__init__(message)
        self.violations = violations

    def __str__(self) -> str:
        violations_str = "\n  - ".join(self.violations)
        return f"Quality gate failed ({len(self.violations)} violation(s)):\n  - {violations_str}"


@dataclass
class QualityGate:
    """Define reliability thresholds for AI responses.

    All thresholds are optional — only the ones you set are enforced.

    Args:
        max_hallucination_score: Maximum allowed hallucination score (0–100).
            Lower is better. Default: no limit.
        min_confidence_score: Minimum required confidence score (0–100).
            Higher is better. Default: no limit.
        min_coherence_score: Minimum required coherence score (0–100). Default: no limit.
        min_instruction_following_score: Minimum instruction-following score. Default: no limit.
        max_cost_usd: Maximum cost per request in USD. Default: no limit.
        max_latency_ms: Maximum allowed latency in milliseconds. Default: no limit.
        name: Optional name for this gate, used in error messages.
    """

    max_hallucination_score: Optional[float] = None
    min_confidence_score: Optional[float] = None
    min_coherence_score: Optional[float] = None
    min_instruction_following_score: Optional[float] = None
    max_cost_usd: Optional[float] = None
    max_latency_ms: Optional[int] = None
    name: str = "default"

    def check_evaluation(self, result) -> None:
        """Check an EvaluationResult against thresholds.

        Args:
            result: An ``art.EvaluationResult`` from ``art.evaluate()``.

        Raises:
            QualityGateError: If any threshold is violated.
        """
        violations: List[str] = []

        if self.max_hallucination_score is not None:
            score = result.hallucination_score
            if score > self.max_hallucination_score:
                violations.append(
                    f"hallucination_score {score:.1f} exceeds max {self.max_hallucination_score}"
                )

        if self.min_confidence_score is not None:
            score = result.confidence_score
            if score < self.min_confidence_score:
                violations.append(
                    f"confidence_score {score:.1f} below min {self.min_confidence_score}"
                )

        if self.min_coherence_score is not None:
            score = result.coherence_score
            if score < self.min_coherence_score:
                violations.append(
                    f"coherence_score {score:.1f} below min {self.min_coherence_score}"
                )

        if self.min_instruction_following_score is not None:
            score = result.instruction_following_score
            if score < self.min_instruction_following_score:
                violations.append(
                    f"instruction_following_score {score:.1f} below min {self.min_instruction_following_score}"
                )

        if violations:
            raise QualityGateError(
                f"Quality gate '{self.name}' failed",
                violations=violations,
            )

    def check_record(self, record) -> None:
        """Check an EvaluationRecord (from instrumentation) against thresholds.

        Args:
            record: An ``art.EvaluationRecord`` produced by ``instrument_openai()``
                or ``instrument_anthropic()``.

        Raises:
            QualityGateError: If any threshold is violated.
        """
        violations: List[str] = []

        if self.max_hallucination_score is not None:
            score = record.hallucination_score
            if score > self.max_hallucination_score:
                violations.append(
                    f"hallucination_score {score:.1f} exceeds max {self.max_hallucination_score}"
                )

        if self.min_confidence_score is not None:
            score = record.confidence_score
            if score < self.min_confidence_score:
                violations.append(
                    f"confidence_score {score:.1f} below min {self.min_confidence_score}"
                )

        if self.min_coherence_score is not None:
            score = record.coherence_score
            if score < self.min_coherence_score:
                violations.append(
                    f"coherence_score {score:.1f} below min {self.min_coherence_score}"
                )

        if self.min_instruction_following_score is not None:
            score = record.instruction_following_score
            if score < self.min_instruction_following_score:
                violations.append(
                    f"instruction_following_score {score:.1f} below min {self.min_instruction_following_score}"
                )

        if self.max_cost_usd is not None:
            cost = record.estimated_cost_usd
            if cost > self.max_cost_usd:
                violations.append(
                    f"cost ${cost:.5f} exceeds max ${self.max_cost_usd:.5f}"
                )

        if self.max_latency_ms is not None:
            latency = record.latency_ms
            if latency > self.max_latency_ms:
                violations.append(
                    f"latency {latency}ms exceeds max {self.max_latency_ms}ms"
                )

        if violations:
            raise QualityGateError(
                f"Quality gate '{self.name}' failed",
                violations=violations,
            )

    def passed(self, result_or_record) -> bool:
        """Return True if the result passes all thresholds, False otherwise."""
        try:
            if hasattr(result_or_record, "latency_ms"):
                self.check_record(result_or_record)
            else:
                self.check_evaluation(result_or_record)
            return True
        except QualityGateError:
            return False
