"""CLI subcommand integration tests for this session's new work.

Covers `arborist selfmodel|memory|capital` subcommand surfaces end-to-
end through ``build_parser`` + ``args.func(args)`` (matches existing
arborist CLI-test pattern in ``tests/test_burn.py``).

This file is the *functional* layer for tickets #000014 (SelfModel),
#000017 (Memory), #000020 (Capital). Unit-level tests for the
underlying modules live in ``tests/test_selfmodel.py``,
``tests/test_memory_root.py``, ``tests/test_capital.py``.
"""

from __future__ import annotations

import json

import pytest

from arborist.cli import build_parser
from arborist.store import append_audit, connect, transaction


def _run(parser, argv: list[str]) -> int:
    """Run an argv through the CLI parser and dispatch table."""
    args = parser.parse_args(argv)
    return args.func(args)


# ---------------------------------------------------------------------
# selfmodel CLI
# ---------------------------------------------------------------------


def test_selfmodel_snapshot_cli_writes_record(tmp_path, capsys):
    db = tmp_path / "shard.db"
    parser = build_parser()
    rc = _run(parser, ["--db", str(db), "selfmodel", "snapshot"])
    assert rc == 0
    payload = json.loads(capsys.readouterr().out)
    root = payload["selfmodel_root"]
    assert len(root) == 64

    # Record present in the database.
    conn = connect(db)
    try:
        row = conn.execute(
            "SELECT state FROM selfmodel_records WHERE selfmodel_root = ?",
            (root,),
        ).fetchone()
        assert row is not None
        assert row["state"] == "live"
    finally:
        conn.close()


def test_selfmodel_show_cli_returns_full_record(tmp_path, capsys):
    db = tmp_path / "shard.db"
    parser = build_parser()
    _run(parser, ["--db", str(db), "selfmodel", "snapshot"])
    capsys.readouterr()  # drain
    rc = _run(parser, ["--db", str(db), "selfmodel", "show"])
    assert rc == 0
    payload = json.loads(capsys.readouterr().out)
    assert "selfmodel_root" in payload
    assert payload["state"] == "live"
    assert payload["schema_version"] == "selfmodel-v1"
    assert "claims" in payload  # at minimum empty list


def test_selfmodel_show_cli_specific_root(tmp_path, capsys):
    db = tmp_path / "shard.db"
    parser = build_parser()
    _run(parser, ["--db", str(db), "selfmodel", "snapshot"])
    snapshot_payload = json.loads(capsys.readouterr().out)
    root = snapshot_payload["selfmodel_root"]
    rc = _run(parser, ["--db", str(db), "selfmodel", "show", "--root", root])
    assert rc == 0
    show_payload = json.loads(capsys.readouterr().out)
    assert show_payload["selfmodel_root"] == root


def test_selfmodel_show_cli_returns_error_on_missing(tmp_path, capsys):
    db = tmp_path / "shard.db"
    parser = build_parser()
    bogus = "00" * 32
    rc = _run(parser, ["--db", str(db), "selfmodel", "show", "--root", bogus])
    assert rc == 1
    payload = json.loads(capsys.readouterr().out)
    assert payload["error"] == "no SelfModel found"


def test_selfmodel_falsify_cli_flips_state(tmp_path, capsys):
    db = tmp_path / "shard.db"
    parser = build_parser()
    _run(parser, ["--db", str(db), "selfmodel", "snapshot"])
    snapshot_payload = json.loads(capsys.readouterr().out)
    root = snapshot_payload["selfmodel_root"]

    rc = _run(parser, [
        "--db", str(db),
        "selfmodel", "falsify", root,
        "--reason", "verifier upgrade",
    ])
    assert rc == 0
    falsify_payload = json.loads(capsys.readouterr().out)
    assert falsify_payload["selfmodel_root"] == root
    assert falsify_payload["audit_event_hash"] is not None
    assert falsify_payload["noop"] is False

    # State row is now falsified.
    conn = connect(db)
    try:
        row = conn.execute(
            "SELECT state, falsified_reason FROM selfmodel_records "
            "WHERE selfmodel_root = ?", (root,),
        ).fetchone()
        assert row["state"] == "falsified"
        assert row["falsified_reason"] == "verifier upgrade"
    finally:
        conn.close()


