from ops import *
import urllib.request
import yaml

def post(path):
    request = urllib.request.Request('http://127.0.0.1:35000'+path,data=b'{}',
        headers={'Content-Type':'application/json'},method='POST')
    with urllib.request.urlopen(request,timeout=20) as r:
        return json.load(r)

images_doc = json.loads(sorted(HERE.glob('images-*.json'))[-1].read_text())
images = images_doc['images']
assert all(hashlib.sha256((REPO/p).read_bytes()).hexdigest()==h for p,h in images_doc['hashes'].items())
assert shutil.disk_usage('/').free > 15*1024**3
expected = json.loads((PRIVATE/'compose.json').read_text())
assert json.loads(cmd(compose()+['config','--format','json'])) == expected, 'COMPOSE_DRIFT'
before = api('/api/v1/streams')['streams']
save('transition-intent.json',dict(images=images,channels=[r['stream_id'] for r in before if r['state']!='stopped']))
cmd(['docker','stop','--time','15','contextra-ai-gateway-1'],timeout=30)
for row in before:
    if row['state'] != 'stopped' or row.get('child_pid') or row.get('cleanup_pending'):
        post('/api/v1/streams/'+row['stream_id']+'/stop')
end = time.monotonic()+120
while True:
    rows = api('/api/v1/streams')['streams']
    if all(r['state']=='stopped' and not r.get('child_pid') and not r.get('cleanup_pending') and
           not any(p.get('child_pid') for p in r.get('provider_processes',{}).values()) for r in rows):
        break
    if time.monotonic() > end:
        raise RuntimeError('REAP_BOUND')
    time.sleep(1)
save('pre-reflect-stopped.json',[{k:r.get(k) for k in ('stream_id','state','last_cleanup')} for r in rows])
end = time.monotonic()+90
while True:
    status = api('/api/v1/metadata/status')['artifacts']
    if not status.get('active') and not status.get('ledger',{}).get('pending'):
        break
    if time.monotonic() > end:
        raise RuntimeError('ARTIFACT_DRAIN_BOUND')
    time.sleep(2)
envfile = Path('/workspace/contextra-runtime-config/web-start-candidate-20260918/candidate.env')
assert envfile.read_bytes() == (PRIVATE/'candidate.env').read_bytes(), 'ENV_DRIFT'
values = dict(CONTEXTRA_SECURE_ANALYTICS_IMAGE=images['analytics']['id'],CONTEXTRA_SIDECAR_IMAGE=images['sidecar']['id'])
lines = envfile.read_text().splitlines()
for key, value in values.items():
    assert sum(line.startswith(key+'=') for line in lines) == 1
    lines = [key+'='+value if line.startswith(key+'=') else line for line in lines]
envfile.write_text('\n'.join(lines)+'\n')
state_dir = Path('/workspace/contextra-runtime-config/crowd-events-20260928-r01')
state_dir.mkdir(mode=0o700)
flags = dict(CONTEXTRA_TEST_NO_CAPTURE='false',CONTEXTRA_PIPELINE_DIAGNOSTICS='false',
             CONTEXTRA_INPUT_TIMING_SECONDS='0',CONTEXTRA_SOURCE_LATEST_TRIAL='true',
             CONTEXTRA_COMPLETED_QUEUE_MAX_AGE_SECONDS='1')
overlay = state_dir/'runtime.yaml'
overlay.write_text(yaml.safe_dump({'services':{'analytics':{'image':images['analytics']['id'],'environment':flags},
    'ai-control-sidecar':{'image':images['sidecar']['id']}}}))
overlay.chmod(0o600)
command = compose()+['-f',str(overlay)]
save('compose-current-command.json',command,private=True)
cmd(['docker','stop','--signal=SIGTERM','--timeout=-1','contextra-analytics-1'],timeout=90)
assert inspect('contextra-analytics-1')['State']['ExitCode'] == 0
cmd(command+['up','-d','--no-deps','--no-build','--pull','never','analytics','ai-control-sidecar'],timeout=150)
end = time.monotonic()+150
while True:
    try:
        ready = api('/ready')
        assert inspect('contextra-ai-control-sidecar-1')['State'].get('Health',{}).get('Status')=='healthy'
        break
    except Exception:
        if time.monotonic() > end:
            raise RuntimeError('READINESS_BOUND')
        time.sleep(2)
for kind, name in [('analytics','contextra-analytics-1'),('sidecar','contextra-ai-control-sidecar-1')]:
    assert inspect(name)['Image']==images[kind]['id']
env = dict(line.split('=',1) for line in inspect('contextra-analytics-1')['Config']['Env'])
assert all(env.get(key)==value for key,value in flags.items())
save('reflect-done.json',dict(images=images,flags=flags,ready=ready,channels=len(rows),gateway_stopped=True))
print(json.dumps(dict(ready=ready,images=images,gateway='stopped')),flush=True)
