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

spec=importlib.util.spec_from_file_location('old_prepare','/tmp/codex/ltb1021/2026-09-20_contextra-fall-ready_r01/prepare.py')
old=importlib.util.module_from_spec(spec);spec.loader.exec_module(old)
cmd,compose,api,inspect,ledger,REPO=old.cmd,old.compose,old.api,old.inspect,old.ledger,old.REPO
HERE=Path(__file__).parent
os.umask(0o077)
def save(name,data):
    with (HERE/name).open('x') as f:json.dump(data,f,indent=2)
def stopped():
    s=api('/api/v1/streams')['streams']
    assert all(x['state']=='stopped' and not x.get('child_pid') and not x.get('cleanup_pending') for x in s)
    return s

if __name__=='__main__':
    assert not (HERE/'build-intent.json').exists()
    head=cmd(['git','-C',str(REPO),'rev-parse','HEAD']).strip()
    assert head=='f9cc52f69503221ebf3c0e5f874f9f307da90112'
    states=stopped();assert shutil.disk_usage('/').free>15*1024**3
    names=cmd(['docker','ps','-a','--format','{{.Names}}']).splitlines()
    save('before.json',dict(containers={n:{k:inspect(n)[k] for k in ('Id','Image','State')} for n in names},
         ledger=ledger(),channels=len(states)))
    config=json.loads(cmd(compose()+['config','--format','json']));save('compose-before.json',config)
    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()[:10]
    save('build-intent.json',dict(head=head,hashes=hashes,digest=digest))
    snap=HERE/'snapshot';snap.mkdir()
    p=subprocess.Popen(['git','-C',str(REPO),'archive',head],stdout=subprocess.PIPE)
    with tarfile.open(fileobj=p.stdout,mode='r|') as a:a.extractall(snap,filter='data')
    assert p.wait()==0
    for path in paths:
        dest=snap/path;dest.parent.mkdir(parents=True,exist_ok=True);shutil.copy2(REPO/path,dest)
    images={}
    for name,context,dockerfile,extra in [('analytics','.', 'docker/Dockerfile',['--target','app']),
        ('sidecar','control_sidecar','control_sidecar/Dockerfile',['--build-arg','SIDECAR_BASE_IMAGE='+config['services']['ai-control-sidecar']['build']['args']['SIDECAR_BASE_IMAGE']])]:
        tag='contextra-'+name+':fight-crowd-'+digest
        with (HERE/(name+'-build.log')).open('x') as log:
            proc=subprocess.Popen(['docker','build','--pull=false','--network=none','-f',dockerfile,'-t',tag,*extra,context],cwd=snap,stdout=log,stderr=log,start_new_session=True)
            start=time.monotonic();lowest=shutil.disk_usage('/').free
            while proc.poll() is None:
                lowest=min(lowest,shutil.disk_usage('/').free)
                if lowest<15*1024**3 or time.monotonic()-start>900:
                    os.killpg(proc.pid,signal.SIGTERM);proc.wait(20);raise RuntimeError('BUILD_RESOURCE_BOUND')
                time.sleep(2)
        assert proc.returncode==0,name+' build failed'
        images[name]=dict(tag=tag,id=json.loads(cmd(['docker','image','inspect',tag]))[0]['Id'],lowest_root_bytes=lowest)
        save(name+'-image.json',images[name]);print(json.dumps({name:images[name]}),flush=True)
    save('images.json',images)
