"""Mutation positive controls.

Each entry breaks ONE guard and names the test that must FAIL because of it.
The anchor is asserted to appear exactly once BEFORE substituting: a mutation whose
anchor misses prints nothing and reads exactly like "the test caught it".
"""
import io, subprocess, sys, shutil, os

SRC = r'C:\Users\decid\Documents\projects\spt-claude-code\tools\claude-spt\src\hook.rs'
MANIFEST = r'C:\Users\decid\Documents\projects\spt-claude-code\tools\claude-spt\Cargo.toml'
BAK = SRC + '.mutbak'

MUTATIONS = [
    (
        'M1 closing payload goes back to a cursor-blind offset-0 read',
        'match batch_payload(&scan.authored) {',
        'match turn_closing_output_MUT(env, &field(v, "transcript_path")) {',
        ['a_turn_ending_on_a_span_does_not_republish_it', 'a_turn_with_no_new_text_republishes_nothing'],
        # the mutation needs the old reader back, appended as a helper
        '''
fn turn_closing_output_MUT(env: &dyn HookEnv, transcript_path: &str) -> Option<String> {
    if transcript_path.is_empty() { return None; }
    let (text, _) = env.read_file_tail(transcript_path, 0)?;
    crate::tag_scan::assistant_texts_from_jsonl(&text).into_iter().rev().find(|t| !t.trim().is_empty())
}
''',
    ),
    (
        'M2 an empty batch is published as an empty span instead of skipped',
        '    let Some(span) = batch_payload(authored) else { return };',
        '    let span = batch_payload(authored).unwrap_or_default();',
        ['an_empty_batch_publishes_no_span'],
        '',
    ),
    (
        'M3 commune bodies are no longer excluded from the batch',
        '    let authored =\n        texts.into_iter().filter(|t| crate::tag_scan::commune_body(t).is_none()).collect();',
        '    let authored = texts.into_iter().collect();',
        ['a_commune_body_is_never_published_as_a_span', 'a_commune_body_is_never_handed_to_the_agent_output_channel'],
        '',
    ),
    (
        'M4 the span drops its --mid marker (publishes as a turn close)',
        '    with_payload.push("--mid");',
        '    // mutated: no --mid',
        ['pretooluse_publishes_its_batch_as_a_midturn_span', 'no_pretooluse_busy_mark_carries_a_user_input_payload'],
        '',
    ),
]


def run_tests():
    r = subprocess.run(
        ['cargo', 'test', '--manifest-path', MANIFEST],
        capture_output=True, text=True, errors='replace',
    )
    return r.stdout + r.stderr


def failing_tests(out):
    names = set()
    for line in out.splitlines():
        line = line.strip()
        if line.startswith('---- ') and line.endswith(' stdout ----'):
            names.add(line[5:-12].strip().split('::')[-1])
    return names


shutil.copyfile(SRC, BAK)
orig = io.open(SRC, encoding='utf-8').read()
ok = True
try:
    for name, anchor, repl, must_fail, extra in MUTATIONS:
        n = orig.count(anchor)
        if n != 1:
            print('SKIP/BAD ANCHOR %-62s count=%d' % (name, n))
            ok = False
            continue
        mutated = orig.replace(anchor, repl, 1) + extra
        io.open(SRC, 'w', encoding='utf-8', newline=chr(10)).write(mutated)
        out = run_tests()
        if 'error[E' in out or 'could not compile' in out:
            print('MUTATION DID NOT COMPILE %-45s' % name)
            for l in out.splitlines():
                if l.startswith('error'):
                    print('    ', l)
            ok = False
            io.open(SRC, 'w', encoding='utf-8', newline=chr(10)).write(orig)
            continue
        failed = failing_tests(out)
        caught = [t for t in must_fail if t in failed]
        missed = [t for t in must_fail if t not in failed]
        status = 'CAUGHT ' if not missed else 'ESCAPED'
        print('%s %-58s failed=%s' % (status, name, sorted(failed) or 'NONE'))
        if missed:
            print('         these did NOT fail and should have:', missed)
            ok = False
        io.open(SRC, 'w', encoding='utf-8', newline=chr(10)).write(orig)
finally:
    io.open(SRC, 'w', encoding='utf-8', newline=chr(10)).write(orig)
    os.remove(BAK)

print()
print('ALL MUTATIONS CAUGHT' if ok else 'SOME MUTATIONS ESCAPED OR MISAPPLIED')
sys.exit(0 if ok else 1)
