import csv,json,os
from pathlib import Path
from preflight import R,g
os.umask(0o077)
d=json.loads((R/'run.json').read_text());p=json.loads((R/'plan.json').read_text())
assert d['final']=='HEALTHY_STOPPED_NEW_IMAGE' and d['cleanup_verified'] and not d.get('restore_error')
m=d['measurement'];b,a=m['before'],m['after'];seconds=(a['sampled_ns']-b['sampled_ns'])/1e9
rates={k:(a['counts'].get(k,0)-b['counts'].get(k,0))/seconds for k in a['counts']}
observations=d['observations']
def near(t):return min(observations,key=lambda o:abs(o['metadata_path']['performance']['sampled_ns']-t) if (o.get('metadata_path') or {}).get('performance') else float('inf'))
rb,ra=near(b['sampled_ns']),near(a['sampled_ns'])
redis_delta=ra['publisher']['metrics']['sent_frames']-rb['publisher']['metrics']['sent_frames']
final_cleanup=next(o['last_cleanup'] for o in reversed(observations) if (o.get('last_cleanup') or {}).get('completed'))
images=sorted((R/'visual').glob('capture-*/frame-*.json'))
assert len(images)==3
rows=[]
for f in images:
    value=json.loads(f.read_text());stats=json.loads(f.with_suffix('.stats').read_text())
    assert value==json.loads((R/'samples'/f.name).read_text())
    rows.append(dict(frame=value['frameSeq'],pts_s=value['ptsNs']/1e9,
                     shown=len(value['objects']),nms=value['nms'],pose=stats['pose_counts']))
with (R/'pose-counts.csv').open('x',newline='') as f:
    w=csv.writer(f);w.writerow(['frame_seq','pts_s','object_ref','track_id','person_score','valid_joints','total_joints','skeleton_eligible'])
    for row in rows:
        for obj in row['pose']['objects']:
            w.writerow([row['frame'],row['pts_s'],obj['objectRef'],obj['trackId'],obj['personScore'],obj['validJoints'],17,obj['eligible']])
c=g.inspect();actual_env=dict(v.split('=',1) for v in c['Config']['Env'])
assert c['Image']==json.loads((R/'build.json').read_text())['image_id']
assert c['State']['Health']['Status']=='healthy'
assert c['HostConfig']['RestartPolicy']==p['analytics']['HostConfig']['RestartPolicy']
assert g.sha(p['overlay'])==p['overlay_hash']
assert g.sha(g.CFG)==p['config_hash'] and g.sha(g.CFG.parent/'2/model.plan')==p['model_hash']
assert g.others()==p['other_containers']
assert not g.sh(['docker','ps','-aq','--filter','name=codex-ltb1021-pose-']).strip()
assert actual_env.get('CONTEXTRA_METADATA_VISUAL_DIR','')==''
assert not any(v['Destination'] in ('/run/contextra-offline/input.mp4','/run/contextra-visual','/run/contextra-boundary') for v in c['Mounts'])
summary=dict(status='RUNTIME_PASS_USER_VISUAL_WAIT',started_utc=p['started_utc'],finished_utc=g.sh(['date','-u','+%FT%TZ']).strip(),
    sdk_cause='fixture PYTHONPATH overwrote /opt/contextra; ModuleNotFoundError for packaged contextra_frame_meta',
    sdk_copy_release='PASS',source_tests=51,image_tests=51,image_builds=1,runtime_windows=1,
    model_conversions=0,measurement_s=seconds,frame_range=[b['last_frame']['result'][0],a['last_frame']['result'][0]],
    fps=rates,redis_frames_delta=redis_delta,redis_fps=redis_delta/seconds,
    nms_live_mean_ms=d['nms_live']['nms_elapsed_ns']/d['nms_live']['nms_calls']/1e6,
    nms_live_scope='cumulative run average, not same interval as FPS',
    stage_durations=a['durations'],durations_scope='last up to256 samples; not interval subtraction',
    result_stale=d['last_metadata_path'].get('stale',0),result_queue_drop=d['last_metadata_path'].get('queue_drop',0),
    publisher=observations[-1]['publisher']['metrics'],cleanup_s=final_cleanup['elapsed_s'],
    control_ms={x['action']:(x['returned_ns']-x['requested_ns'])/1e6 for x in d['calls']},
    images=[{k:v for k,v in row.items() if k!='pose'} for row in rows],
    current=dict(image=c['Image'],healthy=True,stopped=True,pose_mount_retained=True,
                 original_camera_settings_restored=True,capture_diagnostic_mounts_removed=True,
                 restart=c['HostConfig']['RestartPolicy'],gpu_mib=g.gpu(),non_targets_unchanged=True),
    limitations=['single offline channel; not16channel/15fps/long term','offline RTP unavailable',
                 'NMS user visual acceptance pending','Event NOT_SUBMITTED/Artifact URI null'])
(R/'summary.json').write_text(json.dumps(summary,indent=2))
with (R/'summary.csv').open('x',newline='') as f:
    w=csv.writer(f);w.writerow(['metric','value','scope'])
    for k,v in rates.items():w.writerow([k+'_fps',v,'measurement'])
    w.writerow(['redis_fps',summary['redis_fps'],'measurement'])
    w.writerow(['nms_mean_ms',summary['nms_live_mean_ms'],'whole run'])
    w.writerow(['cleanup_s',summary['cleanup_s'],'Stop'])
(R/'final-state.private.json').write_text(json.dumps(c,indent=2))
for file in R.rglob('*'):
    if file.is_file():file.chmod(0o600)
print(json.dumps({k:summary[k] for k in ('status','measurement_s','fps','redis_fps','nms_live_mean_ms','cleanup_s','control_ms','result_stale','result_queue_drop','current')}))
