"""Tests for run directories, the manifest, and `ablate report`.

Re-analysis is the cheapest useful thing the tool does — zero API calls — so
it has to work on runs written by an earlier invocation, after the config file
on disk has moved on.
"""

from __future__ import annotations

from pathlib import Path

import pytest
from click.testing import CliRunner

from ablate.cli import main
from ablate.errors import AblateError
from ablate.record import AssertionRecord, TelemetryRecord, TrialRecord
from ablate.store.log import TrialLog
from ablate.store.run import RunManifest, VariantRef, list_runs, resolve_run


def _record(task: str, arm: str, replicate: int, passed: bool) -> TrialRecord:
    return TrialRecord(
        trial_id=f"{task}-{arm}-{replicate}",
        run_id="2026-07-27T10-00-00Z",
        task_id=task,
        variant_id=arm,
        variant_hash="sha256:abc",
        replicate=replicate,
        agent="claude-code",
        agent_version="2.1.220",
        model="claude-sonnet-5",
        started_at="2026-07-27T10:00:00Z",
        duration_s=1.0,
        telemetry=TelemetryRecord(input_tokens=1000, turns=5, cost_usd=0.3),
        assertions=[AssertionRecord(id="a", type="exit_code", passed=passed)],
        passed=passed,
    )


def _write_run(repo: Path, run_id: str, n_tasks: int = 10) -> Path:
    run_dir = repo / ".ablate" / "runs" / run_id
    log = TrialLog(run_dir)
    for i in range(n_tasks):
        for r in range(5):
            log.append(_record(f"task-{i:02d}", "A", r, r < 4))
            log.append(_record(f"task-{i:02d}", "B", r, r < 2))

    RunManifest(
        run_id=run_id,
        command="ablate compare CLAUDE.md /dev/null --trials 5",
        agent="claude-code",
        agent_version="2.1.220",
        model="claude-sonnet-5",
        variants=[
            VariantRef(id="A", source="CLAUDE.md", text="# Conventions\n\nUse snake_case.\n"),
            VariantRef(id="B", source="/dev/null", text=None),
        ],
        n_tasks=n_tasks,
        n_trials=5,
        seed=7,
    ).save(run_dir)
    return run_dir


# --- manifest ---------------------------------------------------------------


def test_manifest_round_trips(tmp_path: Path):
    run_dir = _write_run(tmp_path, "2026-07-27T10-00-00Z")
    manifest = RunManifest.load(run_dir)
    assert manifest.run_id == "2026-07-27T10-00-00Z"
    assert manifest.n_tasks == 10
    assert manifest.seed == 7
    assert [v.id for v in manifest.variants] == ["A", "B"]


def test_manifest_preserves_config_text_for_later_reanalysis():
    """The config on disk will have been edited by the time anyone re-reads
    the run — that is the point of running this."""
    manifest = RunManifest(
        run_id="r", command="c", agent="claude-code", agent_version=None, model=None,
        variants=[VariantRef(id="A", source="CLAUDE.md", text="original text")],
        n_tasks=1, n_trials=1,
    )
    assert manifest.to_variants()[0].text == "original text"


def test_missing_manifest_explains_itself(tmp_path: Path):
    (tmp_path / "empty").mkdir()
    with pytest.raises(AblateError, match="no manifest"):
        RunManifest.load(tmp_path / "empty")


# --- discovery --------------------------------------------------------------


def test_runs_list_is_chronological(tmp_path: Path):
    _write_run(tmp_path, "2026-07-27T12-00-00Z", n_tasks=1)
    _write_run(tmp_path, "2026-07-27T10-00-00Z", n_tasks=1)
    assert [d.name for d in list_runs(tmp_path)] == [
        "2026-07-27T10-00-00Z",
        "2026-07-27T12-00-00Z",
    ]


def test_resolve_defaults_to_the_most_recent(tmp_path: Path):
    _write_run(tmp_path, "2026-07-27T10-00-00Z", n_tasks=1)
    _write_run(tmp_path, "2026-07-27T12-00-00Z", n_tasks=1)
    assert resolve_run(tmp_path, None).name == "2026-07-27T12-00-00Z"


