#!/usr/bin/env python3
from pathlib import Path
import argparse, tempfile, shutil, subprocess, json, re
ap=argparse.ArgumentParser(); ap.add_argument('--submission-dir',default='learner/starter'); args=ap.parse_args()
HERE=Path(__file__).resolve().parent
lab=HERE if (HERE/'learner').is_dir() else HERE.parent
src=(lab/args.submission_dir).resolve()
PINNED='v22.16.0'
if not src.is_dir(): raise SystemExit('submission dir missing')
def classify(stderr):
    lines=[x.strip() for x in stderr.splitlines() if x.strip()]
    for line in lines:
        if re.search(r'Error \[ERR_[A-Z0-9_]+\]:',line): return line
    for prefix in ('SyntaxError:','TypeError:','ReferenceError:','RangeError:'):
        for line in lines:
            if line.startswith(prefix): return line
    return lines[0] if lines else ''
def run_node(d):
    p=subprocess.run(['node','main.js'],cwd=d,text=True,capture_output=True)
    return {'exit_code':p.returncode,'stdout':p.stdout.strip(),'stderr_classification':classify(p.stderr),'stderr_first_line':(p.stderr.strip().splitlines() or [''])[0]}
ver=subprocess.run(['node','--version'],text=True,capture_output=True).stdout.strip()
if ver!=PINNED: raise SystemExit(f'Pinned Node mismatch: required {PINNED}, observed {ver}')
engine=json.loads((src/'package.json').read_text()).get('engines',{}).get('node')
if engine!='22.16.0': raise SystemExit(f'Submission package must pin Node 22.16.0; found {engine!r}')
base=run_node(src)
results={'required_node_version':PINNED,'observed_node_version':ver,'baseline':base,'mutations':[]}
mutations=['remove_relative_extension','close_exports_subpath','change_module_marker']
for m in mutations:
    with tempfile.TemporaryDirectory(prefix='web002_mut_') as td:
        dst=Path(td)/'submission'; shutil.copytree(src,dst)
        changed=False
        if m=='remove_relative_extension':
            p=dst/'main.js'; t=p.read_text(); nt=t.replace("'./local.js'","'./local'"); changed=(nt!=t); p.write_text(nt)
        elif m=='close_exports_subpath':
            p=dst/'node_modules/demo-pkg/package.json'; d=json.loads(p.read_text()); ex=d.get('exports',{}); changed='./public' in ex; ex.pop('./public',None); d['exports']=ex; p.write_text(json.dumps(d,indent=2)+'\n')
        else:
            p=dst/'package.json'; d=json.loads(p.read_text()); changed=d.get('type')!='commonjs'; d['type']='commonjs'; p.write_text(json.dumps(d,indent=2)+'\n')
        if not changed: raise SystemExit(f'Mutation {m} made no change; submission is not in the expected repaired baseline state.')
        r=run_node(dst); results['mutations'].append({'id':m,**r,'detected':r['exit_code']!=0})
print(json.dumps(results,ensure_ascii=False,indent=2))
raise SystemExit(0 if base['exit_code']==0 and all(x['detected'] for x in results['mutations']) else 2)
