from ops import *
import datetime,re

source=json.loads((PRIVATE/'selected-source.json').read_text())
sid=source['stream_id'];camera=int(source['source_id'])
assert not inspect('contextra-ai-gateway-1')['State']['Running']
assert not (HERE/'gpu-intent.json').exists(),'Resume from evidence, no blind retry'
before=api('/api/v1/streams/'+sid+'/settings')['configured']
save('canary-settings-before.json',before,private=True)
save('gpu-intent.json',dict(camera=camera,stream_id=sid,phases=['pose-fire','pose-fire-crowd']))

def secure(action,body):
    script='''import urllib.request,json,os,sys
from pathlib import Path
body=sys.stdin.buffer.read()
request=urllib.request.Request("http://127.0.0.1:5000/internal/secure-control/ACTION",data=body,headers={"Content-Type":"application/json","Authorization":"Bearer "+Path(os.environ["CONTEXTRA_CONTROL_TOKEN_FILE"]).read_text().strip()})
try:
 with urllib.request.urlopen(request,timeout=5) as r:print(r.read().decode())
except urllib.error.HTTPError as e:print(json.dumps({"http_status":e.code,"error":json.loads(e.read())}));raise SystemExit(1)
'''.replace('ACTION',action)
    return json.loads(cmd(['docker','exec','-i','contextra-analytics-1','python3','-B','-c',script],input=json.dumps(body),timeout=10))

def state():return api('/api/v1/streams/'+sid)

def stop():
    result=request('/api/v1/streams/'+sid+'/stop',{},timeout=80)
    deadline=time.monotonic()+80
    while True:
        s=state()
        if s['state']=='stopped' and not s.get('child_pid') and not s.get('cleanup_pending'):
            return {k:s.get(k) for k in ('state','child_pid','cleanup_pending','last_cleanup','cleanup_error')}
        if time.monotonic()>deadline:raise RuntimeError('CANARY_REAP_FAILED')
        time.sleep(1)

def body(phase):
    configs=[]
    for name in ('INTRUSION','LOITERING','FALL','FIGHT','FIRE','CROWD'):
        cfg=before[name.lower()]
        if name=='FIRE':cfg=cfg.get('scenario') or {}
        roi=cfg.get('roi') or []
        rois=[dict(label=cfg.get('roi_name',name+' ROI'),points=roi)] if roi else []
        enabled=name in ('FALL','FIRE') or (phase=='pose-fire-crowd' and name=='CROWD')
        configs.append(dict(scenarioCode=name,enabled=enabled,rois=rois,parameters={}))
    d=dict(cameraId=camera,rtspUrl=source['uri'],startWallTimeMs=time.time_ns()//1000000,rtpClockRate=90000,scenarios=configs)
    for k,v in [('recServerId','rec_server_id'),('recCameraId','rec_camera_id')]:
        if source.get(v):d[k]=source[v]
    return d

results=[]
for phase,keys in [('pose-fire',{'pose','fire'}),('pose-fire-crowd',{'pose','fire','crowd'})]:
    assert state()['state']=='stopped'
    save(phase+'-intent.json',dict(camera=camera,expected_providers=sorted(keys)))
    samples=[];outcome={'phase':phase,'pass':False}
    since=datetime.datetime.now(datetime.timezone.utc).isoformat()
    try:
        response=secure('start',body(phase));outcome['command']=response
        deadline=time.monotonic()+75;running_at=None
        while True:
            s=state()
            sample={k:s.get(k) for k in ('state','run_id','run_generation','child_pid','cleanup_pending','last_error','roles','scenarios','metadata_path','runtime_observation')}
            sample['sample_monotonic']=time.monotonic();samples.append(sample)
            if s['state']=='error':raise RuntimeError('CANARY_RUNTIME_ERROR')
            providers=s.get('metadata_path',{}).get('providers',{})
            if any(v['state']!='ACTIVE' for v in providers.values()):raise RuntimeError('CANARY_PROVIDER_UNAVAILABLE')
            if s['state']=='running' and set(providers)==keys and all(v['responses']>=3 for v in providers.values()):
                if running_at is None:running_at=time.monotonic()
                if time.monotonic()-running_at>=6:break
            if time.monotonic()>deadline:raise RuntimeError('CANARY_RESULT_TIMEOUT')
            time.sleep(1)
        outcome.update({'pass':True,'last':samples[-1]})
        print(json.dumps(dict(phase=phase,frame_flow=True,providers={k:{f:v[f] for f in ('responses','late','state')} for k,v in providers.items()})),flush=True)
    except Exception as e:
        outcome['error']=type(e).__name__+': '+str(e)[:160]
        print(json.dumps(dict(phase=phase,failed=True,error=outcome['error'])),flush=True)
    finally:
        save(phase+'-samples.json',samples)
        outcome['stop']=stop()
        p=subprocess.run(['docker','logs','--since',since,'--tail','300','contextra-analytics-1'],capture_output=True,text=True,timeout=10)
        safe=re.sub(r'rtsp[s]?://[^\s\"\']+','<RTSP_REDACTED>',p.stdout+p.stderr)
        (HERE/(phase+'-runtime.log')).write_text(safe[-40000:])
        save(phase+'-result.json',outcome);results.append(outcome)
    if not outcome['pass']:break
save('gpu-results.json',dict(phases=[{'phase':r['phase'],'pass':r['pass'],'error':r.get('error'),'stop':r['stop']} for r in results]))
print(json.dumps(dict(phases=[(r['phase'],r['pass']) for r in results],stopped=True)))
