"""Built-in CLI registration pins — the mgf-fed half of v0.4 Phase E.

These tests moved from ``mgf-common/tests/unit/test_phase_e.py`` in the
federation reorg (Wave 3). The CLI built-ins (``explain`` / ``validate``
/ ``doctor`` (now ``app-doctor``) / ``migrate``) and the ``_mgfcli``
console-script entry point now live in ``mgf-fed``; importing
``mgf.fed.cli`` registers them into mgf-common's (KEPT) subcommand
registry.

The registry-mechanics + empty-by-default ``build_cli`` pins stay in
``mgf-common/tests/unit/test_phase_e.py``.

Naming notes (v0.47 reorg):
  - subcommand ``doctor`` was renamed to ``app-doctor`` to avoid
    clashing with mgf-fed's native ``doctor`` (federation self-check).
    The module is still ``mgf.fed.cli._doctor`` / handler ``run_doctor``.
  - ``build_cli``'s hardcoded ``include_builtins`` filter set in
    ``_subcommand_registry.py`` is still ``{"explain","validate",
    "doctor","migrate"}`` (KEEP, unchanged). Since the registered name
    is now ``app-doctor``, ``include_builtins=False`` no longer filters
    it — so the filter-out pin asserts on the three names that ARE in
    both the registry and the hardcoded set: explain / validate /
    migrate. This is expected vestigial behaviour (the source is
    deliberately left unchanged).
"""

from __future__ import annotations

from typing import TYPE_CHECKING

import pytest
from mgf.common.cli import build_cli, register_cli_subcommand, unregister_cli_subcommand
from mgf.common.cli._subcommand_registry import _registered_cli_subcommands

# Importing mgf.fed.cli triggers the registration side-effects (it imports
# mgf.fed.cli._mgfcli, whose module-level register_cli_subcommand(...) calls
# populate mgf-common's registry).
import mgf.fed.cli  # noqa: F401

if TYPE_CHECKING:
    from collections.abc import Iterator


@pytest.fixture
def _scratch_subcommands() -> Iterator[list[str]]:
    """Yield a list to which tests append the names they register;
    unregister all of them on teardown."""
    registered: list[str] = []
    yield registered
    for name in registered:
        unregister_cli_subcommand(name)


class TestBuiltinsRegistered:
    """The built-ins self-register when mgf.fed.cli is imported."""

    def test_builtins_are_pre_registered(self) -> None:
        """Pin: the four v0.3 built-ins (``explain`` / ``validate`` /
        ``app-doctor`` (was ``doctor``) / ``migrate``) are registered
        at import time of ``mgf.fed.cli`` (which imports ``_mgfcli``)."""
        registered = _registered_cli_subcommands()
        for name in ("explain", "validate", "app-doctor", "migrate"):
            assert name in registered, f"built-in subcommand {name!r} MUST be pre-registered"

    def test_lists_builtin_subcommands_in_help(self) -> None:
        """Builtins show up in the parser's subcommand list."""
        parser = build_cli()
        help_text = parser.format_help()
        for name in ("explain", "validate", "app-doctor", "migrate"):
            assert name in help_text

    def test_include_builtins_false_filters_them_out(
        self,
        _scratch_subcommands: list[str],
    ) -> None:
        """``include_builtins=False`` lets a consumer ship a CLI that
        ONLY exposes their own subcommands.

        The hardcoded filter set in _subcommand_registry.py is still
        ``{"explain","validate","doctor","migrate"}``; the registered
        name is now ``app-doctor``, so only explain / validate / migrate
        (the three names present in BOTH the registry and the filter
        set) are filtered out. ``app-doctor`` is NOT filtered — expected
        vestigial behaviour after the rename; the source is unchanged.
        """

        @register_cli_subcommand(
            "phase_e_only_mine",
            help="Phase E only-mine test subcommand.",
        )
        def _h(args: object) -> int:
            return 0

        _scratch_subcommands.append("phase_e_only_mine")

        parser = build_cli(prog="myapp", include_builtins=False)
        # Use format_usage() which renders the choices line only,
        # not the description prose (which legitimately mentions
        # "explain" / "validate" by name).
        usage = parser.format_usage()
        assert "phase_e_only_mine" in usage
        # The built-ins whose REGISTERED name is in the hardcoded filter
        # set MUST NOT appear in the choices list:
        for name in ("explain", "validate", "migrate"):
            assert name not in usage, (
                f"built-in {name!r} appeared in usage choices with include_builtins=False"
            )


class TestDispatchViaMain:
    """Round-trip through the mgf-fed entry point.

    The vestigial ``mgf.fed.cli._mgfcli.main`` is the moved console-script
    entry; for the full-parser round-trip we drive through
    ``mgf.fed.__main__.main`` (native fleet verbs + registered subcommands).
    """

    def test_consumer_subcommand_dispatched_via_main(
        self,
        _scratch_subcommands: list[str],
    ) -> None:
        called: dict[str, object] = {"got_args": None}

        def _handler(args: object) -> int:
            called["got_args"] = args
            return 7  # arbitrary non-zero, distinguishable from EXIT_SUCCESS

        register_cli_subcommand(
            "phase_e_dispatch",
            handler=_handler,
            help="d",
        )
        _scratch_subcommands.append("phase_e_dispatch")

        # Rebuild the parser AFTER registration: __main__.main builds it
        # fresh on each call, so the new subcommand is picked up.
        from mgf.fed.__main__ import main

        rc = main(["phase_e_dispatch"])
        assert rc == 7
        assert called["got_args"] is not None

    def test_existing_builtins_still_dispatch_via_registry(
        self,
        capsys: pytest.CaptureFixture[str],
    ) -> None:
        """Regression smoke: the moved ``app-doctor`` subcommand (was
        ``doctor``) still runs end-to-end through the registry-based
        dispatch driven by the mgf-fed entry point."""
        from mgf.common.cli import EXIT_SUCCESS

        from mgf.fed.__main__ import main

        rc = main(["app-doctor"])
        # app-doctor returns EXIT_SUCCESS when no failures; tests run in
        # an installed env, so it should pass.
        assert rc in (EXIT_SUCCESS, 2), f"app-doctor exit code unexpected: {rc}"
        # Output landed on stdout — the doctor prints check markers.
        out = capsys.readouterr().out
        assert "mgf-common" in out  # always present in the install line
