"""Drift guard for v0.33.0-removed names (RA-18).

v0.33.0 removed three deprecated names per their AP-12 cycles:

  - ``mgf.common.configure``
  - ``mgf.common.config.make_settings_config``
  - ``mgf.common.exceptions.EnvironmentError`` (the alias that
    shadowed Python's built-in)

The post-removal sweep cleaned up every docstring + standards-doc
reference, except for the legitimate ones (migration codemod,
consumer catch-up audit, cutover guide, historical record). This
test pins the cleanup against future drift: any new occurrence of
the removed names in code or docs fails the build with a clear
pointer to the offending file:line + the allowed-context list.

The allow-list is small and intentional:

  - ``src/mgf/common/cli/_migrate.py`` — the codemod that DETECTS
    and renames these names in consumer source. References to the
    old names ARE the implementation.
  - ``src/mgf/common/cli/_catch_up.py`` — the consumer audit that
    flags legacy patterns in consumer code.
  - ``docs/cutover/v0.33.0.md`` — the migration recipe; tells
    consumers exactly what to migrate from.
  - ``docs/design/extraction_history.md`` — historical record of
    what was true at each release.
  - ``docs/inprogress/RELIABILITY_AND_ACCURACY.md`` — tracks RA-18
    itself; mentions the names as the subject of the work.
  - Any ``tests/`` file — tests exist specifically to verify these
    names are gone (``test_phase2_surface``, ``test_phase7_migrate``,
    this very file).

If a new file needs to reference one of these names, add it to the
allowlist below WITH a justification, then update this docstring.
"""

from __future__ import annotations

import re
from pathlib import Path

import pytest

# Files allowed to reference the removed names — see module docstring
# for the rationale per entry.
_REPO_ROOT = Path(__file__).resolve().parent.parent.parent
_ALLOWED_PATHS = {
    _REPO_ROOT / "src" / "mgf" / "common" / "cli" / "_migrate.py",
    _REPO_ROOT / "src" / "mgf" / "common" / "cli" / "_catch_up.py",
    _REPO_ROOT / "docs" / "cutover" / "v0.33.0.md",
    _REPO_ROOT / "docs" / "design" / "extraction_history.md",
    _REPO_ROOT / "docs" / "inprogress" / "RELIABILITY_AND_ACCURACY.md",
    # PUBLIC_API.md mentions the removed name in the "moved to
    # HostEnvironmentError" historical row.
    _REPO_ROOT / "PUBLIC_API.md",
    # CHANGELOG necessarily names what was removed in v0.33.0.
    _REPO_ROOT / "CHANGELOG.md",
}

# Roots to scan. Anything under tests/ is excluded because the test
# suite legitimately exercises the codemod, the catch-up audit, and
# the "is the alias gone" assertions.
_SCAN_ROOTS = [
    _REPO_ROOT / "src",
    _REPO_ROOT / "docs",
]

# The three banned names. Each is matched with a word boundary so
# innocent substrings (e.g., "model_config", "reconfigure") don't
# false-positive.
_BANNED_PATTERNS = {
    "mgf.common.configure": re.compile(r"\bmgf\.common\.configure\b"),
    "make_settings_config(": re.compile(r"\bmake_settings_config\("),
    "mgf.common.exceptions.EnvironmentError": re.compile(
        r"\bmgf\.common\.exceptions\.EnvironmentError\b"
    ),
}


def _files_to_scan() -> list[Path]:
    """Walk the configured roots, yielding .py / .md files only,
    skipping caches and the allow-list."""
    files: list[Path] = []
    for root in _SCAN_ROOTS:
        for path in root.rglob("*"):
            if not path.is_file():
                continue
            if path.suffix not in {".py", ".md"}:
                continue
            if "__pycache__" in path.parts:
                continue
            if path in _ALLOWED_PATHS:
                continue
            files.append(path)
    return sorted(files)


@pytest.mark.parametrize(
    "name,pattern",
    list(_BANNED_PATTERNS.items()),
    ids=list(_BANNED_PATTERNS),
)
def test_no_v033_removed_name_drift(name: str, pattern: re.Pattern[str]) -> None:
    """RA-18: removed v0.33.0 names MUST NOT appear in src/ or docs/
    outside the explicit allowlist.

    If this test fails, the offender is either:
      - a legitimate new reference that belongs in the allow-list
        (add the path to ``_ALLOWED_PATHS`` with a docstring entry), OR
      - actual drift that needs to be updated to the post-v0.33.0
        canonical name:
          configure → bootstrap
          make_settings_config → settings_config
          mgf.common.exceptions.EnvironmentError → HostEnvironmentError
    """
    offenders: list[tuple[Path, int, str]] = []
    for path in _files_to_scan():
        try:
            text = path.read_text(encoding="utf-8")
        except (OSError, UnicodeDecodeError):
            continue
        for lineno, line in enumerate(text.splitlines(), start=1):
            if pattern.search(line):
                offenders.append((path.relative_to(_REPO_ROOT), lineno, line.strip()))

    if offenders:
        lines = [f"  {p}:{ln} — {snippet[:120]}" for p, ln, snippet in offenders]
        msg = (
            f"RA-18 drift: {name!r} still appears in {len(offenders)} place(s) "
            f"outside the allowlist. Update each to the post-v0.33.0 canonical "
            f"name, or add the file to _ALLOWED_PATHS with a justification.\n" + "\n".join(lines)
        )
        pytest.fail(msg)
