"""``mgf-fed hooks`` — install, inspect, and feed the session-hook kit.

The verb group over :mod:`mgf.fed.sessionhooks`. Six actions, in two families.

**Operator actions** — run by a human (or an agent) setting a repo up:

  - ``hooks status``      — is the shim wired, is it current, is the kit switched off?
  - ``hooks install``     — merge the one-line shim into ``.claude/settings.json``.
  - ``hooks uninstall``   — remove exactly our entry; leave every other setting alone.
  - ``hooks emit``        — append one event. This is the command **SEAT-02**'s
    session-start re-teach asks the agent to run; it is the whole point of the kit.
  - ``hooks log``         — read the trail back (``--tail`` / ``--session`` / ``--json``).

**Harness actions** — invoked by the hook shim, never typed by a human. They read the
harness's hook JSON on stdin and are documented here so a reader of ``settings.json``
can find out what the line does:

  - ``hooks session-start`` — the observe-mode SessionStart hook (wave S3's whole install).
  - ``hooks writeset`` / ``hooks gate`` — the **staged, disabled-by-default** enforce
    material. Both return ``0`` immediately unless ``MGF_HOOKS_GATE=1`` arms them; see
    :mod:`mgf.fed.sessionhooks._gate` for the observe/enforce boundary.

Sub-verbs are a positional ``action`` with ``choices=``, matching ``mgf-fed logs`` —
this CLI has no nested subparsers anywhere, and one shape is worth more than one
saved keystroke.

**Why this module lives beside the kit and not in** :mod:`mgf.fed.cli`. Importing
anything from ``mgf.fed.cli`` runs that package's ``__init__``, which registers the
whole governance surface and therefore requires ``mgf-common`` — the ``[cli]`` extra.
That is fine for `explain` or `app-doctor`; it is **not** fine here, because the
installed shim runs ``mgf-fed hooks session-start`` at the start of every session in
every adopting repo, including ones on a bare ``pip install mgf-fed``. A hook path
that depends on an optional extra is a hook path that silently does not exist for
some of the fleet — the exact invisible-absence failure this kit was written to end.
So the module sits here, stdlib-only, and :mod:`mgf.fed.__main__` wires ``hooks`` as
a **native** verb. :mod:`mgf.fed.cli._mgfcli` additionally registers it in the
subcommand registry, which costs nothing and keeps the verb reachable from
``build_cli()``-shaped consumer CLIs; the native ladder wins when both are present.
"""

from __future__ import annotations

import json
import sys
from pathlib import Path
from typing import TYPE_CHECKING, Any

from mgf.fed.sessionhooks import _events, _gate, _install, _session_start
from mgf.fed.sessionhooks._state import (
    disabled_reason,
    gate_armed,
    hook_input,
    mode,
    repo_root,
    session_id,
)

if TYPE_CHECKING:
    import argparse

_OPERATOR_ACTIONS = ("status", "install", "uninstall", "emit", "log")
_HARNESS_ACTIONS = ("session-start", "writeset", "gate")
ACTIONS = (*_OPERATOR_ACTIONS, *_HARNESS_ACTIONS)


def add_hooks_args(parser: argparse.ArgumentParser) -> None:
    """Register ``mgf-fed hooks`` arguments."""
    parser.add_argument(
        "action",
        nargs="?",
        choices=ACTIONS,
        default="status",
        help=(
            "status (default; is the shim wired?) · install · uninstall · "
            "emit <type> [k=v ...] · log · session-start/writeset/gate "
            "(harness entrypoints — invoked by the shim, not by hand)."
        ),
    )
    parser.add_argument(
        "rest",
        nargs="*",
        default=[],
        help="For `emit`: the event type, then `key=value` fields.",
    )
    parser.add_argument(
        "--repo",
        default=None,
        help="Target repo (default: the current working directory's repo root).",
    )
    parser.add_argument(
        "--tail",
        type=int,
        default=None,
        help="For `log`: show only the last N records.",
    )
    parser.add_argument(
        "--session",
        default=None,
        help="For `log`/`emit`: the session id (default: $CLAUDE_SESSION_ID, else `manual`).",
    )
    parser.add_argument(
        "--json",
        action="store_true",
        help="For `log`/`status`: emit machine-readable JSON instead of prose.",
    )


def run_hooks(args: argparse.Namespace) -> int:
    """Dispatch the requested action. Harness actions always return ``0``."""
    action = getattr(args, "action", None) or "status"
    if action in _HARNESS_ACTIONS:
        return _run_harness(action)
    repo = _resolve_repo(getattr(args, "repo", None))
    if action == "install":
        return _run_install(repo)
    if action == "uninstall":
        return _run_uninstall(repo)
    if action == "emit":
        return _run_emit(repo, list(getattr(args, "rest", []) or []), args)
    if action == "log":
        return _run_log(repo, args)
    return _run_status(repo, args)


# --------------------------------------------------------------------------- #
# Harness entrypoints — stdin-driven, fail-open, exit 0 (except an armed,
# enforcing gate, which is the one path that may exit 2).
# --------------------------------------------------------------------------- #


def _run_harness(action: str) -> int:
    payload = hook_input()
    if action == "session-start":
        return _session_start.run(payload)
    if action == "writeset":
        return _gate.run_writeset(payload)
    return _gate.run_gate(payload)


# --------------------------------------------------------------------------- #
# Operator actions
# --------------------------------------------------------------------------- #


