"""Core evaluation types for AgentByte."""

from __future__ import annotations

from typing import Any, Literal

from pydantic import BaseModel, ConfigDict, Field

from agentbyte.messages import Message, Usage

AnswerStrategy = Literal[
    "last_non_empty",
    "last_assistant",
    "last_content",
    "all_assistant",
]


class ExpectedToolCall(BaseModel):
    """Expected tool call metadata used by evaluation tasks and checks."""

    model_config = ConfigDict(frozen=True)

    name: str = Field(..., description="Tool or function name")
    arguments: dict[str, Any] | None = Field(
        default=None,
        description="Expected arguments. None means arguments are not checked.",
    )


class EvalTask(BaseModel):
    """A single evaluation task."""

    model_config = ConfigDict(frozen=True)

    name: str = Field(..., description="Human-readable task identifier")
    input: str = Field(..., description="Prompt or task input")
    expected_output: str | None = Field(
        default=None,
        description="Reference output for reference-based evaluation",
    )
    expected_tool_calls: list[ExpectedToolCall] | None = Field(
        default=None,
        description="Expected tool calls for tool-use verification",
    )
    metadata: dict[str, Any] = Field(
        default_factory=dict,
        description="Additional task metadata",
    )


class EvalTrajectory(BaseModel):
    """Complete runtime trajectory for one evaluation task."""

    model_config = ConfigDict(frozen=True)

    task: EvalTask = Field(..., description="Task that was executed")
    messages: list[Message] = Field(..., description="Full message history")
    success: bool = Field(..., description="Whether execution succeeded")
    error: str | None = Field(default=None, description="Error details if failed")
    usage: Usage | None = Field(
        default=None,
        description="Usage statistics for the execution",
    )
    metadata: dict[str, Any] = Field(
        default_factory=dict,
        description="Additional execution metadata",
    )


class EvalScore(BaseModel):
    """Evaluation score with per-dimension reasoning."""

    model_config = ConfigDict(frozen=True)

    overall: float = Field(..., description="Overall score on a 0-10 scale")
    dimensions: dict[str, float] = Field(
        default_factory=dict,
        description="Per-dimension scores",
    )
    reasoning: dict[str, str] = Field(
        default_factory=dict,
        description="Per-dimension reasoning strings",
    )
    trajectory: EvalTrajectory | None = Field(
        default=None,
        description="Trajectory that produced this score",
    )
    metadata: dict[str, Any] = Field(
        default_factory=dict,
        description="Additional judge metadata",
    )

    def get_final_response(self) -> str:
        """Extract the final message content from the scored trajectory."""
        if self.trajectory is None:
            return "EXECUTION FAILED: No trajectory"
        if not self.trajectory.success:
            return f"EXECUTION FAILED: {self.trajectory.error or 'Unknown error'}"
        if not self.trajectory.messages:
            return "EXECUTION FAILED: No messages"
        return self.trajectory.messages[-1].content

    def get_full_conversation(self) -> str:
        """Return the full conversation as a formatted string."""
        if self.trajectory is None:
            return "EXECUTION FAILED: No trajectory"
        if not self.trajectory.messages:
            return (
                f"EXECUTION FAILED: {self.trajectory.error or 'No messages'}"
                if not self.trajectory.success
                else "EXECUTION FAILED: No messages"
            )
        return "\n".join(str(message) for message in self.trajectory.messages)


class EvalResult(BaseModel):
    """Bundled evaluation result record for one task, trajectory, and score."""

    model_config = ConfigDict(frozen=True)

    task: EvalTask = Field(..., description="Evaluated task")
    trajectory: EvalTrajectory = Field(..., description="Execution trajectory")
    score: EvalScore = Field(..., description="Evaluation score")
    judge_name: str = Field(..., description="Judge that produced the score")


class PairwiseResult(BaseModel):
    """Outcome of a pairwise comparison between two trajectories for the same task."""

    model_config = ConfigDict(frozen=True)

    task: EvalTask = Field(..., description="Task both trajectories were evaluated on")
    winner: Literal["a", "b", "tie"] = Field(
        ..., description="Which trajectory was better, or tie"
    )
    margin: float = Field(
        ...,
        ge=0.0,
        le=1.0,
        description="How decisive the win was: 0.0 (barely) to 1.0 (clear); always 0.0 for ties",
    )
    reasoning: str = Field(..., description="Judge's explanation of the decision")
    trajectory_a: EvalTrajectory = Field(..., description="First trajectory (configuration A)")
    trajectory_b: EvalTrajectory = Field(..., description="Second trajectory (configuration B)")
    metadata: dict[str, Any] = Field(default_factory=dict, description="Judge metadata")

    @property
    def is_tie(self) -> bool:
        """Whether the comparison ended in a tie."""
        return self.winner == "tie"


__all__ = [
    "AnswerStrategy",
    "ExpectedToolCall",
    "EvalTask",
    "EvalTrajectory",
    "EvalScore",
    "EvalResult",
    "PairwiseResult",
]
