import pathlib,json,subprocess,urllib.request,urllib.parse,time,hashlib,sys,os,tempfile,datetime,shutil
import yaml
sys.path.insert(0,'/workspace/contextra-analytics-runtime-lifecycle-refactor')
from tools.validate_deployment import load_env_file,validate_deployment_inputs
from model_tooling.deploy import deployment_plan
from model_tooling.medium_acceptance import POLICY
R=pathlib.Path(__file__).resolve().parent; REPO=pathlib.Path('/workspace/contextra-analytics-runtime-lifecycle-refactor')
ENV=pathlib.Path('/workspace/contextra-runtime-config/runtime.env'); CFG=pathlib.Path('/data/contextra/models/triton/RFDETRMedium/config.pbtxt')
B=json.loads((R/'build-result.json').read_text()); SNAP=json.loads((R/'source-snapshot.json').read_text())
def sha(p):return hashlib.sha256(p.read_bytes()).hexdigest()
def atomic(p,b,mode=0o600):
 fd,n=tempfile.mkstemp(dir=p.parent)
 with os.fdopen(fd,'wb') as f:os.fchmod(f.fileno(),mode);f.write(b);f.flush();os.fsync(f.fileno())
 os.replace(n,p)
def write(d):atomic(R/'runtime.json',json.dumps(d,indent=2).encode())
def inspect():return json.loads(subprocess.check_output(['docker','inspect','contextra-analytics-1'],text=True))[0]
def api(p,method='GET',timeout=5):
 with urllib.request.urlopen(urllib.request.Request(base+p,method=method),timeout=timeout) as f:return json.load(f)
def containers():
 ids=subprocess.check_output(['docker','ps','-aq'],text=True).split()
 data=json.loads(subprocess.check_output(['docker','inspect',*ids],text=True))
 return {c['Id']:(c['RestartCount'],c['State']['OOMKilled'],c['State']['Status'],c['State'].get('Health',{}).get('Status')) for c in data if c['Name']!='/contextra-analytics-1'}
def gpu_used():return int(subprocess.check_output(['nvidia-smi','--id=0','--query-gpu=memory.used','--format=csv,noheader,nounits'],text=True).strip())
assert not (R/'runtime.json').exists()
assert json.loads((R/'image-verify.json').read_text())['status']=='PASS'
assert all(sha(REPO/p)==h for p,h in SNAP['input_hashes'].items())
assert inspect()['State']['Status']=='exited'
assert shutil.disk_usage('/').free>=25*1024**3
gpu_initial=gpu_used();assert gpu_initial<16384
env=load_env_file(str(ENV)); env_hash=sha(ENV)
assert env['CONTEXTRA_IMAGE']=='contextra-analytics:smoke-b3868cf'
assert sha(CFG)=='7b7d0df5fb1383b2d416b5d014d840efbf6c49d54bb69336f5e5eb6f671b611b'
assert sha(CFG.parent/'2/model.plan')=='5fdf1e9c55ab947a3b7d5f7f6bdceaf1e36a9e52efdf86f5a15d3b99b8631d1e'
plan=deployment_plan(pathlib.Path('/data/contextra/model-artifacts/builds/medium-193-20260909-v4'),R/'unapplied-target',1,source_acceptance=POLICY)
assert plan['source_acceptance']['status']=='LIMITED_USER_VISUAL_ACCEPTANCE'
candidate=pathlib.Path('/tmp/codex/ltb1021/2026-09-10_medium-runtime_r01/model-config-candidate.pbtxt').read_bytes()
assert hashlib.sha256(candidate).hexdigest()=='5e5a4d22786f8b5218ac821379fbec091d9e3006f62c880ee3e3da5ced2f0e0b'
overlay=pathlib.Path(env['CONTEXTRA_RUNTIME_STATE_ROOT'])/'streams_runtime.yaml'; ovsha=sha(overlay)
specs=yaml.safe_load(overlay.read_text())['added'];assert len(specs)==1
s=specs[0];assert s['store'] is False and s['scenarios']=='off' and s['active_learning'] is False and s['autostart'] is False
assert s['inference_profile']['roles']==['det'] and s['inference_profile']['det_model']=='RFDETRMedium'
sid=s['stream_id'];path='/api/v1/streams/'+urllib.parse.quote(sid,safe='');base='http://127.0.0.1:'+env['FASTAPI_HOST_PORT']
before=containers();result={'stage':'GATE_PASS','start_attempts':0,'stop_attempts':0,'rollback':False,'snapshot':SNAP['snapshot_sha256'],'gpu_initial_mib':gpu_initial,'gpu_additional_cap_mib':8192};write(result)
def stop_container():
 subprocess.run(['docker','stop','--timeout','20','contextra-analytics-1'],check=True,stdout=subprocess.DEVNULL,timeout=30)
 result['analytics_stopped']=True
