"""task-2609 Track F regression — mock-only, FAIL on any live write.

Drives scripts/run_auto_pr_merge_readiness_dry_run_2609.py purely against
the mock fixture. The suite hard-fails if the engine exposes any GitHub /
git mutation surface (no PR open, no branch/push, no merge, no raw token).
"""
import importlib.util
import json
import subprocess
import sys
from pathlib import Path

import pytest

REPO_ROOT = Path(__file__).resolve().parents[2]
ENTRY = REPO_ROOT / "scripts" / "run_auto_pr_merge_readiness_dry_run_2609.py"
FIXTURE = REPO_ROOT / "memory" / "fixtures" / "task-2609.critical7-cases.json"


def _load_engine():
    spec = importlib.util.spec_from_file_location("t2609_engine", ENTRY)
    mod = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(mod)
    return mod


@pytest.fixture(scope="module")
def engine():
    return _load_engine()


@pytest.fixture(scope="module")
def cases():
    return json.loads(FIXTURE.read_text(encoding="utf-8"))["cases"]


def test_fixture_has_15_cases(cases):
    assert len(cases) == 15


@pytest.mark.parametrize("idx", range(15))
def test_each_regression_matches_expected(engine, cases, idx):
    case = cases[idx]
    out = engine.judge_case(case)
    assert out["verdict"] == case["expected_verdict"], (
        f"case {case['name']}: got {out['verdict']} "
        f"expected {case['expected_verdict']} ({out['reasons']})")
    assert out["critical7_type"] == case["expected_critical7"], (
        f"case {case['name']}: critical7 {out['critical7_type']} "
        f"expected {case['expected_critical7']}")
    assert out["match"] is True


def test_all14_true_is_merge_candidate_ready(engine, cases):
    c = next(x for x in cases if x["name"] == "all14_true")
    out = engine.judge_case(c)
    assert out["conditions_all_true"] is True
    assert out["verdict"] == "MERGE_CANDIDATE_READY"


def test_security_cases_are_fail_closed(engine, cases):
    for name in ("head_sha_mismatch", "owner_pat_detected"):
        c = next(x for x in cases if x["name"] == name)
        assert engine.judge_case(c)["verdict"] == "FAIL_CLOSED"


def test_critical7_cases_classified(engine, cases):
    expect = {
        "forbidden_path_gt_zero": "FORBIDDEN_PATH_INTRUSION",
        "dependency_cycle": "DEPENDENCY_CYCLE_OR_SERIAL_ONLY_COLLISION",
        "replacement_pr_failure": "REPLACEMENT_PR_FAILED",
        "post_merge_smoke_failure": "POST_MERGE_SMOKE_FAILED",
    }
    for name, c7 in expect.items():
        c = next(x for x in cases if x["name"] == name)
        out = engine.judge_case(c)
        assert out["verdict"] == "CRITICAL7"
        assert out["critical7_type"] == c7


def test_consume_modules_byte0_present_and_symbols(engine):
    rep = engine.consume_modules_readonly()
    assert rep["all_present"] is True
    assert rep["all_symbols_ok"] is True
    assert len(rep["modules"]) == 10
    for m in rep["modules"]:
        assert m["sha256"] and len(m["sha256"]) == 64


def test_token_preflight_never_raw(engine):
    pf = engine.redacted_token_preflight({"BOT_GITHUB_TOKEN": "ghs_FAKEvalue123"})
    assert pf["raw_exposed"] is False
    assert "ghs_FAKEvalue123" not in json.dumps(pf)
    assert pf["classified_source"] == "GITHUB_APP_INSTALLATION_TOKEN"
    assert pf["is_ghs_app_token"] is True
    # OWNER PAT must be detectable and never used
    pf2 = engine.redacted_token_preflight({"BOT_GITHUB_TOKEN": "ghp_ownerPAT"})
    assert pf2["owner_pat_detected"] is True
    assert pf2["raw_exposed"] is False


def test_no_live_write_surface(engine):
    g = engine.assert_no_live_write_surface()
    assert g["no_live_write_surface"] is True
    assert g["violations"] == []


def test_engine_source_has_no_github_mutation_calls():
    """mock-only invariant: a live PR/merge/push token implies real write."""
    src = ENTRY.read_text(encoding="utf-8")
    banned = ["gh pr merge", "gh pr create", "gh pr edit",
              "git push", "git commit", "git merge", "git rebase",
              "os.system(", "subprocess.run(", "subprocess.Popen("]
    for tok in banned:
        # the audit's own forbidden-token list legitimately names some of
        # these once; >1 occurrence means real usage → FAIL.
        assert src.count(tok) <= 1, f"live-write token used in engine: {tok}"


def test_run_emit_writes_only_allowlisted_paths(engine, monkeypatch):
    """Run with emit and assert every written path is task-2609.* (additive)."""
    written = []
    real_w = engine._w

    def spy(rel, obj):
        written.append(rel)
        return real_w(rel, obj)

    monkeypatch.setattr(engine, "_w", spy)
    out = engine.run(emit=True)
    assert out["summary"]["real_write_or_merge_attempts"] == 0
    assert out["summary"]["owner_pat_used"] is False
    for rel in written:
        assert "task-2609" in rel, f"non-allowlisted write: {rel}"
        assert rel.startswith(("memory/events/", "memory/reports/")), rel


def test_subprocess_not_invoked_by_judge(engine, cases, monkeypatch):
    """Judging fixtures must never shell out (proves dry-run isolation)."""
    def boom(*a, **k):  # pragma: no cover - must not be reached
        raise AssertionError("engine attempted a subprocess during dry-run")

    monkeypatch.setattr(subprocess, "run", boom)
    monkeypatch.setattr(subprocess, "Popen", boom)
    for c in cases:
        engine.judge_case(c)


if __name__ == "__main__":
    sys.exit(pytest.main([__file__, "-q"]))
