from __future__ import annotations

from pathlib import Path

from click.testing import CliRunner

import ablate.agents.claude_code as claude_code
from ablate.cli import main


def test_help_lists_all_nine_commands():
    result = CliRunner().invoke(main, ["--help"])
    assert result.exit_code == 0
    for cmd in ["init", "compare", "try", "cost", "demo", "report", "ablate", "apply", "mine"]:
        assert cmd in result.output


def test_init_scaffolds_tasks_in_git_repo(git_repo: Path, monkeypatch):
    (git_repo / "pyproject.toml").write_text("[project]\nname='x'\n")
    monkeypatch.chdir(git_repo)

    result = CliRunner().invoke(main, ["init"])

    assert result.exit_code == 0, result.output
    assert "Detected test command" in result.output
    assert (git_repo / ".ablate" / "tasks" / "bug-fix.yaml").exists()


def test_init_outside_git_repo_fails_clearly(tmp_path: Path, monkeypatch):
    monkeypatch.chdir(tmp_path)
    result = CliRunner().invoke(main, ["init"])
    assert result.exit_code != 0


def test_compare_reports_preflight_failure_without_claude_binary(git_repo: Path, monkeypatch):
    (git_repo / "pyproject.toml").write_text("[project]\nname='x'\n")
    monkeypatch.chdir(git_repo)
    CliRunner().invoke(main, ["init"])

    # Force "claude not found" regardless of what's installed on the machine
    # running the test suite -- stripping PATH would also hide `git`, which
    # _repo_root() needs before it ever reaches the adapter.
    monkeypatch.setattr(claude_code.shutil, "which", lambda _: None)

    result = CliRunner().invoke(main, ["compare", "CLAUDE.md", "/dev/null"])

    assert result.exit_code != 0
    assert "preflight failed" in result.output


def test_compare_missing_tasks_dir_errors(git_repo: Path, monkeypatch):
    monkeypatch.chdir(git_repo)
    result = CliRunner().invoke(main, ["compare", "CLAUDE.md", "/dev/null"])
    assert result.exit_code != 0
    assert "ablate init" in result.output


def test_unimplemented_commands_point_to_a_milestone():
    """`mine` is post-v1. It registers so `--help` shows the whole surface,
    and it names its milestone rather than saying "no such command"."""
    result = CliRunner().invoke(main, ["mine"])
    assert result.exit_code != 0
    assert "M5" in result.output


def test_demo_runs_offline_and_labels_itself_synthetic():
    """The first thing anyone runs must cost $0 — and must not imply it
    measured something it did not."""
    result = CliRunner().invoke(main, ["demo"])
    assert result.exit_code == 0, result.output
    assert "SYNTHETIC" in result.output
    assert "PASS RATE" in result.output
    assert "95% CI" in result.output


def test_demo_needs_no_git_repo(tmp_path: Path, monkeypatch):
    """`uvx ablate demo` is first contact — it cannot require a checkout."""
    monkeypatch.chdir(tmp_path)
    assert CliRunner().invoke(main, ["demo"]).exit_code == 0


def test_demo_margin_is_reanalyzable():
    result = CliRunner().invoke(main, ["demo", "--margin", "0.5"])
    assert result.exit_code == 0
    assert "EQUIVALENT" in result.output


def test_expected_errors_print_a_message_not_a_traceback(tmp_path: Path, monkeypatch):
    """Every AblateError carries a sentence written for the user. A traceback
    would bury it and, worse, exit 0."""
    monkeypatch.chdir(tmp_path)
    result = CliRunner().invoke(main, ["compare", "CLAUDE.md"])

    assert result.exit_code == 1
    assert "not inside a git repository" in result.output
    assert "Traceback" not in result.output


def test_cost_needs_no_git_repo(tmp_path: Path, monkeypatch):
    """The zero-cost first-contact command is arithmetic on a file. Demanding
    a checkout to count tokens would be a gate with nothing behind it."""
    config = tmp_path / "CLAUDE.md"
    config.write_text("# Conventions\n\nUse snake_case everywhere in this project.\n")
    monkeypatch.chdir(tmp_path)

    result = CliRunner().invoke(main, ["cost", str(config)])

    assert result.exit_code == 0, result.output
    assert "tokens" in result.output


def test_cost_finds_a_config_in_the_cwd_outside_a_repo(tmp_path: Path, monkeypatch):
    (tmp_path / "CLAUDE.md").write_text("# Conventions\n\nUse snake_case here.\n")
    monkeypatch.chdir(tmp_path)
    assert CliRunner().invoke(main, ["cost"]).exit_code == 0


def test_sections_is_an_alias_for_the_ablation_command():
    """ADR-0009: `ablate ablate CLAUDE.md` reads badly, so the documented form
    is `ablate sections`. Same callback, so the two cannot drift."""
    result = CliRunner().invoke(main, ["--help"])
    assert "sections" in result.output

    def body(command: str) -> str:
        # Everything past the usage line: the flags and the docstring, which
        # are what would drift if the alias were a separate callback.
        output = CliRunner().invoke(main, [command, "--help"]).output
        return output.split("\n", 1)[1]

    assert body("ablate") == body("sections")


def test_version_resolves_against_the_real_distribution():
    """The distribution is `ablate-cli` while the import package is `ablate`.
    Looking up the wrong one silently reports 0.0.0 in every report footer."""
    from ablate.cli import _version

    assert _version() != "0.0.0"
