"""
Eval suite models for releaseops.

Defines evaluation suites — collections of test cases with assertions
that serve as quality gates for bundle promotion.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Dict, List, Optional


class JudgeType(str, Enum):
    """Types of judges that can evaluate test cases."""

    LLM_JUDGE = "llm_judge"
    EXACT_MATCH = "exact_match"
    CONTAINS = "contains"
    REGEX = "regex"
    COMPOSITE = "composite"


@dataclass(frozen=True)
class JudgeConfig:
    """Configuration for a judge instance."""

    judge_type: JudgeType
    config: Dict[str, Any] = field(default_factory=dict)

    def to_dict(self) -> dict:
        d: dict = {"type": self.judge_type.value}
        if self.config:
            d["config"] = dict(self.config)
        return d

    @classmethod
    def from_dict(cls, data: dict) -> JudgeConfig:
        return cls(
            judge_type=JudgeType(data["type"]),
            config=data.get("config", {}),
        )


@dataclass(frozen=True)
class Assertion:
    """
    A single assertion within a test case.

    Each assertion uses a judge to evaluate the output against
    expected criteria, with a weight for scoring.
    """

    judge_type: JudgeType
    weight: float = 1.0
    config: Dict[str, Any] = field(default_factory=dict)

    def __post_init__(self) -> None:
        if self.weight < 0:
            raise ValueError(f"weight must be non-negative, got {self.weight}")

    def to_dict(self) -> dict:
        d: dict = {
            "type": self.judge_type.value,
            "weight": self.weight,
        }
        if self.config:
            d["config"] = dict(self.config)
        return d

    @classmethod
    def from_dict(cls, data: dict) -> Assertion:
        return cls(
            judge_type=JudgeType(data["type"]),
            weight=data.get("weight", 1.0),
            config=data.get("config", {}),
        )


@dataclass(frozen=True)
class EvalCase:
    """
    A single test case in an eval suite.

    Contains input variables and assertions that evaluate the output.
    """

    id: str
    input: Dict[str, Any]  # Variables to render the prompt with
    assertions: List[Assertion] = field(default_factory=list)
    description: str = ""
    expected_output: Optional[str] = None  # Optional reference output
    tags: List[str] = field(default_factory=list)

    def __post_init__(self) -> None:
        if not self.id:
            raise ValueError("EvalCase id cannot be empty")
        if not self.assertions:
            raise ValueError(f"EvalCase '{self.id}' must have at least one assertion")

    @property
    def total_weight(self) -> float:
        return sum(a.weight for a in self.assertions)

    def to_dict(self) -> dict:
        d: dict = {
            "id": self.id,
            "input": dict(self.input),
            "assertions": [a.to_dict() for a in self.assertions],
        }
        if self.description:
            d["description"] = self.description
        if self.expected_output:
            d["expected_output"] = self.expected_output
        if self.tags:
            d["tags"] = list(self.tags)
        return d

    @classmethod
    def from_dict(cls, data: dict) -> EvalCase:
        assertions = [Assertion.from_dict(a) for a in data.get("assertions", [])]
        return cls(
            id=data["id"],
            input=data.get("input", {}),
            assertions=assertions,
            description=data.get("description", ""),
            expected_output=data.get("expected_output"),
            tags=data.get("tags", []),
        )


@dataclass(frozen=True)
class PromotionGate:
    """Quality gate configuration tied to an eval suite."""

    pass_rate_threshold: float = 0.95
    blocking: bool = True

    def __post_init__(self) -> None:
        if not 0.0 <= self.pass_rate_threshold <= 1.0:
            raise ValueError(
                f"pass_rate_threshold must be between 0.0 and 1.0, "
                f"got {self.pass_rate_threshold}"
            )

    def to_dict(self) -> dict:
        return {
            "pass_rate_threshold": self.pass_rate_threshold,
            "blocking": self.blocking,
        }

    @classmethod
    def from_dict(cls, data: dict) -> PromotionGate:
        return cls(
            pass_rate_threshold=data.get("pass_rate_threshold", 0.95),
            blocking=data.get("blocking", True),
        )


@dataclass(frozen=True)
class EvalSuite:
    """
    An evaluation suite — a collection of test cases with a default judge.

    Stored as YAML in .releaseops/evals/{suite-id}.yaml.
    Used as quality gates for bundle promotion.
    """

    id: str
    version: int = 1
    description: str = ""
    target_artifacts: List[str] = field(default_factory=list)  # Which artifact roles to eval
    pass_threshold: float = 0.95
    default_judge: Optional[JudgeConfig] = None
    cases: List[EvalCase] = field(default_factory=list)
    promotion_gate: Optional[PromotionGate] = None

    def __post_init__(self) -> None:
        if not self.id:
            raise ValueError("EvalSuite id cannot be empty")
        if not 0.0 <= self.pass_threshold <= 1.0:
            raise ValueError(
                f"pass_threshold must be between 0.0 and 1.0, got {self.pass_threshold}"
            )

    @property
    def total_cases(self) -> int:
        return len(self.cases)

    def get_case(self, case_id: str) -> Optional[EvalCase]:
        """Look up a case by ID."""
        for case in self.cases:
            if case.id == case_id:
                return case
        return None

    def to_dict(self) -> dict:
        d: dict = {
            "id": self.id,
            "version": self.version,
            "description": self.description,
            "target_artifacts": list(self.target_artifacts),
            "pass_threshold": self.pass_threshold,
        }
        if self.default_judge:
            d["default_judge"] = self.default_judge.to_dict()
        d["cases"] = [c.to_dict() for c in self.cases]
        if self.promotion_gate:
            d["promotion_gate"] = self.promotion_gate.to_dict()
        return d

    @classmethod
    def from_dict(cls, data: dict) -> EvalSuite:
        default_judge = None
        if "default_judge" in data:
            default_judge = JudgeConfig.from_dict(data["default_judge"])

        cases = [EvalCase.from_dict(c) for c in data.get("cases", [])]

        promotion_gate = None
        if "promotion_gate" in data:
            promotion_gate = PromotionGate.from_dict(data["promotion_gate"])

        return cls(
            id=data["id"],
            version=data.get("version", 1),
            description=data.get("description", ""),
            target_artifacts=data.get("target_artifacts", []),
            pass_threshold=data.get("pass_threshold", 0.95),
            default_judge=default_judge,
            cases=cases,
            promotion_gate=promotion_gate,
        )
