"""Eval command: generate a context file and optionally measure its performance via coderace."""

from __future__ import annotations

import json
import shutil
import subprocess
import tempfile
from pathlib import Path
from typing import Any

import typer

from agentmd.analyzer import ProjectAnalyzer
from agentmd.generators import GENERATOR_MAP


# ---------------------------------------------------------------------------
# coderace detection
# ---------------------------------------------------------------------------

def _coderace_available() -> bool:
    """Return True if coderace is available on PATH."""
    return shutil.which("coderace") is not None


# ---------------------------------------------------------------------------
# generation helpers
# ---------------------------------------------------------------------------

def _run_generate(root: Path, *, force: bool = True) -> dict[str, Any]:
    """Run generation for the claude agent and return metadata."""
    analysis = ProjectAnalyzer().analyze(root)
    GeneratorClass = GENERATOR_MAP["claude"]
    gen = GeneratorClass(analysis)
    content = gen.generate()
    output_path = root / gen.output_filename
    output_path.write_text(content, encoding="utf-8")

    file_count = len(analysis.existing_context_files)
    subsystem_count = len(analysis.directory_structure.source_directories or [])

    return {
        "file": gen.output_filename,
        "path": str(output_path),
        "line_count": len(content.splitlines()),
        "file_count": file_count,
        "subsystem_count": subsystem_count,
    }


def _get_existing_context(root: Path) -> dict[str, Any] | None:
    """Return metadata for existing CLAUDE.md if present."""
    claude_md = root / "CLAUDE.md"
    if not claude_md.exists():
        return None
    content = claude_md.read_text(encoding="utf-8")
    return {
        "file": "CLAUDE.md",
        "path": str(claude_md),
        "line_count": len(content.splitlines()),
        "file_count": None,
        "subsystem_count": None,
    }


# ---------------------------------------------------------------------------
# coderace runner
# ---------------------------------------------------------------------------

def _run_coderace(context_path: str) -> dict[str, Any] | None:
    """
    Run coderace context-eval and return parsed results dict, or None on failure.

    Returns dict with keys: with_context, without_context, delta, or error/timeout.
    """
    try:
        result = subprocess.run(
            ["coderace", "context-eval", "--context", context_path, "--json"],
            capture_output=True,
            text=True,
            timeout=600,  # 10 minutes
        )
    except FileNotFoundError:
        return None  # coderace not installed
    except subprocess.TimeoutExpired:
        return {"error": "timeout", "message": "coderace timed out after 10 minutes"}

    if result.returncode != 0:
        return {"error": "nonzero", "message": result.stderr.strip() or "coderace exited non-zero"}

    try:
        data = json.loads(result.stdout)
    except json.JSONDecodeError:
        return {"error": "parse", "message": "coderace output was not valid JSON"}

    # Normalise common coderace JSON shapes:
    # Shape A: {"with_context": N, "without_context": N, ...}
    # Shape B: {"with": {"avg": N, "n": N}, "without": {"avg": N, "n": N}}
    if "with_context" in data:
        with_score = data["with_context"]
        without_score = data["without_context"]
        n_with = data.get("n_with", data.get("n", None))
        n_without = data.get("n_without", data.get("n", None))
    elif "with" in data and "without" in data:
        with_score = data["with"].get("avg", data["with"])
        without_score = data["without"].get("avg", data["without"])
        n_with = data["with"].get("n") if isinstance(data["with"], dict) else None
        n_without = data["without"].get("n") if isinstance(data["without"], dict) else None
    else:
        return {"error": "unexpected_shape", "message": f"unrecognised coderace output: {data}"}

    delta = round(with_score - without_score, 2)
    pct = round((delta / without_score * 100), 1) if without_score else 0.0

    return {
        "with_context": with_score,
        "without_context": without_score,
        "delta": delta,
        "delta_pct": pct,
        "n_with": n_with,
        "n_without": n_without,
        "raw": data,
    }


# ---------------------------------------------------------------------------
# display helpers
# ---------------------------------------------------------------------------

def _print_gen_summary(gen_info: dict[str, Any], *, label: str = "Generated") -> None:
    typer.echo(f"\n{label} context file...")
    lines = gen_info["line_count"]
    fname = gen_info["file"]
    if gen_info.get("file_count") is not None:
        typer.echo(f"  \u2713 Analyzed {gen_info['file_count']} context files across "
                   f"{gen_info['subsystem_count']} source directories")
    typer.echo(f"  \u2713 Generated {fname} ({lines} lines)")


