"""Eval API endpoints — reads from existing SQLite eval storage."""

from __future__ import annotations

import json
import sqlite3
from pathlib import Path
from typing import Any

from aiohttp import web

from hatchdx.utils.workspace import discover_server_eval_dbs

eval_routes = web.RouteTableDef()

DB_FILENAME = ".hdx/eval_results.db"


def _get_db_path() -> Path:
    return Path.cwd() / DB_FILENAME


def _get_conn() -> sqlite3.Connection | None:
    """Return a read-only connection to the eval DB, or None if it doesn't exist."""
    db_path = _get_db_path()
    if not db_path.exists():
        return None
    conn = sqlite3.connect(str(db_path), timeout=5)
    conn.row_factory = sqlite3.Row
    return conn


def _get_all_server_eval_dbs(workspace_root: Path | None) -> list[tuple[str, Path]]:
    """Return (server_name, db_path) pairs for all server eval DBs.

    In workspace mode, discovers all ``servers/*/.hdx/eval_results.db``.
    Otherwise, returns the single CWD-based DB (if it exists).
    """
    if workspace_root is not None:
        results: list[tuple[str, Path]] = []
        for db_path in discover_server_eval_dbs(workspace_root):
            # db_path is like workspace/servers/my-server/.hdx/eval_results.db
            server_name = db_path.parent.parent.name
            results.append((server_name, db_path))
        return results

    # Non-workspace: single CWD DB
    db_path = _get_db_path()
    if db_path.exists():
        return [("default", db_path)]
    return []


def _connect(db_path: Path) -> sqlite3.Connection:
    conn = sqlite3.connect(str(db_path), timeout=5)
    conn.row_factory = sqlite3.Row
    return conn


def _get_recent_runs(
    limit: int = 10, workspace_root: Path | None = None
) -> list[dict[str, Any]]:
    """Get recent eval runs with summary stats. Used by overview endpoint too."""
    all_runs: list[dict[str, Any]] = []

    for server_name, db_path in _get_all_server_eval_dbs(workspace_root):
        conn = _connect(db_path)
        try:
            cursor = conn.execute(
                """
                SELECT
                    r.run_id,
                    r.started_at,
                    r.finished_at,
                    COUNT(e.id) as total_cases,
                    SUM(CASE WHEN e.passed = 1 THEN 1 ELSE 0 END) as passed,
                    SUM(CASE WHEN e.passed = 0 THEN 1 ELSE 0 END) as failed
                FROM eval_runs r
                LEFT JOIN eval_results e ON r.run_id = e.run_id
                GROUP BY r.run_id
                ORDER BY r.started_at DESC
                LIMIT ?
                """,
                (limit,),
            )
            for row in cursor.fetchall():
                all_runs.append({
                    "run_id": row["run_id"],
                    "started_at": row["started_at"],
                    "finished_at": row["finished_at"],
                    "total_cases": row["total_cases"],
                    "passed": row["passed"] or 0,
                    "failed": row["failed"] or 0,
                    "source": "server",
                    "server_name": server_name,
                })
        finally:
            conn.close()

    all_runs.sort(key=lambda r: r.get("started_at", ""), reverse=True)
    return all_runs[:limit]


def _get_agent_eval_runs(limit: int, offset: int) -> tuple[list[dict[str, Any]], int]:
    """Fetch agent eval runs from the agent eval DB."""
    agent_db_path = Path.cwd() / ".hdx/agent_evals.db"
    if not agent_db_path.exists():
        return [], 0

    conn = sqlite3.connect(str(agent_db_path), timeout=5)
    conn.row_factory = sqlite3.Row
    try:
        count_row = conn.execute("SELECT COUNT(*) FROM eval_runs").fetchone()
        total = count_row[0] if count_row else 0

        cursor = conn.execute(
            """
            SELECT
                id, agent_name, suite_name, started_at, completed_at,
                total_cases, passed, failed
            FROM eval_runs
            ORDER BY started_at DESC
            LIMIT ? OFFSET ?
            """,
            (limit, offset),
        )

        runs = []
        for row in cursor.fetchall():
            runs.append({
                "run_id": str(row["id"]),
                "started_at": row["started_at"],
                "finished_at": row["completed_at"],
                "total_cases": row["total_cases"],
                "passed": row["passed"] or 0,
                "failed": row["failed"] or 0,
                "suites": [row["suite_name"]] if row["suite_name"] else [],
                "source": "agent",
                "agent_name": row["agent_name"],
            })
        return runs, total
    finally:
        conn.close()


