from ops import *
import urllib.request
import yaml

def stopped(rows):
    return 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)

rows=api('/api/v1/streams')['streams']
assert stopped(rows),'TRIAL_NOT_REAPED'
original=yaml.safe_load((PRIVATE/'streams.yaml').read_text())['added']
saved=json.loads((PRIVATE/'settings-before-trial.json').read_text())
for camera,old in saved.items():
    spec=next(s for s in original if str(s.get('camera_id'))==camera)
    now=api('/api/v1/streams/'+spec['stream_id']+'/settings')['configured']
    a={k:v for k,v in old.items() if k!='revision'}
    b={k:v for k,v in now.items() if k!='revision'}
    if 'scenario' not in a['fire'] and 'scenario' in b['fire']:
        assert not b['fire']['scenario']['enabled']
        del b['fire']['scenario']
    assert a==b,'SETTINGS_RESTORE_'+camera
assert json.loads(cmd(compose()+['config','--format','json']))==json.loads((PRIVATE/'compose.json').read_text())
old=json.loads((PRIVATE/'training.json').read_text());now=training()
assert all(old[k]==now[k] for k in ('configured','effective','revision'))
base=json.loads((HERE/'baseline.json').read_text())
drift=[]
for name,old in base['containers'].items():
    c=inspect(name)
    if name=='contextra-ai-gateway-1':continue
    if c['Id']!=old['id'] or c['Image']!=old['image'] or c['State']['StartedAt']!=old['started']:
        drift.append(name)
assert not drift,'UNEXPECTED_CONTAINER_CHANGE'
ready=api('/ready')
models=json.loads(cmd(['docker','exec','contextra-analytics-1','python3','-B','-c',
    'import urllib.request,json;names=["RFDETRMedium","RFDETRKeypointPreview","RFDETRFireSmoke","Awiros_person_and_head_detection"];print(json.dumps({n:urllib.request.urlopen("http://127.0.0.1:8000/v2/models/"+n+"/ready",timeout=3).status for n in names}))'],timeout=20))
assert all(v==200 for v in models.values())
cmd(['docker','start','contextra-ai-gateway-1'],timeout=30)
restarted=[]
for _ in range(5):
    time.sleep(3)
    rows=api('/api/v1/streams')['streams']
    if not stopped(rows):
        restarted=[r['stream_id'] for r in rows if r['state']!='stopped']
        cmd(['docker','stop','--timeout','15','contextra-ai-gateway-1'],timeout=30)
        for sid in restarted:
            req=urllib.request.Request('http://127.0.0.1:35000/api/v1/streams/'+sid+'/stop',data=b'{}',
                method='POST',headers={'Content-Type':'application/json'})
            with urllib.request.urlopen(req,timeout=20) as r:r.read(65536)
        end=time.monotonic()+120
        while True:
            rows=api('/api/v1/streams')['streams']
            if stopped(rows):break
            if time.monotonic()>end:raise RuntimeError('EXTERNAL_START_REAP_BOUND')
            time.sleep(1)
        break
code='''import urllib.request,json,os
from pathlib import Path
r=urllib.request.Request("http://localhost:8080/internal/bridge-status",headers={"Authorization":"Bearer "+Path(os.environ["CONTEXTRA_CONTROL_TOKEN_FILE"]).read_text().strip()})
print(json.dumps(json.load(urllib.request.urlopen(r,timeout=3))))'''
bridge=json.loads(cmd(['docker','exec','contextra-ai-control-sidecar-1','python3','-B','-c',code],timeout=6))
artifact=api('/api/v1/metadata/status')['artifacts']
state=dict(models=models,ready=ready,channels=len(rows),states={'stopped':len(rows)},children=0,
    gateway_running=inspect('contextra-ai-gateway-1')['State']['Running'],wss_connected=bridge['wss']['connected'],
    blocked_external_start=bool(restarted),settings_restored=True,training_restored=True,compose_unchanged=True,
    images={n:inspect(n)['Image'] for n in ('contextra-analytics-1','contextra-ai-control-sidecar-1')},
    container_drift=drift,root_free_bytes=shutil.disk_usage('/').free,
    artifact={k:artifact.get(k) for k in ('enabled','http_enabled','rendering','active','error')})
save('final-state.json',state)
print(json.dumps(state),flush=True)
