"""Comparison logic for diffing evaluation runs."""

from __future__ import annotations

from typing import Any

from evalkit.server.db.models import EvaluationModel

SCORE_TOLERANCE = 0.05


def _normalize_question(text: str) -> str:
    return text.strip().lower()


def eval_to_comparison_dict(ev: EvaluationModel) -> dict[str, Any]:
    """Convert an EvaluationModel ORM object to the dict format build_comparison expects.
    Shared by both API and HTML routes to avoid duplication."""
    results = []
    for r in ev.results:
        tc = r.get_test_case_dict()
        results.append(
            {
                "question": tc.get("question", ""),
                "answer": tc.get("answer", ""),
                "context": tc.get("context", []),
                "expected_answer": tc.get("expected_answer"),
                "overall_score": r.overall_score,
                "metrics": [
                    {"name": ms.metric_name, "score": ms.score, "passed": bool(ms.passed), "reason": ms.reason}
                    for ms in r.metric_scores
                ],
            }
        )
    return {
        "id": ev.id,
        "suite_name": ev.suite_name,
        "average_score": ev.average_score,
        "created_at": ev.created_at.isoformat() if ev.created_at else "",
        "tags": {t.key: t.value for t in ev.tags},
        "results": results,
    }


def build_comparison(
    baseline: dict[str, Any],
    comparisons: list[dict[str, Any]],
) -> dict[str, Any]:
    # Index baseline results by normalized question
    baseline_by_q: dict[str, dict] = {}
    for r in baseline["results"]:
        key = _normalize_question(r["question"])
        baseline_by_q[key] = r

    # Index comparison results by normalized question
    comp_by_q: list[dict[str, dict]] = []
    for comp in comparisons:
        idx: dict[str, dict] = {}
        for r in comp["results"]:
            idx[_normalize_question(r["question"])] = r
        comp_by_q.append(idx)

    # Collect all questions from all evaluations
    all_questions_normalized: dict[str, str] = {}
    for r in baseline["results"]:
        nq = _normalize_question(r["question"])
        if nq not in all_questions_normalized:
            all_questions_normalized[nq] = r["question"]
    for comp in comparisons:
        for r in comp["results"]:
            nq = _normalize_question(r["question"])
            if nq not in all_questions_normalized:
                all_questions_normalized[nq] = r["question"]

    questions = []
    new_questions = []
    improved = 0
    regressed = 0
    unchanged = 0

    for nq, display_question in all_questions_normalized.items():
        baseline_result = baseline_by_q.get(nq)

        if baseline_result is None:
            # Question exists in comparison(s) but not baseline
            new_questions.append(
                {
                    "question": display_question,
                    "comparison_results": [
                        _build_comp_result(comp_idx.get(nq), None, comp["id"])
                        for comp, comp_idx in zip(comparisons, comp_by_q)
                    ],
                }
            )
            continue

        comparison_results = []
        for comp, comp_idx in zip(comparisons, comp_by_q):
            comp_result = comp_idx.get(nq)
            comparison_results.append(_build_comp_result(comp_result, baseline_result, comp["id"]))

        # Count improved/regressed/unchanged across all comparisons
        for cr in comparison_results:
            if cr is not None:
                delta = cr["score_delta"]
                if delta > SCORE_TOLERANCE:
                    improved += 1
                elif delta < -SCORE_TOLERANCE:
                    regressed += 1
                else:
                    unchanged += 1

        questions.append(
            {
                "question": display_question,
                "baseline_result": baseline_result,
                "comparison_results": comparison_results,
            }
        )

    return {
        "baseline": {k: v for k, v in baseline.items() if k != "results"},
        "comparisons": [{k: v for k, v in c.items() if k != "results"} for c in comparisons],
        "questions": questions,
        "new_questions": new_questions,
        "summary": {
            "improved": improved,
            "regressed": regressed,
            "unchanged": unchanged,
        },
    }


def _build_comp_result(
    comp_result: dict[str, Any] | None, baseline_result: dict[str, Any] | None, eval_id: int
) -> dict[str, Any] | None:
    if comp_result is None:
        return None

    baseline_score = baseline_result["overall_score"] if baseline_result else 0.0
    score_delta = comp_result["overall_score"] - baseline_score

    # Build per-metric deltas
    baseline_metrics = {}
    if baseline_result:
        baseline_metrics = {m["name"]: m["score"] for m in baseline_result.get("metrics", [])}

    metrics_with_delta = []
    for m in comp_result.get("metrics", []):
        base_metric_score = baseline_metrics.get(m["name"], 0.0)
        metrics_with_delta.append(
            {
                **m,
                "score_delta": m["score"] - base_metric_score,
            }
        )

    return {
        "evaluation_id": eval_id,
        "answer": comp_result["answer"],
        "context": comp_result["context"],
        "expected_answer": comp_result.get("expected_answer"),
        "overall_score": comp_result["overall_score"],
        "score_delta": score_delta,
        "metrics": metrics_with_delta,
    }
