"""mgf-fed CLI entry point.

Subcommands: ``status``, ``drift-check``, ``local-index``, ``gates``, ``local-ci``,
``release-local``, ``release-remote``, ``scaffold``, ``registry`` (plan Phases 0-4, all built).
"""

from __future__ import annotations

import argparse
import platform
import shutil
import subprocess
import sys
from pathlib import Path

from mgf.fed import __version__
from mgf.fed._doctor import run_doctor
from mgf.fed._drift import run_drift_check
from mgf.fed._feedback import run_feedback
from mgf.fed._gates import run_gates
from mgf.fed._index import run_local_index
from mgf.fed._localci import run_local_ci
from mgf.fed._provision import provision_ci_host
from mgf.fed._registry import run_registry
from mgf.fed._release import release_local, release_remote
from mgf.fed._scaffold import run_scaffold
from mgf.fed._status import run_status


def _rich_version() -> str:
    """OP-01 rich version: package + git SHA + Python + composed-tool availability."""
    sha = ""
    try:
        cmd = ["git", "-C", str(Path(__file__).resolve().parent), "rev-parse", "--short", "HEAD"]
        proc = subprocess.run(cmd, capture_output=True, text=True, timeout=5)  # noqa: S603
        sha = proc.stdout.strip() if proc.returncode == 0 else ""
    except (OSError, subprocess.SubprocessError):
        sha = ""
    tools = ", ".join(t for t in ("uv", "ruff", "git", "tea", "woodpecker-cli") if shutil.which(t))
    head = f"mgf-fed {__version__}" + (f" ({sha})" if sha else "")
    return f"{head} | Python {platform.python_version()} | tools: {tools or 'none'}"


