"""5a ASSERTION CONTROLS: move the test's INPUTS, leave the product alone.

Proves each assertion is reached and can fail. NOT regression sensitivity -- that is 5b.
One mutation at a time against the pinned content, never stacked; restore verified between.
"""
import subprocess, sys, os, shutil

F = "crates/spt-daemon/src/bootstrap_firewall/windows.rs"
PIN = "53d625cd0bd88a04815efdf6c8209a3096bf53e8"
MOD = "bootstrap_firewall::windows::tests::"

CASES = [
    ("h1_scope", MOD + "a_scope_that_differs_is_rejected_at_the_pair_verdict_not_only_at_the_token",
     '            "192.168.1.99",  // narrower: one host inside the derived prefix',
     '            "192.168.1.0/24",  // MUTATION 5a: the spec\'s own derived prefix'),

    ("h2_enforcement", MOD + "an_unenforced_rule_is_refused_loudly_after_the_pair_matches",
     '            vec!["NotConfigurable".to_string()],',
     '            vec!["Full".to_string()],'),

    # SINGLE-LINE ANCHOR, deliberately. The multi-line version matched ZERO times
    # twice: the file is CRLF so an LF-joined anchor cannot match, and the repair
    # that hand-escaped a CR did not survive the tool/shell/python stack either.
    # The zero-match refusal caught both; neither was a defect in the cell. No
    # escape sequence appears in this anchor at all.
    ("e1_program", MOD + "an_unrestricted_program_spelled_any_satisfies_a_spec_wanting_none",
     '        let observed = vec![captured_tailnet(29470), captured_lan(29470)];',
     '        let mut observed = vec![captured_tailnet(29470), captured_lan(29470)]; observed[0].program = "C:/other/thing.exe".to_string();'),

    ("e2_profile", MOD + "a_profile_set_compares_by_membership_not_by_rendering",
     '            profile: "Domain, Private".to_string(),                  // captured',
     '            profile: "Public".to_string(),'),

    ("e3_network", MOD + "an_ipv4_network_compares_by_value_in_prefix_or_mask_form",
     '            remotes: vec!["192.168.1.0/255.255.255.0".to_string()],  // captured',
     '            remotes: vec!["192.168.2.0/255.255.255.0".to_string()],  // MUTATION'),

    ("e4_pair", MOD + "the_captured_pair_decides_reconciled_once_spellings_compare_semantically",
     '            remotes: vec!["192.168.1.0/255.255.255.0".to_string()],  // captured',
     '            remotes: vec!["192.168.2.0/255.255.255.0".to_string()],  // MUTATION'),

    ("arm1_adjacency", MOD + "the_write_body_renders_an_adjacent_persistentstore_pair_scoped_to_the_derived_prefix",
     '        let rendered = render_writes(&want);',
     '        let rendered = render_writes(&want).replacen("| Out-Null", "| Out-Null ; Write-Host 0", 1);'),

    ("query_onepass", MOD + "the_query_body_makes_one_store_pass_and_reads_no_persistent_store",
     '        let executable = QUERY',
     '        let executable = script(QUERY)'),
]


def sh(cmd):
    return subprocess.run(cmd, shell=True, capture_output=True, text=True, errors="replace")


def free_gb():
    total, used, free = shutil.disk_usage("C:\\")
    return free / (1024 ** 3)


def restore_and_verify(label):
    sh("git checkout -- " + F)
    clean = sh("git diff --quiet").returncode == 0
    head = sh("git rev-parse HEAD").stdout.strip()
    ok = clean and head == PIN
    print("    RESTORE %s: clean=%s head=%s -> %s" % (label, clean, head[:8], "OK" if ok else "FAILED"))
    return ok


print("=== 5a ASSERTION CONTROLS (inputs move, product untouched)")
print("pin %s  free %.2f GiB" % (PIN[:8], free_gb()))
results = []
for label, testname, anchor, repl in CASES:
    print("\n--- CONTROL %s  cell=%s" % (label, testname.split("::")[-1]))
    f = free_gb()
    if f < 96:
        print("    ABORT: floor check %.2f GiB < 96" % f)
        sys.exit(2)
    print("    floor before producer: %.2f GiB" % f)

    src = open(F, "rb").read().decode("utf-8")
    n = src.count(anchor)
    if n != 1:
        print("    ABORT: anchor matched %d times, must be exactly 1 (zero-match refusal)" % n)
        restore_and_verify(label)
        sys.exit(3)
    open(F, "wb").write(src.replace(anchor, repl).encode("utf-8"))
    if sh("git diff --quiet").returncode == 0:
        print("    ABORT: mutation produced no diff")
        sys.exit(4)

    r = sh('cargo nextest run -p spt-daemon --lib -E "test(/^%s$/)" '
           '--build-jobs 2 --test-threads 2 --success-output immediate' % testname)
    out = r.stdout + r.stderr
    compile_err = ("error[E" in out) or ("error: could not compile" in out)
    ran_zero = "0 tests run" in out
    failed = (" 1 failed" in out) or ("FAIL [" in out)
    if compile_err:
        verdict = "VOID (compile failure is NOT the intended red)"
    elif ran_zero:
        verdict = "VOID (filter matched nothing)"
    elif failed:
        verdict = "RED at its own assertion (control satisfied)"
    else:
        verdict = "UNEXPECTED GREEN (cell did not discriminate)"
    print("    VERDICT: " + verdict)
    for line in out.splitlines():
        s = line.strip()
        if s.startswith("FAIL [") or s.startswith("Summary") or "assertion" in s or "panicked at" in s:
            print("      | " + s[:200])
    results.append((label, verdict))

    if not restore_and_verify(label):
        print("    ABORT: restore failed")
        sys.exit(5)

print("\n=== 5a SUMMARY")
for label, verdict in results:
    print("  %-16s %s" % (label, verdict))
print("free after 5a: %.2f GiB" % free_gb())
