import pathlib,json,subprocess,hashlib,io,tarfile,sys,os,shutil
os.umask(0o077)
R=pathlib.Path(__file__).parent;REPO=pathlib.Path('/workspace/contextra-analytics-runtime-lifecycle-refactor')
sys.path.insert(0,str(REPO))
import yaml
from tools.validate_deployment import load_env_file
ENV=pathlib.Path('/workspace/contextra-runtime-config/runtime.env')
CFG=pathlib.Path('/data/contextra/models/triton/RFDETRMedium/config.pbtxt')
NAME='contextra-analytics-1';HEAD='3edcb4472f5d3ff10ab711ded8cc28e9add7a794'
def sh(a,timeout=15):return subprocess.check_output(a,text=True,timeout=timeout)
def sha(p):return hashlib.sha256(pathlib.Path(p).read_bytes()).hexdigest()
def inspect():return json.loads(sh(['docker','inspect',NAME]))[0]
def others():
 ids=sh(['docker','ps','-aq']).split()
 rows=json.loads(sh(['docker','inspect',*ids])) if ids else []
 return {c['Id']:{'restart':c['RestartCount'],'oom':c['State']['OOMKilled'],'status':c['State']['Status']} for c in rows if c['Name']!='/'+NAME}
def gpu():return int(sh(['nvidia-smi','--id=0','--query-gpu=memory.used','--format=csv,noheader,nounits']).strip())
def main():
 assert not (R/'gate.json').exists()
 assert sh(['git','-C',str(REPO),'rev-parse','HEAD']).strip()==HEAD
 assert not sh(['git','-C',str(REPO),'status','--porcelain']).strip()
 c=inspect();assert c['State']['Status']=='exited' and not c['State']['OOMKilled']
 assert c['Image']=='sha256:228fc2493dcb81454453d05a66528dd5927d74a40fb85f76d6950afbf0b56d1a'
 assert c['HostConfig']['RestartPolicy']=={'Name':'on-failure','MaximumRetryCount':3}
 e=load_env_file(str(ENV));overlay=pathlib.Path(e['CONTEXTRA_RUNTIME_STATE_ROOT'])/'streams_runtime.yaml'
 assert sha(CFG)=='5e5a4d22786f8b5218ac821379fbec091d9e3006f62c880ee3e3da5ced2f0e0b'
 assert sha(CFG.parent/'2/model.plan')=='5fdf1e9c55ab947a3b7d5f7f6bdceaf1e36a9e52efdf86f5a15d3b99b8631d1e'
 assert sha(overlay)=='ff73d70010b7dbd2c0f0edba2fc9a389b151910bc74abc4b0ba80ab2fe3ac10e'
 specs=yaml.safe_load(overlay.read_text())['added'];assert len(specs)==1
 s=specs[0];assert not s.get('store',True) and not s.get('active_learning',True) and not s.get('autostart',True)
 assert s.get('scenarios')=='off' and s.get('persist_mode','all')!='event_gated'
 data=subprocess.check_output(['docker','cp',NAME+':/opt/contextra/app/.','-'],timeout=20)
 hashes={}
 with tarfile.open(fileobj=io.BytesIO(data)) as tar:
  for member in tar:
   if not member.isfile() or not member.name.endswith('.py'):continue
   name=member.name.removeprefix('./');local=REPO/'app'/name
   assert local.is_file(),name
   h=hashlib.sha256(tar.extractfile(member).read()).hexdigest()
   assert h==sha(local),name
   hashes['app/'+name]=h
 assert len(hashes)>50
 ce=dict(v.split('=',1) for v in c['Config']['Env'] if '=' in v)
 diag=ce.get('CONTEXTRA_START_DIAGNOSTICS_DIR')
 mounts=[m for m in c['Mounts'] if m['Destination']==diag]
 assert len(mounts)==1;d=pathlib.Path(mounts[0]['Source'])
 assert d.is_dir() and d.stat().st_mode&0o777==0o700
 mem={l.split(':')[0]:int(l.split()[1]) for l in pathlib.Path('/proc/meminfo').read_text().splitlines()}
 assert mem['MemAvailable']>10*1024**2 and gpu()<16384 and shutil.disk_usage(R).free>2*1024**3
 out={'head':HEAD,'container_id':c['Id'],'image':c['Image'],'env_hash':sha(ENV),'config_hash':sha(CFG),
 'plan_hash':sha(CFG.parent/'2/model.plan'),'overlay_hash':sha(overlay),'original_restart':c['HostConfig']['RestartPolicy'],
 'other_containers':others(),'gpu_mib':gpu(),'app_hashes':hashes,'diagnostics_dir':str(d),
 'diagnostics_before':{p.name:sha(p) for p in d.iterdir() if p.is_file()},'stdin_open':c['Config']['OpenStdin'],'tty':c['Config']['Tty']}
 (R/'gate.json').write_text(json.dumps(out,indent=2))
 print(json.dumps({'gate':'PASS','image':out['image'],'app_files_equal':len(hashes),'approved_streams':1,
 'analytics':'exited','original_restart':out['original_restart'],'gpu_mib':out['gpu_mib'],
 'stdin_open':out['stdin_open'],'tty':out['tty'],'diagnostic_stack_exists':(d/'python-stack.txt').exists()}))
if __name__=='__main__':main()

