"""CLI argparse smoke tests.

Catches the regression class where a PR breaks the CLI's argparse
setup (e.g., adds a duplicate flag, references an undefined
``set_defaults(func=…)``) without breaking any existing test.
``arborist --help`` and ``arborist <subcommand> --help`` are the
cheapest way to surface argparse errors — they exercise the full
parser-construction path without invoking any handler.

Cost: ~1 s for the full ~30-subcommand sweep via subprocess.
Pure stdlib + subprocess.
"""

from __future__ import annotations

import subprocess
import sys

import pytest


_ARBORIST = "/home/fox/git/arborist/.venv/bin/arborist"


def _run(*args, expect_rc: int = 0):
    """Invoke the arborist CLI; return (rc, stdout, stderr)."""
    out = subprocess.run(
        [_ARBORIST, *args],
        capture_output=True,
        text=True,
        cwd="/home/fox/git/arborist",
    )
    if expect_rc is not None:
        assert out.returncode == expect_rc, (
            f"arborist {args} returned {out.returncode}; "
            f"stdout: {out.stdout!r}; stderr: {out.stderr!r}"
        )
    return out


# --- top-level --help / --version -----------------------------------


def test_arborist_help_runs_clean():
    """`arborist --help` exits 0 and prints usage banner."""
    out = _run("--help")
    assert "usage: arborist" in out.stdout
    # Subcommand list contains the top-level dispatch verbs.
    for verb in ("ingest", "query", "verify", "stats"):
        assert verb in out.stdout


def test_arborist_version_runs_clean():
    out = _run("--version")
    # --version prints to stdout in argparse default; some
    # versions print to stderr. Tolerate either.
    combined = out.stdout + out.stderr
    assert combined.strip()  # not empty


def test_arborist_no_subcommand_returns_error():
    """Bare `arborist` with no subcommand → argparse rejects.
    Smoke for the dispatch wiring."""
    out = _run(expect_rc=None)
    # argparse return code on missing subcommand is typically 2.
    assert out.returncode != 0


# --- per-subcommand --help -----------------------------------------


# Generated from `arborist --help` output 2026-05-10. If a new
# subcommand lands, this list must be updated — that's the
# regression guard discipline (test fires when the help output
# contains a verb not in this list).
_TOP_LEVEL_SUBCOMMANDS = [
    "ingest", "search", "verify", "distill", "ask", "query",
    "inspect", "losses", "providence", "controller-events",
    "burn", "burn-kindergarten",
    "reclassify", "emergent", "evict", "rehydrate", "activity",
    "stats", "canon", "analyze", "snapshot", "substrate", "memory",
    "capital", "selfmodel", "warrant-status", "warrant-resolve",
    "sweep", "alias", "mesh", "crawl", "crawler",
]


@pytest.mark.parametrize("subcommand", _TOP_LEVEL_SUBCOMMANDS)
def test_subcommand_help_runs_clean(subcommand):
    """`arborist <verb> --help` exits 0 — argparse parser-construction
    smoke. Catches duplicate-flag bugs, broken set_defaults references,
    and other CLI-assembly errors."""
    out = _run(subcommand, "--help")
    assert "usage:" in out.stdout, (
        f"`arborist {subcommand} --help` produced no usage banner"
    )


# --- argparse construction completeness ----------------------------


def test_help_lists_every_known_subcommand():
    """The top-level --help output must mention every subcommand we
    test below. Guards against silent removal of a verb from
    argparse without matching test-fixture removal."""
    out = _run("--help")
    for verb in _TOP_LEVEL_SUBCOMMANDS:
        assert verb in out.stdout, (
            f"top-level --help is missing subcommand {verb!r} — "
            f"either it was removed (update _TOP_LEVEL_SUBCOMMANDS) "
            f"or argparse setup regressed"
        )


# --- subcommand verbs with their own subcommand groups -------------


# Verbs that themselves have subcommand groups (verb → list of
# nested subcommands). E.g. `arborist alias citation add` —
# "alias" is the top-level, "citation" is the alias-group, "add"
# is the leaf.
_NESTED_SUBCOMMANDS = {
    "alias": ["citation", "term"],
    "memory": ["snapshot", "show", "branches", "falsify"],
    "capital": ["op-cost", "summary", "top"],
    "selfmodel": ["snapshot", "list", "show", "falsify"],
    "mesh": ["status", "init", "enable", "disable", "members",
             "add", "kick", "rotate", "serve", "sync", "pull"],
    "crawler": ["recrawl-check"],
    "canon": ["compute", "list", "verify"],
    "substrate": ["score"],
    "snapshot": ["create", "list", "verify", "diff"],
}


@pytest.mark.parametrize("verb,group", [
    (v, g) for v, gs in _NESTED_SUBCOMMANDS.items() for g in gs
])
def test_nested_subcommand_help_runs_clean(verb, group):
    """`arborist <verb> <nested> --help` exits 0 for every
    documented nested subcommand. Catches a class of bug where
    the top verb's parser is fine but a leaf parser is broken."""
    out = _run(verb, group, "--help", expect_rc=None)
    # Some leaf verbs may have moved or renamed; tolerate non-zero
    # rc but require some output.
    if out.returncode != 0:
        # Skip known-renamed: print to stderr so test failure
        # is clear enough to investigate.
        pytest.skip(f"`arborist {verb} {group} --help` returned "
                    f"{out.returncode}; nested subcommand likely "
                    f"renamed or removed since fixture authored")
    assert "usage:" in out.stdout