def _resolve_repo(explicit: str | None) -> Path:
    """The repo to act on: ``--repo`` if given, else the cwd's repo root."""
    if explicit:
        return Path(explicit).expanduser().resolve()
    return repo_root({"cwd": str(Path.cwd())})


def _run_install(repo: Path) -> int:
    try:
        changed, message = _install.install(repo)
    except _install.SettingsRefused as exc:
        print(f"mgf-fed hooks: {exc}", file=sys.stderr)
        return 1
    print(f"{'✅' if changed else 'ℹ'} {message}")
    if changed:
        print("   Restart the session (or start a new one) for the harness to pick it up.")
        # Advisory, not a mutation: `install` writes settings.json and nothing else.
        # But the kill-switch MUST be git-ignored — a sweeping `git add -A` that
        # commits it would switch the kit off for every clone, permanently and
        # invisibly, which is the one failure a kill-switch may never have (SHP-08).
        print(
            "   Add `.claude/state/` and `.claude/mgf-hooks-disabled` to .gitignore: a "
            "sweeping\n   `git add` must never commit the kill-switch or the local trail."
        )
    return 0


def _run_uninstall(repo: Path) -> int:
    try:
        changed, message = _install.uninstall(repo)
    except _install.SettingsRefused as exc:
        print(f"mgf-fed hooks: {exc}", file=sys.stderr)
        return 1
    print(f"{'✅' if changed else 'ℹ'} {message}")
    return 0


def _run_emit(repo: Path, rest: list[str], args: argparse.Namespace) -> int:
    """``mgf-fed hooks emit <type> [k=v ...]`` — the SEAT-02 declaration channel."""
    if not rest:
        print(
            "usage: mgf-fed hooks emit <type> [key=value ...]\n"
            f"       types: {', '.join(sorted(_events.EVENT_TYPES))}\n"
            "       e.g.: mgf-fed hooks emit seat_declared seat=executor model=opus-5",
            file=sys.stderr,
        )
        return 1
    etype, pairs = rest[0], rest[1:]
    malformed = [p for p in pairs if "=" not in p]
    if malformed:
        print(
            f"mgf-fed hooks: fields must be key=value; got {malformed}. Nothing was recorded.",
            file=sys.stderr,
        )
        return 1
    fields = dict(pair.split("=", 1) for pair in pairs)
    payload = {"session_id": args.session} if getattr(args, "session", None) else None
    ok, why = _events.emit(repo, etype, fields, payload=payload)
    if not ok:
        print(f"mgf-fed hooks: REFUSED {etype} — {why}", file=sys.stderr)
        return 1
    sid = session_id(payload)
    print(f"✅ recorded {etype} (session {sid}) in {_events.events_path(repo)}")
    if why:
        print(f"   {why}")
    return 0


def _run_log(repo: Path, args: argparse.Namespace) -> int:
    records = _events.read(
        repo, tail=getattr(args, "tail", None), session=getattr(args, "session", None)
    )
    if getattr(args, "json", False):
        print(json.dumps(records, indent=2, sort_keys=True))
        return 0
    path = _events.events_path(repo)
    if not records:
        print(f"mgf-fed hooks: no records in {path} — the kit has never run here.")
        return 0
    for rec in records:
        extra = {k: v for k, v in rec.items() if k not in ("utc", "protocol", "event", "session")}
        event = str(rec.get("event", "?"))
        print(f"{rec.get('utc', '?')} {event:<24} session={rec.get('session', '?')} {extra}")
    summary = _events.summarise(records)
    print(
        f"\n{summary['records']} record(s), {summary['sessions']} session(s); "
        f"undeclared seats: {len(summary['undeclared_sessions'])}"
    )
    return 0


def _run_status(repo: Path, args: argparse.Namespace) -> int:
    report: dict[str, Any] = _install.status(repo)
    report["mode"] = mode()
    report["gate_armed"] = gate_armed()
    report["disabled"] = disabled_reason(repo)
    records = _events.read(repo)
    report["events"] = _events.summarise(records)
    report["events_path"] = str(_events.events_path(repo))

    if getattr(args, "json", False):
        print(json.dumps(report, indent=2, sort_keys=True))
        return 0

    print(f"mgf-fed hooks — {report['repo']}")
    if report["problem"]:
        print(f"  🔴 {report['problem']}")
    elif report["installed"]:
        current = "current" if report["current"] else "STALE — re-run `mgf-fed hooks install`"
        print(f"  ✅ SessionStart shim installed ({current}) in {report['settings']}")
    else:
        print(
            f"  ℹ  not installed — `mgf-fed hooks install` writes the shim into "
            f"{report['settings']}"
        )
    if report["disabled"]:
        print(f"  ⚠️  KILL-SWITCH ACTIVE — {report['disabled']}. Nothing is recorded.")
    print(
        f"  mode={report['mode']} (observe never blocks) · gate="
        f"{'ARMED' if report['gate_armed'] else 'off (staged; MGF_HOOKS_GATE=1 arms it)'}"
    )
    events = report["events"]
    print(
        f"  events: {events['records']} record(s) over {events['sessions']} session(s) "
        f"in {report['events_path']}"
    )
    if events["undeclared_sessions"]:
        shown = ", ".join(events["undeclared_sessions"][:4])
        print(
            f"  ⚠️  {len(events['undeclared_sessions'])} session(s) never declared a seat "
            f"(SEAT-02): {shown}"
        )
    return 0


__all__ = ["ACTIONS", "add_hooks_args", "run_hooks"]
