import importlib.util
import json
import os
from pathlib import Path
import shutil
import subprocess
import sys
import time
import hashlib
import tarfile
import signal

os.umask(0o077)
R = Path(__file__).parent
R.chmod(0o700)
s = importlib.util.spec_from_file_location('previous_ops', '/tmp/codex/ltb1021/2026-09-15_contextra-tracking-fix_r01/ops.py')
p = importlib.util.module_from_spec(s)
s.loader.exec_module(p)
p.R = R
p.CHANNELS['rtsp-test-source-03'] = 'web-cam01-test'
REPO = p.REPO
POSE = Path('/tmp/codex/ltb1021/2026-09-13_contextra-pose_r01')
FILES = ['app/medium_preprocess.py','app/source_adapters/rtsp.py','app/multi_pipeline.py',
         'app/stream_lifecycle.py','app/stream_manager.py','app/frame_ingress.py',
         'tests/test_medium_preprocess.py','tests/test_rtsp_source_adapter.py','tests/test_pose_batch_path.py']

def gate():
    p.gate()
    plan = p.read('plan.json')
    assert plan['head'] == 'c869cc4acf6e7bc26062af0235528e381f1ba3f0'
    plan['caps'].update(runtime_seconds=2100, analytics_ram_gib=16, gpu_mib=40000,
        min_host_available_gib=8, sample_seconds=[20,20,20,60], build_seconds=600,
        model_build_cpu=4, model_build_ram_gib=12, model_gpu_delta_mib=8192)
    plan['deadline'] = time.time()+1900
    plan['restore'] = 'Restore three original configurations/running states; remove only task temporary registrations after cleanup; preserve sender/Redis/other services.'
    p.save('plan.json',plan)
    print('Mounts',[(v['Source'],v['Destination']) for v in plan['analytics']['Mounts']])
    print('States',[(k,v['state']) for k,v in plan['states'].items()])
    print('Pose ONNX bytes', (POSE/'export/rfdetr-keypoint-preview.onnx').stat().st_size)

def engine():
    assert not (R/'engine-start.json').exists()
    plan=p.read('plan.json'); source=POSE/'export/rfdetr-keypoint-preview.onnx'
    expected=json.loads((POSE/'export-result.json').read_text())['onnx']['fingerprint']['sha256']
    assert p.sha(source)==expected
    image=plan['analytics']['Image']; name='codex-ltb1021-pose-batch-20260916'
    args=['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/model.onnx:ro','-v',str(R)+':/evidence','--entrypoint','timeout',image,'600',
          '/usr/bin/trtexec','--onnx=/input/model.onnx','--saveEngine=/evidence/pose-b4.plan','--fp16',
          '--minShapes=input:1x3x576x576','--optShapes=input:4x3x576x576','--maxShapes=input:4x3x576x576',
          '--memPoolSize=workspace:1024','--skipInference']
    baseline=p.base.g.gpu(); p.save('engine-start.json',dict(at=time.time(),command=args,onnx_sha=expected,gpu_before=baseline))
    started=time.monotonic(); limited=False
    with (R/'engine.log').open('x') as log:
        child=subprocess.Popen(args,stdout=log,stderr=log)
        while child.poll() is None:
            if time.monotonic()-started>620 or shutil.disk_usage('/var/lib/docker').free<15*1024**3 or p.base.g.gpu()-baseline>8192:
                limited=True;subprocess.run(['docker','kill',name],capture_output=True,timeout=15);break
            time.sleep(3)
        child.wait(timeout=20)
    p.save('engine.json',dict(exit=child.returncode,limited=limited,elapsed=time.monotonic()-started,
        sha=p.sha(R/'pose-b4.plan') if child.returncode==0 else None))
    print(json.dumps(p.read('engine.json')))

def build():
    assert p.read('pose-source2.json')['success']
    assert not (R/'image-start.json').exists()
    hashes={name:p.sha(REPO/name) for name in FILES}
    digest=hashlib.sha256(json.dumps(hashes,sort_keys=True).encode()).hexdigest()[:8]
    tag='contextra-analytics:pose-batch-c869cc4-'+digest
    snap=R/'snapshot';snap.mkdir()
    proc=subprocess.Popen(['git','archive',p.read('plan.json')['head']],cwd=REPO,stdout=subprocess.PIPE)
    with tarfile.open(fileobj=proc.stdout,mode='r|') as archive:archive.extractall(snap,filter='data')
    assert proc.wait()==0
    for name in FILES:shutil.copyfile(REPO/name,snap/name)
    p.save('image-start.json',dict(tag=tag,hashes=hashes,at=time.time()))
    started=time.monotonic()
    with (R/'image-build.log').open('x') as log:
        child=subprocess.Popen(['docker','build','--pull=false','--network=default','-f','docker/Dockerfile','--target','app','-t',tag,'.'],
            cwd=snap,env=dict(os.environ,DOCKER_BUILDKIT='1'),stdout=log,stderr=log,start_new_session=True)
        while child.poll() is None:
            if time.monotonic()-started>600 or shutil.disk_usage('/var/lib/docker').free<15*1024**3:
                os.killpg(child.pid,signal.SIGTERM);child.wait(timeout=20);raise RuntimeError('BUILD_LIMIT')
            time.sleep(2)
    assert child.returncode==0
    image=json.loads(p.cmd(['docker','image','inspect',tag]))[0]['Id']
    p.save('image.json',dict(image=image,tag=tag,hashes=hashes,elapsed=time.monotonic()-started))
    p.tests('pose-image',image)
    print('Image built and tested',tag,flush=True)

if __name__=='__main__':
    {'gate':gate,'engine':engine,'build':build}[sys.argv[1]]()
