"""Tests for ``mgf-common agents``.

The subcommand prints the canonical Magogi agent briefing. The
briefing text is a source constant (`AGENTS_BRIEFING`) so it always
ships in the wheel and can never fail to resolve — that is what
makes "run `mgf-common agents`" a reliable, assumption-free
instruction for any project's AI agent.

The load-bearing test here is `test_briefing_matches_starthere`:
it pins the constant to `STARTHERE.md` §"For the AI agent" so the
two representations of the one canonical message cannot drift.
"""

from __future__ import annotations

import argparse
import io
from contextlib import redirect_stderr, redirect_stdout
from pathlib import Path

# The AGENTS_BRIEFING constant is the single source of truth; its canonical
# STARTHERE.md mirror lives in mgf-common (where the briefing text + this test
# originated), NOT in mgf-fed (whose own STARTHERE is a different doc). After
# the federation reorg the _agents module moved here, but the briefing still
# mirrors mgf-common/STARTHERE.md — resolve it via the (editable-installed)
# mgf.common package rather than this repo's tree.
import mgf.common as _mgf_common
import pytest
from mgf.common.cli._exit_codes import EXIT_SUCCESS

from mgf.fed.cli._agents import (
    AGENTS_BRIEFING,
    add_agents_args,
    run_agents,
)

# Editable installs expose mgf-common's repo tree (STARTHERE.md is a repo doc,
# NOT shipped in the wheel), so this resolves in local dev + mgf-common's own
# CI. In a wheel-install context (this repo's CI) it won't be found — default to
# None and let the sync test skip rather than erroring the whole module.
_COMMON_ROOT = next(
    (p for p in Path(_mgf_common.__file__).resolve().parents if (p / "STARTHERE.md").is_file()),
    None,
)
_STARTHERE = (_COMMON_ROOT / "STARTHERE.md") if _COMMON_ROOT else None

# A deterministic two-member registry for the federation-map tests, so they
# don't depend on the live on-disk federation.
_FAKE_REGISTRY = [
    {
        "repo": "mgf-common",
        "version": "9.9.9",
        "deps": {"mgf-standard": ">=0.1,<0.2"},
        "used_by": ["mgf-x"],
    },
    {
        "repo": "mgf-x",
        "version": "0.1.0",
        "deps": {"mgf-common": ">=9,<10"},
        "used_by": [],
    },
]


def _make_args() -> argparse.Namespace:
    return argparse.Namespace()


class TestRunAgents:
    def test_prints_briefing_and_returns_exit_success(self) -> None:
        buf = io.StringIO()
        with redirect_stdout(buf):
            rc = run_agents(_make_args())
        assert rc == EXIT_SUCCESS
        out = buf.getvalue()
        assert "Magogi Foundation" in out
        assert AGENTS_BRIEFING in out

    def test_briefing_covers_the_load_bearing_points(self) -> None:
        # The briefing is "complete" only if it carries every duty
        # the message must convey. Guard the non-negotiables.
        b = AGENTS_BRIEFING
        assert "catch-up" in b  # keep the project in sync
        assert "standards" in b  # follow the rules
        assert "FEDERATION.md" in b  # the canonical contract
        assert "FEEDBACK.md" in b  # the feedback loop
        assert "mgf-fed feedback-howto" in b
        assert "do not reinvent" in b.lower() or "reinvent it" in b
        # The feedback mandate must be unmistakable.
        assert "most important" in b


class TestBriefingSync:
    def test_briefing_matches_starthere(self) -> None:
        """`AGENTS_BRIEFING` MUST appear verbatim in STARTHERE.md.

        The constant and `STARTHERE.md` §"For the AI agent in any
        Magogi project" are the two faces of one canonical message.
        If this fails, they have drifted — re-sync them (edit one to
        match the other); there is exactly one message.
        """
        if _STARTHERE is None or not _STARTHERE.is_file():
            pytest.skip(
                "mgf-common/STARTHERE.md unavailable (wheel install — repo doc not packaged)"
            )
        starthere = _STARTHERE.read_text(encoding="utf-8")
        assert AGENTS_BRIEFING.strip() in starthere, (
            "AGENTS_BRIEFING has drifted from STARTHERE.md §'For the AI "
            "agent'. The CLI constant and the doc section are one message "
            "— re-sync them so `mgf-common agents` and the doc are identical."
        )


class TestAddAgentsArgs:
    def test_accepts_a_subparser_without_raising(self) -> None:
        parser = argparse.ArgumentParser()
        add_agents_args(parser)
        ns = parser.parse_args([])
        assert isinstance(ns, argparse.Namespace)


