"""§3.3 — the fleet-policy loader's ONE-namespace precedence.

``[tool.mgf-fed.policy]`` in mgf-standard's own pyproject.toml is canonical;
``templates/policy.toml`` is the deprecated fallback, read iff the pyproject
section is absent, with a deprecation note on load. Self-contained (tmp_path
roots), so the precedence pin holds in a single-repo CI clone too.
"""

from __future__ import annotations

from pathlib import Path

import pytest

from mgf.fed import _template
from mgf.fed._template import load_policy, policy_source

_PYPROJECT_WITH_POLICY = """\
[project]
name = "mgf-standard"
version = "1.0.0"

[tool.mgf-fed]
vuln_ignore = ["CVE-2025-71176"]

[tool.mgf-fed.policy]
schema = 1
present = ["SCOPE.md"]
py_typed = true

[tool.mgf-fed.policy.exact]
"scripts/verify_codeberg.py" = "verify_codeberg.py"

[tool.mgf-fed.policy.pyproject]
mypy_strict = true
"""

_POLICY_TOML = 'schema = 1\npresent = ["OTHER.md"]\n'


def _std(tmp_path: Path, *, pyproject: str | None, policy_toml: str | None) -> Path:
    std = tmp_path / "mgf-standard"
    (std / "templates").mkdir(parents=True)
    if pyproject is not None:
        (std / "pyproject.toml").write_text(pyproject, encoding="utf-8")
    if policy_toml is not None:
        (std / "templates" / "policy.toml").write_text(policy_toml, encoding="utf-8")
    return tmp_path


def test_pyproject_policy_is_canonical_and_silent(
    tmp_path: Path, capsys: pytest.CaptureFixture[str]
) -> None:
    """When [tool.mgf-fed.policy] exists it wins — even with a (different)
    policy.toml on disk — and no deprecation note prints."""
    root = _std(tmp_path, pyproject=_PYPROJECT_WITH_POLICY, policy_toml=_POLICY_TOML)
    policy = load_policy(root)
    assert policy["present"] == ["SCOPE.md"]  # the pyproject table, not OTHER.md
    assert policy["py_typed"] is True
    assert policy["exact"] == {"scripts/verify_codeberg.py": "verify_codeberg.py"}
    assert policy["pyproject"] == {"mypy_strict": True}
    assert "DEPRECATED" not in capsys.readouterr().err
    src = policy_source(root)
    assert src is not None and src.name == "pyproject.toml"


def test_policy_toml_fallback_prints_deprecation_note(
    tmp_path: Path, capsys: pytest.CaptureFixture[str]
) -> None:
    """No pyproject section → policy.toml is read, with the §3.3 deprecation note
    (once per path per process)."""
    # a pyproject WITHOUT the policy subtable must not shadow the fallback.
    root = _std(
        tmp_path,
        pyproject='[project]\nname = "mgf-standard"\n[tool.mgf-fed]\nvuln_ignore = []\n',
        policy_toml=_POLICY_TOML,
    )
    assert load_policy(root)["present"] == ["OTHER.md"]
    err = capsys.readouterr().err
    assert "DEPRECATED" in err and "[tool.mgf-fed.policy]" in err
    load_policy(root)  # second load: the note does not repeat
    assert "DEPRECATED" not in capsys.readouterr().err
    src = policy_source(root)
    assert src is not None and src.name == "policy.toml"


def test_missing_both_sources_raises(tmp_path: Path) -> None:
    root = _std(tmp_path, pyproject=None, policy_toml=None)
    with pytest.raises(FileNotFoundError, match=r"tool\.mgf-fed\.policy"):
        load_policy(root)
    assert policy_source(root) is None


def test_note_emitted_per_distinct_path(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
    """The once-guard is per policy PATH — two different roots each get their note."""
    root_a = _std(tmp_path / "a", pyproject=None, policy_toml=_POLICY_TOML)
    root_b = _std(tmp_path / "b", pyproject=None, policy_toml=_POLICY_TOML)
    load_policy(root_a)
    load_policy(root_b)
    err = capsys.readouterr().err
    assert err.count("DEPRECATED") == 2


def test_template_dir_unchanged(tmp_path: Path) -> None:
    assert _template.template_dir(tmp_path) == tmp_path / "mgf-standard" / "templates"