def build_parser() -> argparse.ArgumentParser:
    p = argparse.ArgumentParser(
        prog="mgf-fed", description="Magogi Foundation federation orchestrator."
    )
    p.add_argument("--version", action="version", version=_rich_version())
    sub = p.add_subparsers(dest="command", required=True)

    s = sub.add_parser("status", help="read-only federation tracking matrix")
    s.add_argument(
        "--root",
        type=Path,
        default=None,
        help="federation root (default: $MGF_ROOT or ~/PycharmProjects)",
    )
    s.add_argument("--shape", choices=["all", "python", "zig", "other"], default="all")
    s.add_argument("--json", action="store_true", help="emit MGF_FED_STATUS.json to stdout")

    dc = sub.add_parser(
        "drift-check", help="diff each repo against the canonical scaffolding policy"
    )
    dc.add_argument("--root", type=Path, default=None, help="federation root")
    dc.add_argument("--shape", choices=["all", "python", "zig", "other"], default="python")

    sub.add_parser(
        "local-index", help="inspect the local package index (release-local publishes here)"
    )

    g = sub.add_parser(
        "gates", help="run the fast static gates (ruff lint+format) across a selection"
    )
    g.add_argument("--root", type=Path, default=None, help="federation root")
    g.add_argument("--shape", choices=["all", "python", "zig", "other"], default="python")
    g.add_argument(
        "--fix", action="store_true", help="apply ruff --fix + format instead of just checking"
    )

    lc = sub.add_parser(
        "local-ci", help="run the real .woodpecker pipeline for repos (woodpecker-cli exec)"
    )
    lc.add_argument("repos", nargs="+", help="repo name(s), e.g. mgf-http")
    lc.add_argument("--root", type=Path, default=None, help="federation root")

    rl = sub.add_parser(
        "release-local", help="local-release gate → publish to the local index (wave-ordered)"
    )
    rl.add_argument("repos", nargs="+", help="repo name(s), e.g. mgf-standard")
    rl.add_argument("--root", type=Path, default=None, help="federation root")

    rr = sub.add_parser(
        "release-remote", help="promote local-validated wheels to Codeberg (tag→publish→verify)"
    )
    rr.add_argument("repos", nargs="+", help="repo name(s), e.g. mgf-standard")
    rr.add_argument(
        "--dry-run", action="store_true", help="show the plan + pre-req checks; don't tag/publish"
    )
    rr.add_argument("--root", type=Path, default=None, help="federation root")

    sc = sub.add_parser(
        "scaffold", help="apply the template's byte-exact files to a repo (inverse of drift-check)"
    )
    sc.add_argument("repos", nargs="+", help="repo name(s) to scaffold")
    sc.add_argument("--dry-run", action="store_true", help="preview without writing")
    sc.add_argument("--root", type=Path, default=None, help="federation root")

    rg = sub.add_parser(
        "registry", help="regenerate (+ --check) the federation registry (MGF_LIBRARIES)"
    )
    rg.add_argument("--check", action="store_true", help="REL-13 consistency + currency gate")
    rg.add_argument("--root", type=Path, default=None, help="federation root")

    dr = sub.add_parser(
        "doctor", help="self-diagnostic — mgf-fed's own operational prerequisites (OP-02)"
    )
    dr.add_argument("--root", type=Path, default=None, help="federation root")

    fb = sub.add_parser(
        "feedback", help="relay cross-repo feedback outboxes into target inboxes (FB-05)"
    )
    fb.add_argument(
        "--check",
        action="store_true",
        help="scan outboxes; exit non-zero if any un-relayed (the gate; default)",
    )
    fb.add_argument(
        "--relay",
        action="store_true",
        help="relay outbox papers into target FEEDBACK.md + move source to relayed/",
    )
    fb.add_argument("--target", default=None, help="restrict to one target repo (e.g. mgf-common)")
    fb.add_argument(
        "--dry-run", action="store_true", help="with --relay: show the plan; write nothing"
    )
    fb.add_argument("--root", type=Path, default=None, help="federation root")

    pc = sub.add_parser(
        "provision-ci-host",
        help="provision the self-hosted Woodpecker VM via mgf-cloud (WF-11); needs [cloud]",
    )
    pc.add_argument("--hostname", default="woodpecker", help="host base name (default: woodpecker)")
    pc.add_argument("--env", default="prod", help="environment suffix (default: prod)")
    pc.add_argument("--flavor", default=None, help="VM flavour (default: the Infomaniak nano)")
    pc.add_argument("--image", default=None, help="OS image (default: Ubuntu 24.04 LTS)")
    pc.add_argument("--network", default=None, help="network (default: ext-net1)")
    pc.add_argument(
        "--volume-size", type=int, default=20, help="boot-volume GB (0 = ephemeral disk)"
    )
    pc.add_argument(
        "--floating-ip",
        action="store_true",
        help="allocate a floating IP from ext-floating1 (only if NOT on ext-net1, "
        "whose VMs already get a public IP directly)",
    )
    pc.add_argument("--key-name", default=None, help="existing SSH keypair to inject")
    pc.add_argument("--bundle-dir", type=Path, default=None, help="override deploy/woodpecker/")
    pc.add_argument("--provider", default="infomaniak", help="cloud provider (default: infomaniak)")
    pc.add_argument("--clouds-yaml", default=None, help="path to an OpenStack clouds.yaml")
    pc.add_argument("--cloud", default="openstack", help="clouds.yaml entry (e.g. PCP-…-dc3-a)")
    pc.add_argument("--dry-run", action="store_true", help="plan only; touch no cloud")
    pc.add_argument("--yes", action="store_true", help="confirm — actually provision")

    # v0.47 reorg: the moved consumer/governance CLI (explain, validate,
    # app-doctor, standards, agents, catch-up, version, logs, …) lives in
    # mgf.fed.cli and self-registers into mgf-common's subcommand registry.
    # Surface each as a `mgf-fed <verb>` subparser. Imported lazily + guarded
    # so the zero-dep orchestration core (status/release/…) still parses
    # without the `[cli]` extra installed.
    _add_registered_subcommands(sub)
    return p


