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

HERE=Path(__file__).parent
REPO=Path('/workspace/contextra-analytics-runtime-lifecycle-refactor')
PRIVATE=Path('/workspace/contextra-runtime-config')
os.umask(0o077)
def cmd(args,timeout=30):
    return subprocess.run(args,text=True,capture_output=True,check=True,timeout=timeout).stdout
def save(name,value):
    with (HERE/name).open('x') as out:json.dump(value,out,indent=2)
assert cmd(['git','-C',str(REPO),'rev-parse','HEAD']).strip()=='2d67e2db28a1422463b76c23089c27dccd017af5'
assert shutil.disk_usage('/var/lib/docker').free>15*1024**3
if len(sys.argv)>1 and sys.argv[1]=='finish-config':
    image_data=json.loads((HERE/'image.json').read_text())
    tag,image=image_data['tag'],image_data['id']
    hashes=json.loads((HERE/'build-intent.json').read_text())['hashes']
    for p,h in hashes.items():
        assert hashlib.sha256((REPO/p).read_bytes()).hexdigest()==h, 'Built source drift'
    code=Path(__file__).read_text().split("# CONFIG_ONLY_RESUME\n",1)[1]
    exec(compile(code,__file__,'exec'))
    raise SystemExit(0)
paths=cmd(['git','-C',str(REPO),'diff','--name-only']).splitlines()+cmd(['git','-C',str(REPO),'ls-files','--others','--exclude-standard']).splitlines()
hashes={p:hashlib.sha256((REPO/p).read_bytes()).hexdigest() for p in paths}
digest=hashlib.sha256(json.dumps(hashes,sort_keys=True).encode()).hexdigest()[:8]
snapshot=HERE/'snapshot';snapshot.mkdir()
proc=subprocess.Popen(['git','-C',str(REPO),'archive','HEAD'],stdout=subprocess.PIPE)
with tarfile.open(fileobj=proc.stdout,mode='r|') as archive:archive.extractall(snapshot,filter='data')
assert proc.wait()==0
for p in paths:
    target=snapshot/p;target.parent.mkdir(parents=True,exist_ok=True)
    shutil.copy2(REPO/p,target)
tag='contextra-analytics:web-start-2d67e2d-'+digest
save('build-intent.json',dict(tag=tag,hashes=hashes,head='2d67e2db28a1422463b76c23089c27dccd017af5',
    network='default',pull=False,timeout_s=900,min_disk_gib=15))
with (HERE/'build.log').open('x') as log:
    proc=subprocess.Popen(['docker','build','--pull=false','--network=default','--target','app',
        '-f','docker/Dockerfile','-t',tag,'.'],cwd=snapshot,stdout=log,stderr=log,start_new_session=True)
    start=time.monotonic()
    while proc.poll() is None:
        if time.monotonic()-start>900 or shutil.disk_usage('/var/lib/docker').free<15*1024**3:
            os.killpg(proc.pid,signal.SIGTERM);proc.wait(20);raise RuntimeError('BUILD_RESOURCE_BOUND')
        time.sleep(2)
assert proc.returncode==0,'BUILD_FAILED'
image=json.loads(cmd(['docker','image','inspect',tag]))[0]['Id']
save('image.json',dict(tag=tag,id=image))
print('BUILT',tag,image,flush=True)
# CONFIG_ONLY_RESUME
candidate=PRIVATE/'web-start-candidate-20260918';candidate.mkdir(mode=0o700,exist_ok=True)
for name in ('medium','pose','fire'):
    target=candidate/f'{name}.json'
    assert not target.exists()
    shutil.copy2(REPO/f'configs/control_templates/{name}.json',target)
    (candidate/f'{name}.json').chmod(0o600)
env=candidate/'candidate.env'
env.write_text('CONTEXTRA_SECURE_ANALYTICS_IMAGE='+image+'\nCONTEXTRA_CONTROL_TEMPLATE_SOURCE='+str(candidate/'medium.json')+'\nCONTEXTRA_WEB_ARTIFACT_ENABLED=1\n')
env.chmod(0o600)
common=['docker','compose','-p','contextra','--project-directory',str(REPO),
    '--env-file',str(PRIVATE/'runtime.env'),'--env-file',str(PRIVATE/'secure-bridge.env')]
base=['-f',str(REPO/'docker-compose.yml'),'-f',str(PRIVATE/'metadata-active/compose.yaml'),'-f',str(REPO/'deploy/secure-bridge.compose.yml')]
before=json.loads(cmd(common+base+['config','--format','json']))
after=json.loads(cmd(common+['--env-file',str(env)]+base+['-f',str(REPO/'deploy/web-start.compose.yml'),'config','--format','json']))
a=after['services']['analytics'];b=before['services']['analytics']
assert a['image']==image
allowed={'CONTEXTRA_CONTROL_TEMPLATE_FILE','CONTEXTRA_EVENT_ARTIFACT_ENABLED','CONTEXTRA_ARTIFACT_HTTP_ENABLED','CONTEXTRA_ARTIFACT_DELIVERY_CAMERAS'}
assert {k:v for k,v in a['environment'].items() if k not in allowed}=={k:v for k,v in b['environment'].items() if k not in allowed}
assert a['environment']['CONTEXTRA_EVENT_ARTIFACT_ENABLED']=='1'
assert a['environment']['CONTEXTRA_ARTIFACT_DELIVERY_CAMERAS']==''
assert a['environment']['CONTEXTRA_EVENT_ENDPOINT']=='http://ai-control-sidecar:8080/internal/events'
assert after['services']['ai-control-sidecar']['environment']['AI_CONTROL_MANAGER_BASE_URL']=='https://192.168.50.101'
vol=lambda service:{v['target']:v for v in service['volumes']}
av,bv=vol(a),vol(b)
assert {k:v for k,v in av.items() if k!='/run/bridge-config/camera-template.json'}==bv
assert av['/run/bridge-config/camera-template.json']['read_only']
assert all(after['services'][k]==v for k,v in before['services'].items() if k!='analytics')
assert {k:v for k,v in a.items() if k not in ('image','environment','volumes')}=={k:v for k,v in b.items() if k not in ('image','environment','volumes')}
save('prepared.json',dict(image=tag,image_id=image,candidate_env=str(env),template=str(candidate/'medium.json'),
    artifact_enabled=True,delivery_cameras=None,event_endpoint='https://192.168.50.101/api/v1/events',
    compose_checks='PASS; only analytics image, template mount and approved environment differ',
    other_services_unchanged=True,source_hashes=hashes,
    template_hashes={name:hashlib.sha256((candidate/f'{name}.json').read_bytes()).hexdigest() for name in ('medium','pose','fire')},
    actual_runtime_reflected=False))
print('PREPARED',str(env),flush=True)
