"""Phase 2: approval CLI for pending auto-fix branches.

Subcommands:
  bot/approve.py                # list pending fixes (table)
  bot/approve.py show <branch>  # full record + diff
  bot/approve.py merge <branch> # re-run verify, ff-merge into local master, cleanup
  bot/approve.py reject <branch># cleanup worktree + pending-fix entry, do not touch master

Never pushes to remote, never tags, never invokes release.sh. Phase 3 wires
those.
"""
import argparse
import subprocess
import sys
from pathlib import Path

from bot.lib import state
from bot.lib.verify import verify as run_verify


def _git(repo: Path, *args: str) -> subprocess.CompletedProcess:
    return subprocess.run(
        ["git", "-C", str(repo), *args],
        capture_output=True, text=True, check=False,
    )


def cmd_list(state_dir: Path) -> str:
    fixes = state.load_pending_fixes(state_dir)
    releases = state.load_pending_releases(state_dir)
    if not fixes and not releases:
        return "no pending fixes or scheduled releases"
    parts: list[str] = []
    if fixes:
        parts.append("== PENDING FIXES ==")
        parts.append(f"{'BRANCH':40}  {'STATUS':22}  {'CONF':>4}  REPORT")
        for branch, entry in sorted(fixes.items()):
            parts.append(
                f"{branch:40}  {entry.get('status',''):22}  "
                f"{entry.get('confidence',0):>4}  {entry.get('report_path','')}"
            )
    if releases:
        if parts:
            parts.append("")
        parts.append("== SCHEDULED RELEASES ==")
        parts.append(f"{'VERSION':12}  {'STATUS':12}  {'SCHEDULED_AT':32}  RETRIES")
        for version, entry in sorted(releases.items()):
            parts.append(
                f"{version:12}  {entry.get('status',''):12}  "
                f"{entry.get('scheduled_at',''):32}  {entry.get('retries', 0)}"
            )
    return "\n".join(parts)


def cmd_show(state_dir: Path, repo_root: Path, branch: str) -> tuple[int, str]:
    fixes = state.load_pending_fixes(state_dir)
    entry = fixes.get(branch)
    if entry is None:
        return 1, f"{branch}: not found"
    out = [f"branch: {branch}"]
    for k in ("status", "confidence", "rationale", "report_path", "worktree_path", "created_at"):
        out.append(f"{k}: {entry.get(k, '')}")
    out.append("verify_rows:")
    for row in entry.get("verify_rows", []):
        mark = "PASS" if row.get("passed") else "FAIL"
        out.append(f"  [{mark}] {row.get('name','?')}: {row.get('detail','')}")
    diff = _git(repo_root, "diff", "master..." + branch)
    out.append("---- diff ----")
    out.append(diff.stdout)
    return 0, "\n".join(out)


def cmd_merge(state_dir: Path, repo_root: Path, branch: str) -> tuple[int, str]:
    fixes = state.load_pending_fixes(state_dir)
    entry = fixes.get(branch)
    if entry is None:
        return 1, f"{branch}: not found"

    worktree_path = Path(entry["worktree_path"])
    removed_symbols: set[str] = set(entry.get("removed_symbols") or [])
    vr = run_verify(
        worktree_path=worktree_path,
        removed_symbols=removed_symbols,
        repo_root=repo_root,
    )
    if not vr.passed_all:
        failed = [f"{r.name}: {r.detail}" for r in vr.rows if not r.passed]
        return 1, "verify failed at merge time:\n  " + "\n  ".join(failed)

    merge = _git(repo_root, "merge", "--ff-only", branch)
    if merge.returncode != 0:
        return 1, (
            "ff-merge failed; master may have moved. Rebase manually:\n"
            f"  git -C {repo_root} fetch && git -C {worktree_path} rebase master\n"
            f"stderr: {merge.stderr.strip()}"
        )

    _git(repo_root, "worktree", "remove", str(worktree_path))
    _git(repo_root, "branch", "-d", branch)
    state.remove_pending_fix(state_dir, branch)
    return 0, f"merged {branch} into master (no push). Cleanup complete."


def cmd_reject(state_dir: Path, repo_root: Path, branch: str) -> tuple[int, str]:
    fixes = state.load_pending_fixes(state_dir)
    entry = fixes.get(branch)
    if entry is None:
        return 1, f"{branch}: not found"
    worktree_path = Path(entry["worktree_path"])
    _git(repo_root, "worktree", "remove", "--force", str(worktree_path))
    _git(repo_root, "branch", "-D", branch)
    state.remove_pending_fix(state_dir, branch)
    return 0, f"rejected {branch} and removed worktree."


def cmd_cancel(state_dir: Path, version: str) -> tuple[int, str]:
    releases = state.load_pending_releases(state_dir)
    if version not in releases:
        return 1, f"{version}: not found"
    state.remove_pending_release(state_dir, version)
    return 0, f"cancelled scheduled release {version}"


def _main(argv: list[str] | None = None) -> int:
    repo_root = Path(__file__).resolve().parent.parent
    state_dir = repo_root / "bot" / "state"

    parser = argparse.ArgumentParser(description="CritMatic patch-watcher approval CLI")
    sub = parser.add_subparsers(dest="cmd")
    sub.add_parser("list")
    p_show = sub.add_parser("show"); p_show.add_argument("branch")
    p_merge = sub.add_parser("merge"); p_merge.add_argument("branch")
    p_reject = sub.add_parser("reject"); p_reject.add_argument("branch")
    p_cancel = sub.add_parser("cancel"); p_cancel.add_argument("version")
    args = parser.parse_args(argv)

    if args.cmd in (None, "list"):
        print(cmd_list(state_dir))
        return 0
    if args.cmd == "show":
        rc, msg = cmd_show(state_dir, repo_root, args.branch)
        print(msg); return rc
    if args.cmd == "merge":
        rc, msg = cmd_merge(state_dir, repo_root, args.branch)
        print(msg); return rc
    if args.cmd == "reject":
        rc, msg = cmd_reject(state_dir, repo_root, args.branch)
        print(msg); return rc
    if args.cmd == "cancel":
        rc, msg = cmd_cancel(state_dir, args.version)
        print(msg); return rc
    parser.print_help()
    return 2


if __name__ == "__main__":
    raise SystemExit(_main())