class TestDynamicRuleRanges:
    """PAPER-67: briefing's `WF-01..WF-NN` range is computed at
    import time from WORKFLOWS.md. Pin the behavior so:
      1. The rendered range matches the actual rule count.
      2. The fallback path works when standards docs aren't found
         (broken briefing would break onboarding).
      3. TS-22..TS-27 (curated subrange) stays as-is — auto-derive
         doesn't apply to subranges."""

    def test_wf_range_matches_workflows_md_rule_count(self) -> None:
        # The briefing's WF range MUST match what WORKFLOWS.md
        # actually carries. If WF-13 lands and the briefing still
        # says WF-01..WF-12, this fails — same fail surface as
        # the STARTHERE pin.
        import re

        from mgf.fed.cli._agents import _WF_MAX, _WF_RANGE
        from mgf.fed.cli._standards import _resolve_standards_dir

        # The corpus now lives in the mgf-standard sibling; resolve it
        # through the same seam the briefing uses rather than a hardcoded
        # mgf-common repo path (which no longer carries docs/standards/).
        standards_dir = _resolve_standards_dir()
        assert standards_dir is not None, (
            "standards corpus not resolvable — is mgf-standard installed "
            "(or checked out adjacent for an editable install)?"
        )
        workflows_md = standards_dir / "WORKFLOWS.md"
        assert workflows_md.is_file()
        text = workflows_md.read_text(encoding="utf-8")
        all_wf_ids = sorted(int(m) for m in re.findall(r'id="WF-(\d+)"', text))
        actual_max = max(all_wf_ids)
        assert actual_max == _WF_MAX, (
            f"_WF_MAX ({_WF_MAX}) doesn't match WORKFLOWS.md's actual "
            f"max ({actual_max}). If new WF rules landed, the dynamic "
            f"render should pick them up automatically — investigate "
            f"why it didn't (standards dir not resolving? regex broken?)."
        )
        assert f"WF-01..WF-{actual_max:02d}" == _WF_RANGE

    def test_fallback_to_12_when_doc_unreadable(
        self,
        monkeypatch,
    ) -> None:
        # If the standards dir can't be resolved (wheel doesn't
        # include them; editable install without docs/), the
        # briefing must STILL render — never block onboarding on
        # a doc-resolution edge case.
        from mgf.fed.cli import _agents

        monkeypatch.setattr(
            "mgf.fed.cli._agents._resolve_standards_dir",
            lambda: None,
        )
        # Re-run the helper directly to verify fallback path.
        assert _agents._max_rule_id("WORKFLOWS", "WF") is None
        # The module-level fallback (12) kicks in via `or 12`.
        # We can't easily re-run module init, but the constant
        # _WF_MAX must be a positive int either way (verified
        # by the first test).

    def test_ts_supervisor_range_rules_all_exist_in_testing_md(
        self,
    ) -> None:
        # TS-22..TS-27 is a curated subrange. Validate every rule
        # in the curated range actually exists in TESTING.md — so
        # accidental deletion of, say, TS-25 surfaces here.
        import re

        from mgf.fed.cli._agents import _TS_SUPERVISOR_RANGE

        # Parse "TS-22..TS-27" → range(22, 28).
        m = re.match(r"TS-(\d+)\.\.TS-(\d+)", _TS_SUPERVISOR_RANGE)
        assert m is not None
        lo, hi = int(m.group(1)), int(m.group(2))

        from mgf.fed.cli._standards import _resolve_standards_dir

        standards_dir = _resolve_standards_dir()
        assert standards_dir is not None
        testing_md = standards_dir / "TESTING.md"
        assert testing_md.is_file()
        text = testing_md.read_text(encoding="utf-8")
        for n in range(lo, hi + 1):
            assert f'id="TS-{n:02d}"' in text, (
                f"TS-{n:02d} (in curated supervisor range) is missing "
                f"from TESTING.md. Either the rule was deleted (fix "
                f"the rule), or the curated _TS_SUPERVISOR_RANGE in "
                f"_agents.py needs updating (fix the constant)."
            )


class TestFederationSiblings:
    """PAPER-80: the sibling list in AGENTS_BRIEFING is rendered from
    the `_FEDERATION_SIBLINGS` constant. Adding/removing a sibling is
    a one-line update to that constant, not two parallel hand-edits
    in two files."""

    def test_constant_is_a_tuple(self) -> None:
        from mgf.fed.cli._agents import _FEDERATION_SIBLINGS

        assert isinstance(_FEDERATION_SIBLINGS, tuple)

    def test_briefing_includes_all_constant_entries(self) -> None:
        # If a sibling is in the constant, the rendered briefing
        # MUST mention it. (Pin the f-string interpolation against
        # the constant's content.)
        from mgf.fed.cli._agents import (
            _FEDERATION_SIBLINGS,
            AGENTS_BRIEFING,
        )

        for name in _FEDERATION_SIBLINGS:
            assert f"`{name}`" in AGENTS_BRIEFING, (
                f"sibling `{name}` is in _FEDERATION_SIBLINGS but "
                f"missing from AGENTS_BRIEFING — f-string rendering "
                f"broke?"
            )

    def test_known_framework_siblings_present(self) -> None:
        # Federation-baseline: the seven framework + test siblings
        # that consumers pull most often MUST be enumerated.
        # Adding a new one bumps this expected set + the constant.
        from mgf.fed.cli._agents import _FEDERATION_SIBLINGS

        baseline = frozenset(
            {
                "mgf-http",
                "mgf-fastapi",
                "mgf-sqlalchemy",
                "mgf-alembic",
                "mgf-django",
                "mgf-apiprobe",
                "mgf-test-supervisor",
            }
        )
        assert baseline.issubset(set(_FEDERATION_SIBLINGS)), (
            f"federation baseline missing from _FEDERATION_SIBLINGS: "
            f"{baseline - set(_FEDERATION_SIBLINGS)}"
        )


