from ops import *

policy={'CONTEXTRA_FALL_DEMO_DIRECT_DOWN':'true',
 'CONTEXTRA_FALL_DIRECT_DOWN_COOLDOWN_SECONDS':'2.0',
 'CONTEXTRA_FIGHT_CURRENT_FRAME_FALLBACK':'false',
 'CONTEXTRA_FIGHT_EVENT_COOLDOWN_SECONDS':'2.0',
 'CONTEXTRA_FALL_SKIP_DOWN_HOLD_DEFAULT':'true'}
image=json.loads((HERE/'image.json').read_text())
before=json.loads((HERE/'containers-before.json').read_text())
expected=json.loads((PRIVATE/'compose-before.json').read_text())
expected['services']['analytics']['image']=image['id']
expected['services']['analytics']['environment'].update(policy)
actual=json.loads(cmd(compose()+['config','--format','json']))
assert actual==expected,'Unexpected rendered Compose difference'
save('compose-after.json',actual,private=True)
assert shutil.disk_usage('/').free>15*1024**3
assert not inspect('contextra-ai-gateway-1')['State']['Running']
no_channels()
assert ledger()==json.loads((HERE/'ledger-quiescent.json').read_text())
save('reflect-intent.json',dict(image=image,policy=policy,targets=['analytics','ai-gateway']))
try:
    cmd(['docker','stop','--signal=SIGTERM','--timeout=-1','contextra-analytics-1'],timeout=90)
    assert not inspect('contextra-analytics-1')['State']['Running']
    cmd(compose()+['up','-d','--no-deps','--no-build','--pull','never','analytics'],timeout=150)
    deadline=time.monotonic()+120
    while True:
        try:ready=api('/ready');break
        except Exception:
            if time.monotonic()>deadline:raise RuntimeError('ANALYTICS_READINESS_TIMEOUT')
            time.sleep(2)
    c=inspect('contextra-analytics-1');assert c['Image']==image['id']
    env=dict(x.split('=',1) for x in c['Config']['Env'])
    assert all(env[k]==v for k,v in policy.items())
    paths=json.loads((HERE/'build-intent.json').read_text())['hashes']
    source_script='import hashlib,json;from pathlib import Path;print(json.dumps({p:hashlib.sha256((Path("/opt/contextra")/p).read_bytes()).hexdigest() for p in '+repr(list(paths))+'}))'
    assert json.loads(cmd(['docker','exec','contextra-analytics-1','python3','-c',source_script]))==paths
    model_script='''import urllib.request,json
print(json.dumps({m:urllib.request.urlopen("http://127.0.0.1:8000/v2/models/"+m+"/ready",timeout=3).status for m in ["RFDETRMedium","RFDETRKeypointPreview","RFDETRFireSmoke","Awiros_person_and_head_detection"]}))'''
    models=json.loads(cmd(['docker','exec','contextra-analytics-1','python3','-c',model_script]))
    rows=no_channels();settings=[]
    old=json.loads((PRIVATE/'settings-before.json').read_text())
    migration=json.loads((HERE/'migration.json').read_text())
    explicit={r['stream_id']:r['skip_hold'] for r in migration['changed']}
    for row in rows:
        sid=row['stream_id'];s=api('/api/v1/streams/'+sid+'/settings')['configured']
        assert all(s['fall'][k]==v for k,v in dict(person_score=.15,joint_score=.10,down_angle_deg=50.).items())
        assert all(s['fight'][k]==v for k,v in dict(person_score=.15,joint_score=.10,pair_distance_ratio=1.8,pair_close_hold_seconds=.1,arm_motion_window_seconds=1.,arm_motion_ratio=.1,toward_opponent_ratio=.01,required_bursts=1).items())
        assert s['fall']['test_skip_down_hold']==explicit.get(sid,True)
        for section in ('fall','fight'):
            for key in ('enabled','roi'):
                assert s[section][key]==old[sid][section][key]
        settings.append(dict(stream_id=sid,fall=s['fall'],fight=s['fight']))
    save('settings-after.json',settings,private=True)
    artifacts=api('/api/v1/metadata/status')['artifacts']
    assert artifacts['enabled'] and artifacts['http_enabled']
    cmd(['docker','start','contextra-ai-gateway-1'],timeout=30)
    cmd(['docker','exec','contextra-ai-gateway-1','nginx','-t','-c','/tmp/nginx.conf'])
    bridge_script='''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()})
b=json.load(urllib.request.urlopen(r,timeout=3))
print(json.dumps({"wss":{k:v for k,v in b["wss"].items() if k in ("connected","active","bootstrap_count","ping_count","pong_count")}}))'''
    deadline=time.monotonic()+90
    while True:
        bridge=json.loads(cmd(['docker','exec','contextra-ai-control-sidecar-1','python3','-c',bridge_script]))
        if bridge['wss'].get('connected') or bridge['wss'].get('active'):break
        if time.monotonic()>deadline:raise RuntimeError('WSS_RECONNECT_TIMEOUT')
        time.sleep(3)
    no_channels()
    current_ledger=ledger();assert current_ledger==json.loads((HERE/'ledger-quiescent.json').read_text())
    preserved={}
    for name,old_container in before.items():
        if name in ('contextra-analytics-1','contextra-ai-gateway-1'):continue
        c=inspect(name)
        preserved[name]=(c['Id']==old_container['id'] and c['Image']==old_container['image'] and c['State']['StartedAt']==old_container['started'])
    assert all(preserved.values())
    save('reflected.json',dict(image=image,ready=ready,models=models,bridge=bridge,
        channels_stopped=len(rows),policy=policy,artifacts={k:artifacts[k] for k in ('enabled','http_enabled')},
        ledger=current_ledger,non_target_preserved=preserved,source_hash_match=True,compose_exact=True,
        configured_defaults_checked=len(rows),explicit_skip_off=sum(v is False for v in explicit.values())))
    print(json.dumps(dict(image=image['tag'],models=models,wss=bridge,stopped=len(rows),non_target_preserved=all(preserved.values()))))
except Exception as exc:
    save('reflection-failed.json',dict(error=type(exc).__name__,reason=str(exc)[:180]))
    for name in ('contextra-ai-gateway-1','contextra-analytics-1'):
        if inspect(name)['State']['Running']:
            cmd(['docker','stop','--signal=SIGTERM','--timeout=-1',name],timeout=90)
    raise