def _native_commands() -> frozenset[str]:
    """The fleet-orchestration verbs handled by main()'s native ladder."""
    return frozenset(
        {
            "status",
            "drift-check",
            "local-index",
            "gates",
            "local-ci",
            "release-local",
            "release-remote",
            "scaffold",
            "registry",
            "doctor",
            "feedback",
            "provision-ci-host",
        }
    )


def _add_registered_subcommands(sub: argparse._SubParsersAction[argparse.ArgumentParser]) -> None:
    """Merge the mgf.fed.cli registry-registered subcommands into the parser.

    No-op (silently) when the ``[cli]`` extra isn't installed — the core
    fleet commands stay runnable on the zero-dep base install.
    """
    try:
        from mgf.common.cli._subcommand_registry import _iter_registered

        import mgf.fed.cli  # noqa: F401  (registration side-effect; needs [cli])
    except ImportError:
        return
    native = _native_commands()
    for entry in _iter_registered():
        if entry.name in native:  # defensive: never shadow a fleet verb
            continue
        sp = sub.add_parser(entry.name, help=entry.help)
        if entry.parser_setup is not None:
            entry.parser_setup(sp)


def main(argv: list[str] | None = None) -> int:
    # Logging L2 (e): ship this invocation's logs to the federation log host
    # when the env configures it (MGF_LOGSHIP_URL + MGF_LOGSHIP_INGEST_TOKEN).
    # No-op otherwise; never raises. Lazy import keeps the zero-dep core
    # importable without mgf-logship installed.
    from mgf.fed._logging import bootstrap_logging

    bootstrap_logging()
    parser = build_parser()
    args = parser.parse_args(argv)
    try:
        if args.command == "status":
            return run_status(shape=args.shape, as_json=args.json, root=args.root)
        if args.command == "drift-check":
            return run_drift_check(shape=args.shape, root=args.root)
        if args.command == "local-index":
            return run_local_index()
        if args.command == "gates":
            return run_gates(shape=args.shape, fix=args.fix, root=args.root)
        if args.command == "local-ci":
            return run_local_ci(args.repos, root=args.root)
        if args.command == "release-local":
            return release_local(args.repos, root=args.root)
        if args.command == "release-remote":
            return release_remote(args.repos, dry_run=args.dry_run, root=args.root)
        if args.command == "scaffold":
            return run_scaffold(args.repos, dry_run=args.dry_run, root=args.root)
        if args.command == "registry":
            return run_registry(check=args.check, root=args.root)
        if args.command == "doctor":
            return run_doctor(root=args.root)
        if args.command == "feedback":
            return run_feedback(
                relay=args.relay, target=args.target, dry_run=args.dry_run, root=args.root
            )
        if args.command == "provision-ci-host":
            return provision_ci_host(
                hostname=args.hostname,
                env=args.env,
                flavor=args.flavor,
                image=args.image,
                network=args.network,
                volume_size_gb=(args.volume_size or None),
                floating_ip=args.floating_ip,
                key_name=args.key_name,
                bundle_dir=args.bundle_dir,
                provider_name=args.provider,
                clouds_yaml=args.clouds_yaml,
                cloud=args.cloud,
                dry_run=args.dry_run,
                yes=args.yes,
            )
        # Not a native fleet verb → a moved consumer/governance CLI command
        # registered by mgf.fed.cli. Dispatch through mgf-common's registry
        # (reuses its exit-code translation + no-subcommand help fallback).
        from mgf.common.cli import dispatch

        return dispatch(args, parser=parser)
    except Exception as exc:
        # LG-05: ship the traceback to the central log (exc_info) instead of
        # swallowing it; keep a concise human-facing line on stderr.
        from mgf.fed._logging import LOG

        LOG.error("unexpected error in '%s'", args.command, exc_info=True)
        print(f"mgf-fed: unexpected error in '{args.command}': {exc!r}", file=sys.stderr)
        return 1


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