try:
 assert sha(ENV)==env_hash and sha(overlay)==ovsha
 raw=ENV.read_text().splitlines(keepends=True); n=0
 for i,line in enumerate(raw):
  if line.split('=',1)[0].strip()=='CONTEXTRA_IMAGE':raw[i]='CONTEXTRA_IMAGE='+B['tag']+'\n';n+=1
 assert n==1
 atomic(CFG,candidate,0o644);atomic(ENV,''.join(raw).encode());result['stage']='CONFIG_ENV_APPLIED';write(result)
 validate_deployment_inputs(str(ENV))
 compose=['docker','compose','--env-file',str(ENV),'-f',str(REPO/'docker-compose.yml')]
 subprocess.run(compose+['config','--quiet'],check=True,timeout=30)
 with (R/'compose.log').open('w') as f:subprocess.run(compose+['up','-d','--no-deps','--force-recreate','--pull','never','analytics'],stdout=f,stderr=subprocess.STDOUT,check=True,timeout=120)
 end=time.monotonic()+300; ready=False
 while time.monotonic()<end:
  c=inspect();assert c['Image']==B['image_id']
  if c['RestartCount'] or c['State']['OOMKilled']:raise RuntimeError('restart or OOM')
  try:
   for endpoint in ['/live','/ready','/health']:api(endpoint)
   ready=c['State'].get('Health',{}).get('Status')=='healthy'
   if ready:break
  except Exception:pass
  time.sleep(3)
 assert ready
 streams=api('/api/v1/streams')['streams'];assert len(streams)==1 and streams[0]['state']=='stopped'
 query='import urllib.request,json; d=json.load(urllib.request.urlopen("http://localhost:8000/v2/models/RFDETRMedium",timeout=5)); print(json.dumps({"versions":d["versions"]}))'
 versions=json.loads(subprocess.check_output(['docker','exec','contextra-analytics-1','python3','-c',query],text=True))['versions'];assert versions==['2']
 result.update(stage='DIAGNOSTIC_ARMED',model_versions=versions);write(result)
 since=datetime.datetime.now(datetime.timezone.utc).isoformat();start=time.monotonic();first=None;success=False
 try:
  assert api(path)['state']=='stopped' and sha(overlay)==ovsha
  result['start_attempts']=1;write(result);api(path+'/start','POST',10);next_gpu=time.monotonic()
  while time.monotonic()-start<60:
   state=api(path,timeout=3)
   if state.get('last_error'):result['failure']=state['last_error'];break
   if time.monotonic()>=next_gpu:
    used=gpu_used();result['gpu_peak_mib']=max(result.get('gpu_peak_mib',gpu_initial),used);next_gpu=time.monotonic()+5
    if used-gpu_initial>8192:result['failure']={'error_code':'GPU_BUDGET'};break
   frames=state.get('per_task_buffers',{}).get('det',0);result['frames']=frames
   if frames and state['observed_state']=='running':
    if first is None:first=time.monotonic()
    if frames>=100 or time.monotonic()-first>=10:success=True;break
   time.sleep(.5)
 finally:
  result['stop_attempts']=1;write(result)
  try:
   api(path+'/stop','POST',25); state=api(path);result['stream_stopped']=state['state']==state['desired_state']==state['observed_state']=='stopped'
  except Exception:result['stream_stopped']=False;stop_container()
 result['seconds']=round(time.monotonic()-start,2)
 logs=subprocess.run(['docker','logs','--since',since,'--tail','250','contextra-analytics-1'],capture_output=True,text=True,timeout=10)
 result['diagnostics']=[]
 for line in (logs.stdout+logs.stderr).splitlines():
  prefix='[NATIVE_METADATA_DIAGNOSTIC] '
  if prefix in line:
   d=json.loads(line.split(prefix,1)[1]);result['diagnostics'].append(d)
 result['stage']='PIPELINE_PASS_STOPPED' if success and result['stream_stopped'] else 'DIAGNOSTIC_COMPLETE_PIPELINE_FAIL'
except Exception as error:
 result.update(stage='FAILED',exception_type=type(error).__name__)
 stop_container()
finally:
 result['other_containers_unchanged']=containers()==before
 result['overlay_unchanged']=sha(overlay)==ovsha
 c=inspect();result['analytics']={'status':c['State']['Status'],'health':c['State'].get('Health',{}).get('Status'),'restart':c['RestartCount'],'oom':c['State']['OOMKilled']}
 if c['RestartCount'] and c['State']['Status']=='running':stop_container();result['analytics']['status']='exited'
 write(result);print(json.dumps(result,indent=2))