def test_resolve_by_id(tmp_path: Path):
    _write_run(tmp_path, "2026-07-27T10-00-00Z", n_tasks=1)
    _write_run(tmp_path, "2026-07-27T12-00-00Z", n_tasks=1)
    assert resolve_run(tmp_path, "2026-07-27T10-00-00Z").name == "2026-07-27T10-00-00Z"


def test_resolve_unknown_run_lists_what_exists(tmp_path: Path):
    _write_run(tmp_path, "2026-07-27T10-00-00Z", n_tasks=1)
    with pytest.raises(AblateError, match="2026-07-27T10-00-00Z"):
        resolve_run(tmp_path, "nope")


def test_resolve_with_no_runs_points_at_compare(tmp_path: Path):
    with pytest.raises(AblateError, match="ablate compare"):
        resolve_run(tmp_path, None)


# --- the command ------------------------------------------------------------


def test_report_reanalyzes_a_stored_run(git_repo: Path, monkeypatch):
    _write_run(git_repo, "2026-07-27T10-00-00Z")
    monkeypatch.chdir(git_repo)

    result = CliRunner().invoke(main, ["report", "--last"])

    assert result.exit_code == 0, result.output
    assert "PASS RATE" in result.output
    assert "95% CI" in result.output
    assert "CLAUDE.md" in result.output


def _pass_rate_block(output: str) -> str:
    """Just the PASS RATE section — the other metrics have their own verdicts."""
    start = output.index("PASS RATE")
    return output[start : output.index("TOKENS PER TURN", start)]


def test_report_margin_can_flip_the_verdict(git_repo: Path, monkeypatch):
    """The whole point of free re-analysis: re-decide from data already paid for."""
    _write_run(git_repo, "2026-07-27T10-00-00Z")
    monkeypatch.chdir(git_repo)

    tight = CliRunner().invoke(main, ["report", "--last", "--margin", "0.01"])
    loose = CliRunner().invoke(main, ["report", "--last", "--margin", "0.90"])

    assert tight.exit_code == 0 and loose.exit_code == 0
    assert "EQUIVALENT" in _pass_rate_block(loose.output)
    assert "EQUIVALENT" not in _pass_rate_block(tight.output)


def test_report_counts_and_discloses_repeat_analyses(git_repo: Path, monkeypatch):
    """Peeking guard: re-analysis is free, so the count is printed."""
    _write_run(git_repo, "2026-07-27T10-00-00Z")
    monkeypatch.chdir(git_repo)

    runner = CliRunner()
    runner.invoke(main, ["report", "--last"])
    runner.invoke(main, ["report", "--last"])
    third = runner.invoke(main, ["report", "--last"])

    assert "Analyses run against this dataset: 4" in third.output
    assert "not protected by the stated" in third.output


def test_report_makes_no_api_calls(git_repo: Path, monkeypatch):
    """`ablate report` must never touch the agent. If it did, "zero cost"
    would be a lie and CI would bill for a re-read."""
    _write_run(git_repo, "2026-07-27T10-00-00Z")
    monkeypatch.chdir(git_repo)

    import subprocess as sp

    def explode(*a, **kw):
        raise AssertionError("report shelled out to a subprocess")

    monkeypatch.setattr(sp, "run", explode)
    monkeypatch.setattr(sp, "Popen", explode)

    # _repo_root uses git, so resolve the repo before the guard bites.
    result = CliRunner().invoke(main, ["report", str(git_repo / ".ablate" / "runs" / "2026-07-27T10-00-00Z")])
    assert "report shelled out" not in result.output


def test_report_lists_stored_runs(git_repo: Path, monkeypatch):
    _write_run(git_repo, "2026-07-27T10-00-00Z", n_tasks=1)
    _write_run(git_repo, "2026-07-27T12-00-00Z", n_tasks=1)
    monkeypatch.chdir(git_repo)

    result = CliRunner().invoke(main, ["report", "--list"])

    assert result.exit_code == 0
    assert "2026-07-27T10-00-00Z" in result.output
    assert "2026-07-27T12-00-00Z" in result.output


def test_report_without_any_runs_is_a_clear_error(git_repo: Path, monkeypatch):
    monkeypatch.chdir(git_repo)
    result = CliRunner().invoke(main, ["report", "--last"])
    assert result.exit_code == 1
    assert "ablate compare" in result.output
