"""Tests for deterministic evaluation helpers."""

from __future__ import annotations

from dataclasses import dataclass
from typing import Any

import pandas as pd

from src.evals import (
    aggregate_judge_results,
    aggregate_metrics,
    build_results_dataframe,
    compare_systems,
    judge_responses,
    load_eval_dataset,
    run_eval_examples,
    score_prediction,
)
from src.schemas import JudgeResult, SystemOutput


@dataclass
class FakeJudgeWrapper:
    """Return precomputed judge results."""

    outputs: list[JudgeResult]

    def invoke(self, _: list[tuple[str, str]]) -> JudgeResult:
        """Return the next judge result."""
        return self.outputs.pop(0)


@dataclass
class FakeJudgeLLM:
    """Mimic the structured-output surface for judge evaluation."""

    outputs: list[JudgeResult]

    def with_structured_output(self, _: Any) -> FakeJudgeWrapper:
        """Return the fake judge wrapper."""
        return FakeJudgeWrapper(self.outputs)


def _runner(_: str, metadata: dict[str, Any]) -> SystemOutput:
    """Return a predictable structured output for evaluation tests."""
    return SystemOutput(
        ticket_id=metadata["ticket_id"],
        intent="refund_request",
        route="general",
        should_escalate=False,
        response="Please share the order ID so I can help.",
        used_tools=["search_kb"],
        tool_call_count=1,
    )


def test_load_eval_dataset_reads_examples() -> None:
    """Load the local JSONL dataset into validated models."""
    examples = load_eval_dataset("data/eval_dataset.jsonl")

    assert len(examples) == 20
    assert examples[0].id == "ticket-001"


def test_score_prediction_marks_expected_fields() -> None:
    """Score a prediction against one evaluation example."""
    example = load_eval_dataset("data/eval_dataset.jsonl")[10]
    prediction = _runner(example.input, example.metadata.model_dump())

    scores = score_prediction(example, prediction)

    assert scores["intent_correct"] is True
    assert scores["response_mentions_missing_info_when_needed"] is True


def test_run_eval_examples_and_aggregate_metrics() -> None:
    """Run evaluation examples and aggregate deterministic metrics."""
    examples = load_eval_dataset("data/eval_dataset.jsonl")[:3]

    results = run_eval_examples("workflow", _runner, examples)
    metrics = aggregate_metrics(results)

    assert len(results) == 3
    assert metrics["n_examples"] == 3
    assert "avg_tool_calls" in metrics


def test_build_results_dataframe_and_compare_systems() -> None:
    """Build per-system frames and join them side by side."""
    examples = load_eval_dataset("data/eval_dataset.jsonl")[:2]
    workflow_results = run_eval_examples("workflow", _runner, examples)
    agent_results = run_eval_examples("agent", _runner, examples)

    frame = build_results_dataframe(workflow_results)
    comparison = compare_systems(workflow_results, agent_results)

    assert isinstance(frame, pd.DataFrame)
    assert "predicted_intent" in frame.columns
    assert len(comparison) == 2


def test_judge_responses_and_aggregate(monkeypatch) -> None:
    """Run optional judge scoring and aggregate the returned scores."""
    examples = load_eval_dataset("data/eval_dataset.jsonl")[:2]
    results = run_eval_examples("workflow", _runner, examples)
    fake_judge_results = [
        JudgeResult(
            helpfulness_score=4,
            correctness_score=4,
            policy_alignment_score=5,
            overall_pass=True,
            rationale="Solid answer.",
        ),
        JudgeResult(
            helpfulness_score=3,
            correctness_score=4,
            policy_alignment_score=4,
            overall_pass=True,
            rationale="Good enough.",
        ),
    ]
    monkeypatch.setattr(
        "src.evals.build_langchain_llm",
        lambda temperature=0, deployment_name=None: FakeJudgeLLM(fake_judge_results),
    )

    judged = judge_responses(results)
    aggregate = aggregate_judge_results(judged)

    assert len(judged) == 2
    assert aggregate["pass_rate"] == 1.0
