import copy
import hashlib
import json
import time
from prepare import HERE,REPO,api,cmd,compose,inspect,ledger,save,stopped

images=json.loads((HERE/'images.json').read_text())
before=json.loads((HERE/'before.json').read_text())
intent=json.loads((HERE/'build-intent.json').read_text())
for p,h in intent['hashes'].items():
    assert hashlib.sha256((REPO/p).read_bytes()).hexdigest()==h,'Source changed after snapshot: '+p
original=json.loads((HERE/'compose-before.json').read_text())
config=json.loads(cmd(compose()+['config','--format','json']))
expected=copy.deepcopy(original)
expected['services']['analytics']['image']=images['analytics']['id']
expected['services']['ai-control-sidecar']['image']=images['sidecar']['id']
mount=dict(type='bind',source='/data/contextra/model-artifacts/deployments/crowd-512-f9cc52f/Awiros_person_and_head_detection',
           target='/opt/contextra/models/triton/Awiros_person_and_head_detection',read_only=True,bind=dict(create_host_path=False))
expected['services']['analytics']['volumes'].append(mount)
for obj in (config,expected):
    obj['services']['analytics']['volumes'].sort(key=lambda x:x['target'])
assert config==expected,'Unexpected Compose drift'
assert config['services']['analytics']['environment']['CONTEXTRA_EVENT_ARTIFACT_ENABLED']=='1'
assert config['services']['analytics']['environment']['CONTEXTRA_ARTIFACT_HTTP_ENABLED']=='1'
assert config['services']['ai-gateway']['environment']['AI_ARTIFACT_ACCESS_POLICY']=='lan_public'
states=stopped();assert ledger()==before['ledger'] and not before['ledger']['reservations']
targets=['contextra-ai-gateway-1','contextra-ai-control-sidecar-1','contextra-analytics-1']
save('reflect-intent.json',dict(targets=targets,images=images,mount=mount,channels_stopped=len(states)))
changed=[]
try:
    for name in targets:
        assert inspect(name)['Id']==before['containers'][name]['Id']
        changed.append(name)
        cmd(['docker','stop','--signal=SIGTERM','--timeout=-1',name],timeout=90)
        assert not inspect(name)['State']['Running']
        print('STOPPED',name,flush=True)
    cmd(compose()+['up','-d','--no-deps','--no-build','--pull','never','analytics'],timeout=150)
    deadline=time.monotonic()+120
    while time.monotonic()<deadline:
        try:
            ready=api('/ready');break
        except Exception:time.sleep(2)
    else:raise RuntimeError('ANALYTICS_READINESS_TIMEOUT')
    model_script='''import urllib.request,json
print(json.dumps({m:urllib.request.urlopen("http://localhost: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','-B','-c',model_script]))
    cmd(compose()+['up','-d','--no-deps','--no-build','--pull','never','ai-control-sidecar'],timeout=90)
    deadline=time.monotonic()+40
    while time.monotonic()<deadline:
        if inspect('contextra-ai-control-sidecar-1')['State'].get('Health',{}).get('Status')=='healthy':break
        time.sleep(2)
    else:raise RuntimeError('SIDECAR_READINESS_TIMEOUT')
    cmd(compose()+['up','-d','--no-deps','--no-build','--pull','never','ai-gateway'],timeout=90)
    cmd(['docker','exec','contextra-ai-gateway-1','nginx','-t','-c','/tmp/nginx.conf'])
    script='''import os,json,urllib.request
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))))'''
    deadline=time.monotonic()+45
    while time.monotonic()<deadline:
        bridge=json.loads(cmd(['docker','exec','contextra-ai-control-sidecar-1','python3','-B','-c',script]))
        if bridge['wss'].get('connected') or bridge['wss'].get('active'):break
        time.sleep(3)
    else:raise RuntimeError('WSS_NOT_CONNECTED')
    states=stopped();artifacts=api('/api/v1/metadata/status')['artifacts']
    assert artifacts['enabled'] and artifacts['http_enabled']
    assert ledger()==before['ledger']
    preserved={}
    for name,old in before['containers'].items():
        if name in targets:continue
        new=inspect(name)
        preserved[name]=new['Id']==old['Id'] and new['Image']==old['Image'] and new['State']['StartedAt']==old['State']['StartedAt']
    assert all(preserved.values())
    save('reflected.json',dict(images=images,ready=ready,models=models,bridge=bridge,artifacts=artifacts,
         channels_stopped=len(states),ledger_unchanged=True,non_target_preserved=preserved))
    print(json.dumps(dict(models=models,channels_stopped=len(states),wss=True,artifacts=True,preserved=True)),flush=True)
except Exception as e:
    save('reflection-failed.json',dict(type=type(e).__name__,reason=str(e)[:160]))
    for name in reversed(changed):
        if inspect(name)['State']['Running']:
            cmd(['docker','stop','--signal=SIGTERM','--timeout=-1',name],timeout=90)
    raise