def _query_server_runs(
    workspace_root: Path | None,
    limit: int,
    offset: int,
    suite: str | None = None,
) -> tuple[list[dict[str, Any]], int]:
    """Query server eval runs across all DBs (workspace-aware)."""
    server_runs: list[dict[str, Any]] = []
    server_total = 0

    for server_name, db_path in _get_all_server_eval_dbs(workspace_root):
        conn = _connect(db_path)
        try:
            where = ""
            params: list[Any] = []
            if suite:
                where = "HAVING GROUP_CONCAT(DISTINCT e.suite_name) LIKE ?"
                params.append(f"%{suite}%")

            count_cursor = conn.execute(
                f"""
                SELECT COUNT(*) FROM (
                    SELECT r.run_id
                    FROM eval_runs r
                    LEFT JOIN eval_results e ON r.run_id = e.run_id
                    GROUP BY r.run_id
                    {where}
                )
                """,
                params,
            )
            server_total += count_cursor.fetchone()[0]

            cursor = conn.execute(
                f"""
                SELECT
                    r.run_id,
                    r.started_at,
                    r.finished_at,
                    COUNT(e.id) as total_cases,
                    SUM(CASE WHEN e.passed = 1 THEN 1 ELSE 0 END) as passed,
                    SUM(CASE WHEN e.passed = 0 THEN 1 ELSE 0 END) as failed,
                    GROUP_CONCAT(DISTINCT e.suite_name) as suites
                FROM eval_runs r
                LEFT JOIN eval_results e ON r.run_id = e.run_id
                GROUP BY r.run_id
                {where}
                ORDER BY r.started_at DESC
                """,
                params,
            )

            for row in cursor.fetchall():
                suites_str = row["suites"] or ""
                server_runs.append({
                    "run_id": row["run_id"],
                    "started_at": row["started_at"],
                    "finished_at": row["finished_at"],
                    "total_cases": row["total_cases"],
                    "passed": row["passed"] or 0,
                    "failed": row["failed"] or 0,
                    "suites": [s for s in suites_str.split(",") if s],
                    "source": "server",
                    "server_name": server_name,
                })
        finally:
            conn.close()

    # Sort merged results and apply pagination
    server_runs.sort(key=lambda r: r.get("started_at", ""), reverse=True)
    return server_runs[offset : offset + limit], server_total


@eval_routes.get("/api/eval/runs")
async def list_runs(request: web.Request) -> web.Response:
    limit = int(request.query.get("limit", "50"))
    offset = int(request.query.get("offset", "0"))
    suite = request.query.get("suite")
    source = request.query.get("source", "all")
    workspace_root: Path | None = request.app.get("workspace_root")

    # Agent-only: return from agent eval DB
    if source == "agent":
        agent_runs, agent_total = _get_agent_eval_runs(limit, offset)
        return web.json_response({"runs": agent_runs, "total": agent_total})

    # Server runs (workspace-aware)
    server_runs, server_total = _query_server_runs(workspace_root, limit, offset, suite)

    # Server-only: return just server runs
    if source == "server":
        return web.json_response({"runs": server_runs, "total": server_total})

    # All: fetch both, merge and sort by date, trim to limit
    agent_runs, agent_total = _get_agent_eval_runs(limit, offset)
    all_runs = server_runs + agent_runs
    all_runs.sort(key=lambda r: r.get("started_at", ""), reverse=True)
    all_runs = all_runs[:limit]

    return web.json_response({
        "runs": all_runs,
        "total": server_total + agent_total,
    })


def _find_run_conn(
    run_id: str, workspace_root: Path | None
) -> sqlite3.Connection | None:
    """Find the DB connection containing the given run_id."""
    for _name, db_path in _get_all_server_eval_dbs(workspace_root):
        conn = _connect(db_path)
        row = conn.execute(
            "SELECT 1 FROM eval_runs WHERE run_id = ?", (run_id,)
        ).fetchone()
        if row is not None:
            return conn
        conn.close()
    return None


@eval_routes.get("/api/eval/runs/{run_id}")
async def get_run(request: web.Request) -> web.Response:
    run_id = request.match_info["run_id"]
    workspace_root: Path | None = request.app.get("workspace_root")

    conn = _find_run_conn(run_id, workspace_root)
    if conn is None:
        return web.json_response({"error": f"Run {run_id} not found"}, status=404)

    try:
        cursor = conn.execute(
            "SELECT run_id, started_at, finished_at FROM eval_runs WHERE run_id = ?",
            (run_id,),
        )
        row = cursor.fetchone()

        # Get summary stats
        stats = conn.execute(
            """
            SELECT
                COUNT(*) as total,
                SUM(CASE WHEN passed = 1 THEN 1 ELSE 0 END) as passed,
                SUM(CASE WHEN passed = 0 THEN 1 ELSE 0 END) as failed,
                AVG(latency_ms) as avg_latency,
                GROUP_CONCAT(DISTINCT suite_name) as suites
            FROM eval_results WHERE run_id = ?
            """,
            (run_id,),
        ).fetchone()

        suites_str = stats["suites"] or ""
        return web.json_response({
            "run_id": row["run_id"],
            "started_at": row["started_at"],
            "finished_at": row["finished_at"],
            "total_cases": stats["total"],
            "passed": stats["passed"] or 0,
            "failed": stats["failed"] or 0,
            "avg_latency_ms": round(stats["avg_latency"] or 0, 1),
            "suites": [s for s in suites_str.split(",") if s],
        })
    finally:
        conn.close()