class TestProjectScoped:
    """`mgf-common agents --project` — the project-scoped briefing that
    gives EVERY project an `agents` command scoped to itself, via the
    universal mgf-common tool."""

    def test_flag_parses(self) -> None:
        parser = argparse.ArgumentParser()
        add_agents_args(parser)
        assert parser.parse_args(["--project"]).project is True
        assert parser.parse_args(["-p"]).project is True
        assert parser.parse_args([]).project is False

    def test_project_prints_local_claude_doc(self, tmp_path, monkeypatch) -> None:
        claude = tmp_path / "docs" / "claude" / "CLAUDE.md"
        claude.parent.mkdir(parents=True)
        claude.write_text("# Project agent doc\nSENTINEL-CONTENT\n", encoding="utf-8")
        monkeypatch.chdir(tmp_path)
        buf = io.StringIO()
        with redirect_stdout(buf):
            rc = run_agents(argparse.Namespace(project=True))
        out = buf.getvalue()
        assert rc == EXIT_SUCCESS
        assert "SENTINEL-CONTENT" in out
        assert "docs/claude/CLAUDE.md" in out
        # Always points back at the federation-wide briefing.
        assert "mgf-fed agents" in out

    def test_project_falls_back_to_agents_md(self, tmp_path, monkeypatch) -> None:
        (tmp_path / "AGENTS.md").write_text("AGENTS-FALLBACK\n", encoding="utf-8")
        monkeypatch.chdir(tmp_path)
        buf = io.StringIO()
        with redirect_stdout(buf):
            rc = run_agents(argparse.Namespace(project=True))
        assert rc == EXIT_SUCCESS
        assert "AGENTS-FALLBACK" in buf.getvalue()

    def test_project_with_no_docs_guides_to_catch_up(self, tmp_path, monkeypatch) -> None:
        monkeypatch.chdir(tmp_path)
        buf = io.StringIO()
        with redirect_stdout(buf):
            rc = run_agents(argparse.Namespace(project=True))
        out = buf.getvalue()
        assert rc == EXIT_SUCCESS
        assert "No project-scoped agent doc found" in out
        assert "catch-up --apply" in out


class TestFederationMap:
    """The dynamic federation roster appended to ``mgf-common agents``,
    resolved from the installed mgf-standard registry — the drift-proof
    "which other projects exist" half of federation-awareness."""

    def test_map_follows_briefing_when_registry_resolves(self, monkeypatch) -> None:
        monkeypatch.setattr("mgf.fed.cli._agents._resolve_libraries", lambda: _FAKE_REGISTRY)
        buf = io.StringIO()
        with redirect_stdout(buf):
            rc = run_agents(_make_args())
        out = buf.getvalue()
        assert rc == EXIT_SUCCESS
        # The static briefing is still emitted verbatim ...
        assert AGENTS_BRIEFING in out
        # ... and the live federation map follows it.
        assert "Federation map" in out
        assert "2 libraries" in out
        assert "cornerstone mgf-common 9.9.9" in out

    def test_degrades_loudly_when_registry_unavailable(self, monkeypatch) -> None:
        # The briefing (stdout) always works and the command still exits 0,
        # but a missing federation map is surfaced LOUDLY on stderr with the
        # one-line install fix — never silently omitted (WU3 loud-degrade).
        monkeypatch.setattr("mgf.fed.cli._agents._resolve_libraries", lambda: None)
        out_buf, err_buf = io.StringIO(), io.StringIO()
        with redirect_stdout(out_buf), redirect_stderr(err_buf):
            rc = run_agents(_make_args())
        out, err = out_buf.getvalue(), err_buf.getvalue()
        assert rc == EXIT_SUCCESS
        assert AGENTS_BRIEFING in out  # the briefing never depends on the map
        assert "Federation map" not in out  # the map block is omitted from stdout
        # ... but the degrade is announced, with the actionable fix:
        assert "federation map unavailable" in err
        assert "mgf-common[standards]" in err

    def test_you_are_here_when_cwd_is_a_member(self, tmp_path, monkeypatch) -> None:
        (tmp_path / "pyproject.toml").write_text(
            '[project]\nname = "mgf-x"\nversion = "0.1.0"\n', encoding="utf-8"
        )
        monkeypatch.chdir(tmp_path)
        monkeypatch.setattr("mgf.fed.cli._agents._resolve_libraries", lambda: _FAKE_REGISTRY)
        buf = io.StringIO()
        with redirect_stdout(buf):
            run_agents(_make_args())
        out = buf.getvalue()
        assert "You are here: mgf-x 0.1.0" in out
        assert "depends on (mgf-*): mgf-common" in out
