from pathlib import Path
import os,sys,json,shutil,subprocess
here=Path(__file__).resolve().parent; root=here/'working'/'polyglot'; mode=sys.argv[1] if len(sys.argv)>1 else 'baseline'
MISSING_RUNTIME_RC=23; EVIDENCE_FAIL_RC=24

def run(cmd,cwd,env=None):
    p=subprocess.run(cmd,cwd=cwd,env=env,capture_output=True,text=True)
    return {'command':cmd,'rc':p.returncode,'stdout':p.stdout[-2500:],'stderr':p.stderr[-2500:]}
def tool(name): return shutil.which(name)
def clean():
    for p in [root/'js'/'node_modules',root/'python'/'.venv',root/'rust'/'target']:
        shutil.rmtree(p,ignore_errors=True)
def relevant_env_ok(): return os.environ.get('MAKNOW_PROFILE')=='lab'
if not root.exists(): raise SystemExit('run setup_fixture.py first')
clean(); results={}; missing=[]
# pnpm: no installation/provision from network is attempted
if not tool('pnpm'):
    results['pnpm']={'status':'RUNTIME_UNAVAILABLE','rc':MISSING_RUNTIME_RC}; missing.append('pnpm')
else:
    env={**os.environ,'PNPM_HOME':os.environ.get('PNPM_HOME','')}
    artifact=root/'js'/'prepared_store'/'artifacts'/'fixture-external-1.0.0.tgz'; store=here/'working'/'pnpm-store'
    if not artifact.exists():
        results['pnpm']={'status':'FAIL','rc':EVIDENCE_FAIL_RC,'failure_layer':'LOCAL_ARTIFACT_AVAILABILITY','artifact':str(artifact)}
    else:
        shutil.rmtree(store,ignore_errors=True)
        seed=run([tool('pnpm'),'store','add',str(artifact),'--store-dir',str(store)],root/'js',env)
        if seed['rc']!=0: results['pnpm']={'status':'FAIL','rc':seed['rc'],'failure_layer':'LOCAL_STORE_PROVISION','seed':seed}
        else:
            results['pnpm']=run([tool('pnpm'),'install','--frozen-lockfile','--offline','--ignore-scripts','--store-dir',str(store)],root/'js',env)
            results['pnpm']['status']='PASS' if results['pnpm']['rc']==0 else 'FAIL'; results['pnpm']['fixture_local_store']=str(store)
# uv: isolated cache, wheelhouse remains inside project
if not tool('uv'):
    results['uv']={'status':'RUNTIME_UNAVAILABLE','rc':MISSING_RUNTIME_RC}; missing.append('uv')
else:
    cache=here/'working'/'uv-cache'; shutil.rmtree(cache,ignore_errors=True); env={**os.environ,'UV_CACHE_DIR':str(cache)}
    results['uv']=run([tool('uv'),'sync','--locked','--offline','--python',sys.executable],root/'python',env); results['uv']['status']='PASS' if results['uv']['rc']==0 else 'FAIL'; results['uv']['python_executable']=sys.executable
# Cargo/Rust/rustup are one evidence chain. Selection must be observable in the Rust workspace.
missing_rust=[x for x in ('cargo','rustc','rustup') if not tool(x)]
if missing_rust:
    results['cargo']={'status':'RUNTIME_UNAVAILABLE','rc':MISSING_RUNTIME_RC,'missing':missing_rust,'cargo':tool('cargo'),'rustc':tool('rustc'),'rustup':tool('rustup')}; missing.append('cargo/rustc/rustup')
else:
    active=run([tool('rustup'),'show','active-toolchain'],root/'rust')
    cargo_id=run([tool('cargo'),'--version'],root/'rust'); rustc_id=run([tool('rustc'),'--version'],root/'rust'); rustup_id=run([tool('rustup'),'--version'],root/'rust')
    if active['rc']!=0:
        results['cargo']={'status':'FAIL','rc':EVIDENCE_FAIL_RC,'failure_layer':'TOOLCHAIN_SELECTION_EVIDENCE','rustup_active_toolchain':active,'cargo_identity':cargo_id,'rustc_identity':rustc_id,'rustup_identity':rustup_id}
    else:
        build=run([tool('cargo'),'build','--workspace','--locked','--offline'],root/'rust')
        results['cargo']={**build,'status':'PASS' if build['rc']==0 else 'FAIL','rustup_active_toolchain':active,'cargo_identity':cargo_id,'rustc_identity':rustc_id,'rustup_identity':rustup_id}
# Explicit tool expectations are material only when not OBSERVE_AND_RECORD.
cfg=json.load(open(root/'closure_config.json')); expectation_fail=[]
for nm,expected in cfg.get('tool_expectations',{}).items():
    if expected=='OBSERVE_AND_RECORD': continue
    exe=sys.executable if nm=='python' else tool(nm)
    if not exe: expectation_fail.append({'tool':nm,'expected':expected,'observed':'UNAVAILABLE'}); continue
    pr=run([exe,'--version'],root); observed=(pr.get('stdout') or pr.get('stderr') or '').strip()
    if expected not in observed: expectation_fail.append({'tool':nm,'expected':expected,'observed':observed})
results['tool_expectations']={'status':'FAIL' if expectation_fail else 'PASS','mismatches':expectation_fail}
env_ok=relevant_env_ok(); results['relevant_environment']={'status':'PASS' if env_ok else 'FAIL','expected':'lab','observed':os.environ.get('MAKNOW_PROFILE')}
failed=[k for k,v in results.items() if v.get('status')=='FAIL']
# Confirmed failure dominates missing-runtime incompleteness.
if failed:
    overall='RECONSTRUCTION_FAIL'; rc=EVIDENCE_FAIL_RC
elif missing:
    overall='RUNTIME_EVIDENCE_INCOMPLETE'; rc=MISSING_RUNTIME_RC
else:
    overall='CLEAN_OFFLINE_RECONSTRUCTION_PASS'; rc=0
print(json.dumps({'mode':mode,'clean_state_removed':['js/node_modules','python/.venv','rust/target'],'network_install_permitted':False,'results':results,'missing_runtimes':missing,'confirmed_failures':failed,'overall':overall,'rc':rc},indent=2))
raise SystemExit(rc)
