"""Deterministic graders over an agent's final result and trace.

All graders are pure and offline — no LLM judging in v1 — so evaluation
results are exactly reproducible for a given agent output.
"""

from __future__ import annotations

import re

from aop.evals.models import EvalCase, GradeCheck, GraderSpec
from aop.orchestrator.models import AgentResult


def run_grader(spec: GraderSpec, result: AgentResult) -> GradeCheck:
    """Apply one grader to an agent result.

    Args:
        spec: The check to apply.
        result: The agent's structured run result (output + trace).

    Returns:
        A :class:`GradeCheck` with the verdict and a short human detail.
    """
    output = result.output if spec.case_sensitive else result.output.casefold()
    needle = spec.value if spec.case_sensitive else spec.value.casefold()

    if spec.type == "contains":
        passed = needle in output
        detail = "" if passed else f"output does not contain {spec.value!r}"
    elif spec.type == "not_contains":
        passed = needle not in output
        detail = "" if passed else f"output contains forbidden {spec.value!r}"
    elif spec.type == "regex":
        flags = 0 if spec.case_sensitive else re.IGNORECASE
        passed = re.search(spec.value, result.output, flags) is not None
        detail = "" if passed else f"pattern {spec.value!r} not found"
    elif spec.type == "tool_called":
        passed = any(
            s.tool_call is not None and s.tool_call.tool == spec.value and s.tool_call.ok
            for s in result.steps
        )
        detail = "" if passed else f"no successful call of tool {spec.value!r} in the trace"
    elif spec.type == "min_words":
        minimum = int(spec.value)
        words = len(result.output.split())
        passed = words >= minimum
        detail = "" if passed else f"output has {words} words, expected >= {minimum}"
    else:  # pragma: no cover - blocked by GraderSpec validation
        passed = False
        detail = f"unknown grader type {spec.type!r}"

    return GradeCheck(type=spec.type, value=spec.value, passed=passed, detail=detail)


def grade_case(case: EvalCase, result: AgentResult) -> tuple[bool, list[GradeCheck]]:
    """Apply all of a case's graders; the case passes only if every check does.

    Args:
        case: The golden task with its checks.
        result: The agent's run result.

    Returns:
        ``(passed, checks)``.
    """
    checks = [run_grader(g, result) for g in case.graders]
    return all(c.passed for c in checks), checks
