import csv,json,math,statistics as st
from pathlib import Path
ROOT=Path(__file__).parent
labels=('pose-a','pose-a-resume','mixed-a','mixed-c-16')
summaries=[];lanes=[];extras=[]
for label in labels:
    data=json.loads((ROOT/label/'analysis.json').read_text())
    for row in data['summary']:
        row=dict(row);batch=row['batch_executions'];total=sum(batch.values())
        row['batch_ratios']={name:dict(numerator=sum(n for b,n in batch.items() if predicate(int(b))),denominator=total,
            percent=100*sum(n for b,n in batch.items() if predicate(int(b)))/total if total else None)
            for name,predicate in [('ge4',lambda b:b>=4),('gt4',lambda b:b>4),('eq8',lambda b:b==8)]}
        group=[x for x in data['lanes'] if x['level']==row['level'] and x['provider']==row['provider']]
        row['ingress_redis_mean_ms']=st.mean(x['ingress_to_redis_ms']['mean'] for x in group)
        row['ingress_redis_max_ms']=max(x['ingress_to_redis_ms']['maximum'] for x in group)
        row['reference_pass']=row['redis_fps']>=9.5 and row['redis_min']>=9
        summaries.append(row)
    lanes.extend(data['lanes'])
    for path in sorted((ROOT/label).glob('stage-*.json')):
        stage=json.loads(path.read_text());a,b=stage['samples'][0],stage['samples'][-1]
        counter={};hist={};query=[]
        for sample in stage['samples']:
            query.extend(v['read_end']-v['read_begin'] for v in sample['channels'].values())
        for camera,channel in b['channels'].items():
            for provider,v in channel['providers'].items():
                old=a['channels'][camera]['providers'][provider];ap,bp=old['metadata_path'],v['metadata_path']
                for field in ('admitted','events_created'):
                    counter[field]=counter.get(field,0)+bp.get(field,0)-ap.get(field,0)
                for field in ('accepted','artifact_admission_failed','unaccepted_events','capture_failed','metadata_queue_drop','event_candidates_suppressed'):
                    counter['artifact_'+field]=counter.get('artifact_'+field,0)+(bp.get('artifacts')or{}).get(field,0)-(ap.get('artifacts')or{}).get(field,0)
                x=ap.get('event_cooldown',{}).get('counts',{});y=bp.get('event_cooldown',{}).get('counts',{})
                for key,n in y.items():counter['cooldown_'+key]=counter.get('cooldown_'+key,0)+n-x.get(key,0)
                ad=ap['performance']['pipeline_diagnostic']['providers'][provider];bd=bp['performance']['pipeline_diagnostic']['providers'][provider]
                for key,duration in bd['durations'].items():
                    before=ad['durations'].get(key,{}).get('histogram',[0]*len(duration['histogram']))
                    counts=[v-u for u,v in zip(before,duration['histogram'])]
                    out=hist.setdefault(provider+':'+key,dict(bounds_ms=bd['histogram_bounds_ms'],counts=[0]*len(counts)))
                    out['counts']=[x+y for x,y in zip(out['counts'],counts)]
        for value in hist.values():
            n=sum(value['counts']);rank=math.ceil(n*.95);cumulative=0;value['p95_bucket_upper_ms']=None
            for i,count in enumerate(value['counts']):
                cumulative+=count
                if n and cumulative>=rank:
                    value['p95_bucket_upper_ms']=value['bounds_ms'][i] if i<len(value['bounds_ms']) else 'over_5000';break
        pending=[]
        for sample in stage['samples']:
            for value in sample['artifacts']['ledger'].get('pending',[]):
                pending.append(max(0,sample['wall_ms']/1000-value['oldest']))
        extras.append(dict(mode=label,level=stage['level'],counters=counter,duration_histograms=hist,
            sampled_reserved_jobs_max=max(s['artifacts']['ledger']['storage']['reserved_jobs'] for s in stage['samples']),
            sampled_pending_age_max_s=max(pending) if pending else 0,
            sample_collection_max_s=max(s['finished']-s['t'] for s in stage['samples']),status_query_max_s=max(query),
            artifact_global_counts_delta={k:b['artifacts']['counts'].get(k,0)-a['artifacts']['counts'].get(k,0) for k in set(a['artifacts']['counts'])|set(b['artifacts']['counts'])},
            training_first=a.get('training'),training_last=b.get('training')))
result=dict(scope='Pose A1..16 (resume at12); Mixed A1..16; Mixed C16 only per final user decision',summary=summaries,lanes=lanes,stage_details=extras,
    event_cohorts=json.loads((ROOT/'event-cohorts.json').read_text()),final_state=json.loads((ROOT/'final-state.json').read_text()),
    limits=['A12 partial failure retained, then user approved normal Artifact admission rejection to continue; limits unchanged',
            'Pose A12 restarted prior channels; anchors reset only because runs changed',
            'Pose C1 completed before scope reduction is ancillary; no full C curve',
            'C16 fresh runs, A16 cumulative runs; same URIs do not imply same Frames or Event burden',
            'p95_bucket_upper_ms is a histogram bucket bound, not exact p95; native joins are sampled',
            'Negative anchored drift is not negative absolute video latency; no absolute capture clock verified'])
(ROOT/'comparison-sot.json').write_text(json.dumps(result,indent=2))
keys=('mode','level','provider','complete_fps','accepted_fps','finalized_fps','redis_fps','redis_min','redis_std','queue_expired','capacity_drop','ingress_redis_mean_ms','ingress_redis_max_ms','max_redis_silence_s','cpu_busy_pct','gpu_util_pct','reference_pass')
with (ROOT/'comparison-sot.csv').open('w',newline='') as f:
    writer=csv.DictWriter(f,fieldnames=keys,extrasaction='ignore');writer.writeheader();writer.writerows(summaries)
print('| N | Pose A | Mixed A Pose | Fire | Crowd |')
print('|---|---:|---:|---:|---:|')
for n in range(1,17):
    pose=next(x for x in summaries if x['mode'] in ('pose-a','pose-a-resume') and x['level']==n)
    mixed={x['provider']:x for x in summaries if x['mode']=='mixed-a' and x['level']==n}
    print('|'+str(n)+'|'+'|'.join(f'{x["redis_fps"]:.2f}' for x in (pose,mixed['pose'],mixed['fire'],mixed['crowd']))+'|')
print('C16',[(x['provider'],round(x['redis_fps'],2)) for x in summaries if x['mode']=='mixed-c-16'])