def _print_eval_results(eval_result: dict[str, Any] | None, *, coderace_available: bool) -> None:
    if not coderace_available:
        typer.echo("\nMeasuring performance impact...")
        typer.echo("  Install coderace for performance measurement: `pip install coderace`")
        return

    if eval_result is None:
        typer.echo("\nMeasuring performance impact...")
        typer.echo("  Install coderace for performance measurement: `pip install coderace`")
        return

    if "error" in eval_result:
        err = eval_result["error"]
        msg = eval_result.get("message", "")
        typer.echo("\nMeasuring performance impact...")
        if err == "timeout":
            typer.echo(f"  \u26a0 coderace timed out: {msg}")
        else:
            typer.echo(f"  \u26a0 coderace error: {msg}")
        return

    with_score = eval_result["with_context"]
    without_score = eval_result["without_context"]
    delta = eval_result["delta"]
    pct = eval_result["delta_pct"]
    n_with = eval_result.get("n_with")
    n_without = eval_result.get("n_without")

    n_with_str = f" (n={n_with} tasks)" if n_with is not None else ""
    n_without_str = f" (n={n_without} tasks)" if n_without is not None else ""

    sign = "+" if delta >= 0 else ""

    typer.echo("\nMeasuring performance impact...")
    typer.echo("  Running coderace context-eval (this takes 2-3 minutes)")
    typer.echo("")
    typer.echo("Context File Impact Report")
    typer.echo("\u2501" * 25)
    typer.echo(f"  With context: avg score {with_score}{n_with_str}")
    typer.echo(f"  Without context: avg score {without_score}{n_without_str}")
    typer.echo(f"  Net improvement: {sign}{delta} points ({sign}{pct}%)")
    typer.echo("")
    if delta >= 0:
        typer.echo("  \u2713 Context file improves agent performance")
    else:
        typer.echo("  \u26a0 Context file does not improve agent performance")


# ---------------------------------------------------------------------------
# main eval function (called by CLI command)
# ---------------------------------------------------------------------------

def run_eval(
    root: Path,
    *,
    no_benchmark: bool = False,
    existing: bool = False,
    json_output: bool = False,
) -> None:
    """Orchestrate the eval flow."""

    # --- Step 1: get / generate context file ---
    if existing:
        gen_info = _get_existing_context(root)
        if gen_info is None:
            if json_output:
                typer.echo(json.dumps({"error": "no CLAUDE.md found", "path": str(root)}, indent=2))
            else:
                typer.echo(f"Error: no CLAUDE.md found in {root}", err=True)
            raise typer.Exit(1)
        gen_label = "Using existing"
    else:
        gen_info = _run_generate(root, force=True)
        gen_label = "Generating"

    context_path = gen_info["path"]

    # --- Step 2: optionally run coderace ---
    coderace_ok = _coderace_available()
    eval_result: dict[str, Any] | None = None

    if not no_benchmark and coderace_ok:
        eval_result = _run_coderace(context_path)

    # --- JSON output path ---
    if json_output:
        out: dict[str, Any] = {"generated": gen_info}
        if no_benchmark:
            out["eval"] = None
            out["eval_missing"] = "--no-benchmark flag used"
        elif not coderace_ok:
            out["eval"] = None
            out["eval_missing"] = "coderace not installed"
        elif eval_result is not None and "error" in eval_result:
            out["eval"] = None
            out["eval_missing"] = eval_result.get("message", "coderace error")
        elif eval_result is not None:
            out["eval"] = {
                "with_context": eval_result["with_context"],
                "without_context": eval_result["without_context"],
                "delta": eval_result["delta"],
            }
        else:
            out["eval"] = None
            out["eval_missing"] = "coderace not installed"
        typer.echo(json.dumps(out, indent=2))
        return

    # --- Human-readable output ---
    _print_gen_summary(gen_info, label=gen_label)

    if not no_benchmark:
        _print_eval_results(eval_result, coderace_available=coderace_ok)

    typer.echo(f"\n  Saved: {gen_info['file']}")
