import json, os, sys, time, hashlib, shutil, subprocess, signal, tarfile, tempfile
from pathlib import Path
import yaml
os.umask(0o077)
R=Path(__file__).parent; R.chmod(0o700)
REPO=Path('/workspace/contextra-analytics-runtime-lifecycle-refactor')
sys.path.insert(0,str(REPO))
from tools.stream_benchmark import load_config, preflight, Benchmark
from tools.stream_benchmark_runtime import Runtime, file_hash
OLD=Path('/workspace/contextra-runtime-config/test-run/medium6-batch4.yaml')
ACTIVE=Path('/workspace/contextra-runtime-config/metadata-active/compose.yaml')
ENV=Path('/workspace/contextra-runtime-config/runtime.env')
SOURCE=Path('/data/contextra/model-artifacts/builds/medium-193-20260909-v4/export')
TARGET=Path('/data/contextra/model-artifacts/deployments/medium-fp16-4471edc/RFDETRMedium')
def save(n,v):
    p=R/n;p.write_text(json.dumps(v,ensure_ascii=False,indent=2));p.chmod(0o600)
def read(n):return json.loads((R/n).read_text())
def cmd(a,t=30):
    p=subprocess.run(a,capture_output=True,text=True,timeout=t)
    if p.returncode:
        save('command-error-'+str(time.time_ns())+'.json',dict(args=a,code=p.returncode,stderr=p.stderr[-4000:]))
        raise RuntimeError('Command failed; details private')
    return p.stdout
def gate():
    assert not (R/'plan.json').exists()
    cfg=load_config(OLD);rt=Runtime(cfg,R);plan=preflight(rt)
    plan.update(head=cmd(['git','-C',str(REPO),'rev-parse','HEAD']).strip(),started=time.time(),inspect=rt.inspect(),
        old_config=cfg,redis_id=json.loads(cmd(['docker','inspect','contextra-redis-1']))[0]['Id'],
        sender=cmd(['python3',str(REPO/'tools/rtsp_test.py'),'list']),
        failure_policy='Stop all target channels, do not restore or restart',
        caps=dict(engine_seconds=900,image_seconds=900,free_disk_gib=15,host_available_gib=8,container_ram_gib=16),
        onnx_sha256=file_hash(SOURCE/'model.onnx'))
    for p,n in ((ACTIVE,'active.before'),(ENV,'env.before'),(OLD,'benchmark.before')):shutil.copy2(p,R/n)
    plan['persistent_hashes']={str(p):file_hash(p) for p in (ACTIVE,ENV,OLD)}
    save('plan.json',plan)
    print('Gate saved; 16 registered targets, instance6 baseline, existing source modifications task-owned')
def engine():
    assert not (R/'engine-start.json').exists()
    p=read('plan.json');name='codex-medium-fp16-20260916'
    a=['docker','run','--rm','--pull','never','--name',name,'--network','none','--gpus','device=0',
       '--cpus','4','--memory','12g','--memory-swap','12g','--ulimit','core=0',
       '-v',str(SOURCE)+':/input:ro','-v',str(R)+':/evidence','--entrypoint','timeout',p['image'],
       '900','/usr/bin/trtexec','--onnx=/input/model.onnx','--saveEngine=/evidence/medium-fp16.plan',
       '--fp16','--inputIOFormats=fp16:chw','--outputIOFormats=fp32:chw,fp32:chw',
       '--minShapes=input:1x3x576x576','--optShapes=input:4x3x576x576','--maxShapes=input:4x3x576x576',
       '--memPoolSize=workspace:1024','--skipInference']
    save('engine-start.json',dict(command=a,started=time.time()))
    with (R/'engine.log').open('x') as f:
        process=subprocess.Popen(a,stdout=f,stderr=f);start=time.monotonic()
        while process.poll() is None:
            if time.monotonic()-start>920 or shutil.disk_usage('/var/lib/docker').free<15*1024**3:
                cmd(['docker','stop','-t','5',name]);process.wait(20);raise RuntimeError('Engine bound')
            time.sleep(2)
    assert process.returncode==0
    save('engine.json',dict(sha256=file_hash(R/'medium-fp16.plan'),elapsed=time.monotonic()-start,profile=[1,4,4],input='FP16',outputs='FP32',quality='not evaluated'))
    print('Engine ready')
def image():
    assert not (R/'image-start.json').exists()
    paths=cmd(['git','-C',str(REPO),'diff','--name-only']).splitlines()
    paths+=cmd(['git','-C',str(REPO),'ls-files','--others','--exclude-standard']).splitlines()
    hashes={p:file_hash(REPO/p) for p in paths};digest=hashlib.sha256(json.dumps(hashes,sort_keys=True).encode()).hexdigest()[:8]
    tag='contextra-analytics:medium-fp16-4471edc-'+digest
    snapshot=R/'snapshot';snapshot.mkdir()
    child=subprocess.Popen(['git','-C',str(REPO),'archive',read('plan.json')['head']],stdout=subprocess.PIPE)
    with tarfile.open(fileobj=child.stdout,mode='r|') as t:t.extractall(snapshot,filter='data')
    assert child.wait()==0
    for p in paths:(snapshot/p).parent.mkdir(parents=True,exist_ok=True);shutil.copy2(REPO/p,snapshot/p)
    save('image-start.json',dict(hashes=hashes,tag=tag,started=time.time()))
    with (R/'image.log').open('x') as f:
        p=subprocess.Popen(['docker','build','--pull=false','--network=default','-f','docker/Dockerfile','--target','app','-t',tag,'.'],
            cwd=snapshot,env=dict(os.environ,DOCKER_BUILDKIT='1'),stdout=f,stderr=f,start_new_session=True);start=time.monotonic()
        while p.poll() is None:
            if time.monotonic()-start>900 or shutil.disk_usage('/var/lib/docker').free<15*1024**3:
                os.killpg(p.pid,signal.SIGTERM);p.wait(20);raise RuntimeError('Image bound')
            time.sleep(2)
    assert p.returncode==0
    image_id=json.loads(cmd(['docker','image','inspect',tag]))[0]['Id']
    save('image.json',dict(image=image_id,tag=tag,hashes=hashes,elapsed=time.monotonic()-start))
    print('Image ready',tag)
if __name__=='__main__':globals()[sys.argv[1]]()