def test_selfmodel_falsify_cli_idempotent(tmp_path, capsys):
    db = tmp_path / "shard.db"
    parser = build_parser()
    _run(parser, ["--db", str(db), "selfmodel", "snapshot"])
    root = json.loads(capsys.readouterr().out)["selfmodel_root"]
    _run(parser, ["--db", str(db), "selfmodel", "falsify", root, "--reason", "r1"])
    capsys.readouterr()
    rc = _run(parser, ["--db", str(db), "selfmodel", "falsify", root, "--reason", "r2"])
    assert rc == 0
    payload = json.loads(capsys.readouterr().out)
    assert payload["noop"] is True
    assert payload["audit_event_hash"] is None


def test_selfmodel_list_cli_returns_recent(tmp_path, capsys):
    db = tmp_path / "shard.db"
    parser = build_parser()
    # Ensure at least one record exists.
    _run(parser, ["--db", str(db), "selfmodel", "snapshot"])
    capsys.readouterr()
    rc = _run(parser, ["--db", str(db), "selfmodel", "list", "--limit", "5"])
    assert rc == 0
    payload = json.loads(capsys.readouterr().out)
    assert isinstance(payload, list)
    assert len(payload) >= 1
    assert payload[0]["state"] == "live"


# ---------------------------------------------------------------------
# memory CLI
# ---------------------------------------------------------------------


def test_memory_snapshot_cli_writes_record(tmp_path, capsys):
    db = tmp_path / "shard.db"
    parser = build_parser()
    rc = _run(parser, ["--db", str(db), "memory", "snapshot"])
    assert rc == 0
    payload = json.loads(capsys.readouterr().out)
    root = payload["memory_root"]
    assert len(root) == 64

    conn = connect(db)
    try:
        row = conn.execute(
            "SELECT state, schema_version FROM memory_records "
            "WHERE memory_root = ?", (root,),
        ).fetchone()
        assert row is not None
        assert row["state"] == "live"
        assert row["schema_version"] == "memory-v1"
    finally:
        conn.close()


def test_memory_show_cli_returns_branches(tmp_path, capsys):
    db = tmp_path / "shard.db"
    parser = build_parser()
    _run(parser, ["--db", str(db), "memory", "snapshot"])
    capsys.readouterr()
    rc = _run(parser, ["--db", str(db), "memory", "show"])
    assert rc == 0
    payload = json.loads(capsys.readouterr().out)
    assert payload["state"] == "live"
    branch_ids = {b["branch_id"] for b in payload["branches"]}
    # Default projection set per ticket #000017.
    assert "audit-mode-distribution" in branch_ids
    assert "falsification-state" in branch_ids


def test_memory_branches_cli_lists_summaries(tmp_path, capsys):
    db = tmp_path / "shard.db"
    parser = build_parser()
    _run(parser, ["--db", str(db), "memory", "snapshot"])
    capsys.readouterr()
    rc = _run(parser, ["--db", str(db), "memory", "branches"])
    assert rc == 0
    payload = json.loads(capsys.readouterr().out)
    assert isinstance(payload, list)
    branch_ids = {b["branch_id"] for b in payload}
    assert "audit-mode-distribution" in branch_ids


def test_memory_falsify_cli(tmp_path, capsys):
    db = tmp_path / "shard.db"
    parser = build_parser()
    _run(parser, ["--db", str(db), "memory", "snapshot"])
    snap = json.loads(capsys.readouterr().out)
    root = snap["memory_root"]
    rc = _run(parser, [
        "--db", str(db), "memory", "falsify", root, "--reason", "drift detected",
    ])
    assert rc == 0
    payload = json.loads(capsys.readouterr().out)
    assert payload["audit_event_hash"] is not None
    assert payload["noop"] is False

    conn = connect(db)
    try:
        row = conn.execute(
            "SELECT state, falsified_reason FROM memory_records WHERE memory_root = ?",
            (root,),
        ).fetchone()
        assert row["state"] == "falsified"
        assert row["falsified_reason"] == "drift detected"
    finally:
        conn.close()


# ---------------------------------------------------------------------
# capital CLI
# ---------------------------------------------------------------------