@eval_routes.get("/api/eval/runs/{run_id}/cases")
async def get_run_cases(request: web.Request) -> web.Response:
    run_id = request.match_info["run_id"]
    workspace_root: Path | None = request.app.get("workspace_root")

    conn = _find_run_conn(run_id, workspace_root)
    if conn is None:
        return web.json_response({"run_id": run_id, "cases": []})

    try:
        cursor = conn.execute(
            """
            SELECT id, suite_name, eval_name, passed, latency_ms, timestamp, assertion_details
            FROM eval_results
            WHERE run_id = ?
            ORDER BY suite_name, eval_name
            """,
            (run_id,),
        )

        cases = []
        for row in cursor.fetchall():
            cases.append({
                "id": row["id"],
                "suite_name": row["suite_name"],
                "eval_name": row["eval_name"],
                "passed": bool(row["passed"]),
                "latency_ms": row["latency_ms"],
                "timestamp": row["timestamp"],
                "assertion_details": json.loads(row["assertion_details"]),
            })

        return web.json_response({"run_id": run_id, "cases": cases})
    finally:
        conn.close()


@eval_routes.get("/api/eval/compare/{id1}/{id2}")
async def compare_runs(request: web.Request) -> web.Response:
    id1 = request.match_info["id1"]
    id2 = request.match_info["id2"]
    workspace_root: Path | None = request.app.get("workspace_root")

    conn_a = _find_run_conn(id1, workspace_root)
    if conn_a is None:
        return web.json_response({"error": f"Run {id1} not found"}, status=404)

    conn_b = _find_run_conn(id2, workspace_root)
    if conn_b is None:
        conn_a.close()
        return web.json_response({"error": f"Run {id2} not found"}, status=404)

    try:
        def _get_results(conn: sqlite3.Connection, rid: str) -> dict[str, dict[str, Any]]:
            cursor = conn.execute(
                """
                SELECT eval_name, suite_name, passed, latency_ms, assertion_details
                FROM eval_results WHERE run_id = ?
                """,
                (rid,),
            )
            return {
                row["eval_name"]: {
                    "suite_name": row["suite_name"],
                    "eval_name": row["eval_name"],
                    "passed": bool(row["passed"]),
                    "latency_ms": row["latency_ms"],
                    "assertion_details": json.loads(row["assertion_details"]),
                }
                for row in cursor.fetchall()
            }

        results_a = _get_results(conn_a, id1)
        results_b = _get_results(conn_b, id2)

        all_names = set(results_a) | set(results_b)
        regressions = []
        fixes = []
        unchanged = []

        for name in sorted(all_names):
            a = results_a.get(name)
            b = results_b.get(name)
            if a and b:
                if a["passed"] and not b["passed"]:
                    regressions.append({"eval_name": name, "run_a": a, "run_b": b})
                elif not a["passed"] and b["passed"]:
                    fixes.append({"eval_name": name, "run_a": a, "run_b": b})
                else:
                    unchanged.append({"eval_name": name, "run_a": a, "run_b": b})

        return web.json_response({
            "run_a": id1,
            "run_b": id2,
            "regressions": regressions,
            "fixes": fixes,
            "unchanged": unchanged,
        })
    finally:
        conn_a.close()
        if conn_b is not conn_a:
            conn_b.close()


@eval_routes.get("/api/eval/trends")
async def get_trends(request: web.Request) -> web.Response:
    days = int(request.query.get("days", "30"))
    workspace_root: Path | None = request.app.get("workspace_root")

    all_trends: list[dict[str, Any]] = []

    for _server_name, db_path in _get_all_server_eval_dbs(workspace_root):
        conn = _connect(db_path)
        try:
            cursor = conn.execute(
                """
                SELECT
                    DATE(r.started_at) as date,
                    e.suite_name,
                    COUNT(*) as total,
                    SUM(CASE WHEN e.passed = 1 THEN 1 ELSE 0 END) as passed,
                    AVG(e.latency_ms) as avg_latency
                FROM eval_results e
                JOIN eval_runs r ON e.run_id = r.run_id
                WHERE r.started_at >= datetime('now', ?)
                GROUP BY DATE(r.started_at), e.suite_name
                ORDER BY date
                """,
                (f"-{days} days",),
            )

            for row in cursor.fetchall():
                total = row["total"]
                passed = row["passed"] or 0
                all_trends.append({
                    "date": row["date"],
                    "suite": row["suite_name"],
                    "total": total,
                    "passed": passed,
                    "pass_rate": passed / total if total > 0 else 0,
                    "avg_latency_ms": round(row["avg_latency"] or 0, 1),
                })
        finally:
            conn.close()

    all_trends.sort(key=lambda t: t["date"])
    return web.json_response({"trends": all_trends})
