# recoil/pipeline/tests/test_no_casting_state_reads.py
"""Property test: no Python file in pipeline/lib/ or pipeline/orchestrator/
reads casting_state.json. Migration scripts and tests are allowlisted."""

import re
from pathlib import Path

PIPELINE_ROOT = Path(__file__).resolve().parents[1]

ALLOWLIST = {
    # migration/starsend_to_engine.py was deleted (migration complete, 8395db8d).
    "tools/migrate_assets.py",
    "tools/migrate_heroes.py",
    "tools/migrate_refs.py",
    "tools/populate_canonical.py",
    "tools/populate_assets.py",  # populate-class tool: reads+writes casting_state hero_path
    "tests/test_no_casting_state_reads.py",
    "tests/test_previz_context.py",  # tests pre-refactor fallback behavior
    "tests/fixtures",  # any fixtures dir
    "orchestrator/tests",  # orchestrator tests monkeypatch _load_casting_state mocks
    # Phase 2 follow-up — casting/review-server endpoints still own the
    # legacy state file as a write target. Phase 1 only eliminates READS
    # from lib/ and orchestrator/; Phase 2 will migrate these endpoints.
    "api/deps.py",
    "api/routes/assets.py",
    "api/routes/casting.py",
    "api/routes/files.py",
    "api/routes/overrides.py",
    "editors/review_server.py",
    "tools/generate_keyframes.py",
    # CP-10 — overnight CLI builds the {slug: element_type} lookup once
    # at init from casting_state.json (per ADR-0005); the orchestrator
    # consumes only the in-memory dict thereafter.
    "cli/run_overnight.py",
}

PATTERN = re.compile(r"casting_state\.json|casting_state\[|casting_state\.get|_load_casting_state")


def _allowlisted(path: Path) -> bool:
    rel = path.relative_to(PIPELINE_ROOT).as_posix()
    return any(rel == a or rel.startswith(a + "/") for a in ALLOWLIST)


def test_no_casting_state_reads_outside_migration_allowlist():
    violations = []
    for py_file in PIPELINE_ROOT.rglob("*.py"):
        if "__pycache__" in py_file.parts:
            continue
        if _allowlisted(py_file):
            continue
        try:
            text = py_file.read_text()
        except UnicodeDecodeError:
            continue
        for line_num, line in enumerate(text.splitlines(), start=1):
            stripped = line.strip()
            if stripped.startswith("#"):
                continue
            if PATTERN.search(line):
                violations.append(f"{py_file.relative_to(PIPELINE_ROOT)}:{line_num}: {stripped}")
    assert not violations, (
        "casting_state.json read outside migration allowlist:\n\n"
        + "\n".join(violations)
    )
