import json,time,sys,urllib.request,urllib.parse,subprocess,pathlib,hashlib,datetime
import yaml
from deploy import R,ENV,G,load_env_file,record,rollback,inspect,BUILD
assert json.loads((R/'deploy-state.json').read_text())['stage']=='INFRA_READY'
assert not (R/'smoke.json').exists(), 'inspect previous smoke before resume'
env=load_env_file(str(ENV)); base='http://127.0.0.1:'+env['FASTAPI_HOST_PORT']
overlay=pathlib.Path(env['CONTEXTRA_RUNTIME_STATE_ROOT'])/'streams_runtime.yaml'
overlay_hash=hashlib.sha256(overlay.read_bytes()).hexdigest()
specs=yaml.safe_load(overlay.read_text())['added']; assert len(specs)==1
spec=specs[0]
assert spec['source_type']=='rtsp' and spec['store'] is False
assert spec['scenarios']=='off' and spec['active_learning'] is False and spec['autostart'] is False
assert spec['inference_profile']['roles']==['det'] and spec['inference_profile']['det_model']=='RFDETRMedium'
sid=spec['stream_id']; path='/api/v1/streams/'+urllib.parse.quote(sid,safe='')
def api(p,method='GET',timeout=5):
 req=urllib.request.Request(base+p,method=method)
 with urllib.request.urlopen(req,timeout=timeout) as r:return json.load(r)
def gpu_used():
 return int(subprocess.check_output(['nvidia-smi','--id=0','--query-gpu=memory.used','--format=csv,noheader,nounits'],text=True).strip())
def save(data):
 p=R/'smoke.json'; p.write_text(json.dumps(data,indent=2));p.chmod(0o600)
s=api(path); assert s['state']==s['desired_state']==s['observed_state']=='stopped'
assert inspect()['Image']==BUILD['image_id']
baseline_gpu=gpu_used(); assert baseline_gpu<16*1024
result={'status':'START_INTENT','start_attempts':0,'stop_attempts':0,'gpu_initial_mib':baseline_gpu,
 'deadline_seconds':60,'post_first_frame_seconds':10,'target_frames':100,'store':False,'scenarios':False,'active_learning':False}
save(result)
start=time.monotonic(); first=None; success=False; next_gpu=start
since=datetime.datetime.now(datetime.timezone.utc).isoformat()
try:
 assert hashlib.sha256(overlay.read_bytes()).hexdigest()==overlay_hash
 result['start_attempts']=1;save(result); response=api(path+'/start','POST',10)
 result['start_response']=response.get('observed_state');save(result)
 while time.monotonic()-start<60:
  s=api(path,timeout=3)
  result['states']={k:s.get(k) for k in ('state','desired_state','observed_state')}
  result['roles']={k:{a:v.get(a) for a in ('state','initialized','source_stage','last_error')} for k,v in s.get('roles',{}).items()}
  result['frames']=s.get('per_task_buffers',{}).get('det',0)
  result['latest_count']=s.get('latest',{}).get('det')
  if s.get('last_error'):
   result['failure']=s['last_error'];break
  if s.get('observed_state')=='running' and result['frames']>0:
   if first is None:first=time.monotonic();result['first_frame_seconds']=round(first-start,2)
   if time.monotonic()-first>=10 or result['frames']>=100:success=True;break
  if time.monotonic()>=next_gpu:
   used=gpu_used(); result['gpu_peak_mib']=max(result.get('gpu_peak_mib',baseline_gpu),used); next_gpu=time.monotonic()+5
   if used-baseline_gpu>8192:result['failure']={'error_code':'GPU_BUDGET'};break
  time.sleep(.5)
except Exception as e: result['exception_type']=type(e).__name__
finally:
 result['stop_attempts']=1;save(result)
 try:
  api(path+'/stop','POST',25)
  s=api(path,timeout=5)
  result['stopped']=s['state']==s['desired_state']==s['observed_state']=='stopped'
 except Exception as e:result.update(stopped=False,stop_error_type=type(e).__name__)
 result['seconds']=round(time.monotonic()-start,2)
 result['status']='PASS' if success and result['stopped'] else 'FAIL'
 result['overlay_unchanged']=hashlib.sha256(overlay.read_bytes()).hexdigest()==overlay_hash
 save(result)
 raw=subprocess.run(['docker','logs','--since',since,'--tail','250','contextra-analytics-1'],capture_output=True,text=True,timeout=10)
 lines=(raw.stdout+raw.stderr).splitlines()
 result['log_summary']={'native_load':sum('processor loaded' in l for l in lines),
  'native_attach':sum('frame metadata attached' in l for l in lines),'python_det':sum('[DET] buf' in l for l in lines),
  'error_categories':sorted({word for word in ('NvDsPreProcess','nvdspreprocess','tensor_meta','native_metadata','PIPELINE_INIT_FAILED','ROLE_RUNTIME_FAILED','cudaError','not-negotiated','Segmentation fault') if any(word in l for l in lines)})}
 save(result);print(json.dumps(result,indent=2))
 if result['status']!='PASS': rollback('PIPELINE_VALIDATION_FAILED')
 else:
  state=json.loads((R/'deploy-state.json').read_text());state.update(stage='PIPELINE_PASS_STOPPED',smoke=result);record(state)
