"""Core data models for evalkit."""

from __future__ import annotations

from enum import StrEnum
from typing import Any

from pydantic import BaseModel, ConfigDict, Field, model_validator


class Suite(StrEnum):
    """Available evaluation suites."""

    RAG_QA = "rag_qa"
    DOCUMENT_SEARCH = "document_search"
    CONVERSATIONAL = "conversational"
    AGENT_TOOL_CALL = "agent_tool_call"
    LIVE_QA = "live_qa"
    ANSWER_SIMILARITY = "answer_similarity"


class ToolCall(BaseModel):
    """A single tool invocation with name and parameters."""

    model_config = ConfigDict(frozen=False)

    name: str
    parameters: dict[str, Any] = Field(default_factory=dict)


class TestCase(BaseModel):
    """A single evaluation test case fed to the RAG pipeline."""

    model_config = ConfigDict(frozen=False)

    question: str
    context: list[str] = Field(default_factory=list)
    answer: str = ""
    expected_answer: str | None = None
    expected_tool_calls: list[ToolCall] | None = None
    actual_tool_calls: list[ToolCall] | None = None
    metadata: dict[str, Any] = Field(default_factory=dict)


class JudgeResponse(BaseModel):
    """Structured response returned by a judge LLM.

    Also used as the structured output schema for OpenAI (``response_format``)
    and Anthropic (``output_format``).
    """

    model_config = ConfigDict(frozen=False)

    score: float = Field(ge=0.0, le=1.0, description="Evaluation score from 0.0 (worst) to 1.0 (best)")
    reason: str = Field(description="Brief explanation of the score")


class MetricScore(BaseModel):
    """Score produced by a single metric for one test case."""

    model_config = ConfigDict(frozen=False)

    metric_name: str
    score: float = Field(ge=0.0, le=1.0)
    reason: str
    passed: bool


class EvaluationResult(BaseModel):
    """Aggregated result for a single test case across all metrics."""

    model_config = ConfigDict(frozen=False)

    test_case: TestCase
    scores: list[MetricScore]
    overall_score: float = Field(default=0.0, ge=0.0, le=1.0)

    @model_validator(mode="after")
    def _compute_overall_score(self) -> EvaluationResult:
        """Auto-populate overall_score as the mean of metric scores."""
        if self.scores:
            self.overall_score = sum(s.score for s in self.scores) / len(self.scores)
        return self


class MetricSummary(BaseModel):
    """Per-metric statistics for an evaluation run."""

    model_config = ConfigDict(frozen=False)

    average_score: float = Field(ge=0.0, le=1.0)
    pass_rate: float = Field(ge=0.0, le=1.0)
    min_score: float = Field(ge=0.0, le=1.0)
    max_score: float = Field(ge=0.0, le=1.0)
    total_count: int
    failed_count: int


class FailureGroup(BaseModel):
    """A cluster of evaluation failures sharing the same failure type."""

    model_config = ConfigDict(frozen=False)

    failure_type: str
    count: int
    average_score: float = Field(default=0.0, ge=0.0, le=1.0)
    examples: list[EvaluationResult]
    suggestion: str
    severity: str = "warning"
    patterns: list[str] = Field(default_factory=list)
    llm_analysis: str | None = None


class ReportSummary(BaseModel):
    """High-level statistics for an evaluation run."""

    model_config = ConfigDict(frozen=False)

    total_cases: int
    passed: int
    failed: int
    average_score: float = Field(ge=0.0, le=1.0)
    score_by_metric: dict[str, float]
    metric_summaries: dict[str, MetricSummary] = Field(default_factory=dict)


class EvaluationReport(BaseModel):
    """Complete report produced after running an evaluation suite."""

    model_config = ConfigDict(frozen=False)

    suite_name: str
    results: list[EvaluationResult]
    summary: ReportSummary
    failures: list[FailureGroup]

    def print_report(self) -> None:
        """Print a formatted summary to stdout."""
        print(f"\n{'=' * 60}")
        print(f"Suite:         {self.suite_name}")
        print(f"Total cases:   {self.summary.total_cases}")
        print(f"Passed:        {self.summary.passed}")
        print(f"Failed:        {self.summary.failed}")
        print(f"Average score: {self.summary.average_score:.2%}")
        print("\nScore by metric:")
        for name, value in self.summary.score_by_metric.items():
            ms = self.summary.metric_summaries.get(name)
            if ms:
                suffix = f"  ({ms.total_count - ms.failed_count}/{ms.total_count} passed)"
                if ms.failed_count > 0 and ms.failed_count / ms.total_count > 0.5:
                    suffix += "  [!]"
            else:
                suffix = ""
            print(f"  {name:<30} {value:.2%}{suffix}")
        if self.failures:
            print(f"\nSuggestions ({len(self.failures)} groups):")
            for fg in self.failures:
                severity_label = fg.severity.upper()
                print(f"\n  [{fg.failure_type}] {severity_label} ({fg.count} failures, avg: {fg.average_score:.2f})")
                print(f"    {fg.suggestion}")
                if fg.patterns:
                    print("    Patterns detected:")
                    for p in fg.patterns:
                        print(f"      - {p}")
                if fg.llm_analysis:
                    print("    LLM Analysis:")
                    for line in fg.llm_analysis.split("\n"):
                        print(f"      {line}")
        else:
            print("\nNo failures detected.")
