import json,os,pathlib,subprocess,time
from apply import a,plan,built,C,R,g,cmd,api
os.umask(0o077)
def main():
 assert not (R/'final.json').exists()
 run=json.loads((R/'runtime.json').read_text());state=json.loads((R/'apply.json').read_text())
 out=dict(runtime_stage=run['stage'],start=sum(x['action']=='start' for x in run['calls']),stop=sum(x['action']=='stop' for x in run['calls']))
 try:
  # Capture bounded private logs before recreating or removing the failed candidate.
  log=subprocess.run(['docker','logs','--tail','250',g.NAME],capture_output=True,timeout=10)
  (R/'window.private.log').write_bytes((log.stdout+log.stderr)[-1024*1024:])
  c=g.inspect();out['candidate_state']={k:c['State'].get(k) for k in ['Status','ExitCode','OOMKilled']}
  if run['stage']=='passed':
   assert c['Image']==built['image_id'] and c['State']['Status']=='running'
   assert api('/api/v1/streams')['streams'][0]['state']=='stopped'
   target=C/'compose.yaml';before=g.sha(target)
   override=g.yaml.safe_load(target.read_text());override['services']['analytics']['restart']=a.restart_arg(plan['analytics']['HostConfig']['RestartPolicy'])
   a.atomic(target,g.yaml.safe_dump(override).encode(),before)
   cmd(['docker','update','--restart='+a.restart_arg(plan['analytics']['HostConfig']['RestartPolicy']),g.NAME])
   cmd(['python3',str(C/'firewall.py'),'check'])
   out['settings']='RETAIN_AUTH_LAN_CAPTURE_OFF'
  else:
   expected=run.get('overlay_final') or run.get('overlay_after')
   if expected:
    a.atomic(a.overlay,(R/'overlay.before').read_bytes(),expected)
   a.rollback(state)
   cmd(['docker','update','--restart='+a.restart_arg(plan['analytics']['HostConfig']['RestartPolicy']),g.NAME])
   out['rollback']='applied';out['settings']='RESTORED_PREVIOUS_ACTUAL'
  end=time.monotonic()+120
  while time.monotonic()<end:
   c=g.inspect()
   try:
    if c['State'].get('Health',{}).get('Status')=='healthy' and api('/ready')['ready']:break
   except Exception:pass
   time.sleep(2)
  assert c['State']['Health']['Status']=='healthy'
  assert api('/api/v1/streams')['streams'][0]['state']=='stopped'
  r=json.loads(g.sh(['docker','inspect','contextra-redis-1']))[0]
  assert {k:v for k,v in g.others().items() if k!=r['Id']}==plan['other_containers']
  if run['stage']!='passed':
   assert c['Image']==plan['analytics']['Image'] and g.sha(g.ENV)==plan['env_hash']
   assert g.sha(a.overlay)==plan['overlay_hash'] and not r['HostConfig']['PortBindings']
   assert not pathlib.Path(plan['unit']).exists()
   assert subprocess.run(['iptables','-S','CTX_METADATA_LAN'],capture_output=True).returncode!=0
   assert 'nopass' in g.sh(['docker','exec',r['Id'],'redis-cli','ACL','LIST'])
  assert g.sha(g.CFG)==plan['config_hash'] and g.sha(g.CFG.parent/'2/model.plan')==plan['plan_hash']
  assert not (R/'visual/capture.enabled').exists()
  out.update(analytics='healthy',stream='stopped',image=c['Image'],model_unchanged=True,non_targets_unchanged=True,
    restart_restored=c['HostConfig']['RestartPolicy']==plan['analytics']['HostConfig']['RestartPolicy'],gpu_mib=g.gpu(),capture_off=True)
 except Exception as error:
  out['finish_error']=type(error).__name__
  try:cmd(['docker','stop','--timeout','20',g.NAME],40);out['analytics']='safely_stopped'
  except Exception:out['safety_stop_failed']=True
 finally:
  (R/'final.json').write_text(json.dumps(out,indent=2))
  print(json.dumps(out))
if __name__=='__main__':main()