def _seed_capital_rows(db_path) -> None:
    """Write a few capital_ledger rows tied to real audit events."""
    from arborist.capital import CapitalProfile, record

    conn = connect(db_path)
    try:
        with transaction(conn):
            ev_ingest = append_audit(conn, event_type="ingest",
                                     subject_root=None, body={"k": "v"})
            record(conn, audit_event_hash=ev_ingest, op_type="ingest",
                   profile=CapitalProfile(material=2.0, intellectual=0.05))
            ev_qa = append_audit(conn, event_type="qa",
                                 subject_root=None, body={"k": "v"})
            record(conn, audit_event_hash=ev_qa, op_type="qa",
                   profile=CapitalProfile(financial=0.01, experiential=0.5))
            ev_distill = append_audit(conn, event_type="distill",
                                      subject_root=None, body={"k": "v"})
            record(conn, audit_event_hash=ev_distill, op_type="distill",
                   profile=CapitalProfile(material=1.5, intellectual=0.02))
    finally:
        conn.close()


def test_capital_summary_cli_full(tmp_path, capsys):
    db = tmp_path / "shard.db"
    _seed_capital_rows(db)
    parser = build_parser()
    rc = _run(parser, ["--db", str(db), "capital", "summary"])
    assert rc == 0
    payload = json.loads(capsys.readouterr().out)
    assert payload["row_count"] == 3
    assert payload["material"] == pytest.approx(3.5)
    assert payload["financial"] == pytest.approx(0.01)


def test_capital_summary_cli_filter_op_type(tmp_path, capsys):
    db = tmp_path / "shard.db"
    _seed_capital_rows(db)
    parser = build_parser()
    rc = _run(parser, [
        "--db", str(db), "capital", "summary", "--op-type", "ingest",
    ])
    assert rc == 0
    payload = json.loads(capsys.readouterr().out)
    assert payload["row_count"] == 1
    assert payload["material"] == pytest.approx(2.0)


def test_capital_op_cost_cli(tmp_path, capsys):
    db = tmp_path / "shard.db"
    _seed_capital_rows(db)
    parser = build_parser()
    rc = _run(parser, ["--db", str(db), "capital", "op-cost", "qa"])
    assert rc == 0
    payload = json.loads(capsys.readouterr().out)
    assert payload["row_count"] == 1
    assert payload["financial"] == pytest.approx(0.01)
    assert payload["experiential"] == pytest.approx(0.5)


def test_capital_top_cli(tmp_path, capsys):
    db = tmp_path / "shard.db"
    _seed_capital_rows(db)
    parser = build_parser()
    rc = _run(parser, [
        "--db", str(db), "capital", "top", "--form", "material", "--limit", "10",
    ])
    assert rc == 0
    payload = json.loads(capsys.readouterr().out)
    assert isinstance(payload, list)
    op_types = [r["op_type"] for r in payload]
    # ingest=2.0, distill=1.5, qa=0 → ingest first
    assert op_types[0] == "ingest"
    assert op_types[1] == "distill"


def test_capital_top_cli_rejects_unknown_form(tmp_path, capsys):
    db = tmp_path / "shard.db"
    parser = build_parser()
    with pytest.raises(SystemExit):
        # argparse rejects on the choices list before the handler runs.
        _run(parser, [
            "--db", str(db), "capital", "top", "--form", "imaginary_form",
        ])


# ---------------------------------------------------------------------
# Audit chain stays intact across all three CLI families
# ---------------------------------------------------------------------


def test_chain_stays_clean_across_session_clis(tmp_path, capsys):
    """All three CLI families chain through ``store.append_audit`` and
    must leave the audit_events chain re-verifiable."""
    import hashlib

    db = tmp_path / "shard.db"
    parser = build_parser()
    # SelfModel + Memory + Capital all trigger audit events.
    _run(parser, ["--db", str(db), "selfmodel", "snapshot"])
    capsys.readouterr()
    _run(parser, ["--db", str(db), "memory", "snapshot"])
    capsys.readouterr()
    _seed_capital_rows(db)

    conn = connect(db)
    try:
        rows = conn.execute(
            "SELECT event_hash, prev_event_hash, body FROM audit_events ORDER BY seq"
        ).fetchall()
        prev = None
        for row in rows:
            h = hashlib.sha256()
            if prev is not None:
                h.update(bytes.fromhex(prev))
            h.update(row["body"].encode("utf-8", errors="surrogatepass"))
            assert h.hexdigest() == row["event_hash"], (
                "audit chain break detected at row "
                f"{row['event_hash']}"
            )
            assert row["prev_event_hash"] == prev
            prev = row["event_hash"]
    finally:
        conn.close()
