import csv
import hashlib
import json
import sys
from pathlib import Path

R=Path(sys.argv[1]) if len(sys.argv)>1 else Path(__file__).parent
def read(name):return json.loads((R/name).read_text())
plan=read('plan.json');final=read('final.json');window=read('window.json');samples=window['samples']
profile=read('publisher-profile.json')['metrics']['profile'] if (R/'publisher-profile.json').exists() else {}
rows=[];totals={}
for c in plan['channels']:
    sid=c['stream_id'];started=c['original'] or (R/(sid+'-started.json')).exists()
    row=dict(channel=f"G{c['group']}-{c['copy']}",started=started,result_fps=None,redis_fps=None,queue_min=None,queue_max=None,
             stale=None,drop=None,replaced=None,send_error=None,stored=None,ttl_ms=None,identity_aligned=None)
    if len(samples)>=2:
        x,y=samples[0]['channels'][sid],samples[-1]['channels'][sid];dt=samples[-1]['at']-samples[0]['at']
        key=hashlib.sha256(c['source_id'].encode()).hexdigest();counts=profile.get('channels',{}).get(key,{}).get('counts',{})
        available=not(x['redis'].get('missing') or y['redis'].get('missing'))
        aligned=True
        for value in (x,y):
            f=value['redis'].get('frame')
            if not f:aligned=None;break
            aligned &= (f['sourceId']==c['source_id'] and f['streamId']==sid and f['runId']==value['run_id']
                        and f['runGeneration']==value['generation'] and all(o.get('trackId') is None for o in f['objects']))
        row.update(result_fps=(y['processed']-x['processed'])/dt,
            redis_fps=(y['redis']['entries']-x['redis']['entries'])/(y['redis']['at']-x['redis']['at']) if available else None,
            queue_min=min(s['channels'][sid]['queued'] for s in samples),queue_max=max(s['channels'][sid]['queued'] for s in samples),
            stale=y['stale']-x['stale'],drop=y['drop']-x['drop'],
            replaced=counts.get('replaced_by_frame',0) if profile else None,send_error=counts.get('send_error',0) if profile else None,
            stored=y['redis'].get('length'),ttl_ms=y['redis'].get('pttl'),identity_aligned=aligned)
    rows.append(row)
if len(samples)>=2:
    a,b=samples[0],samples[-1];dt=b['at']-a['at'];r0,r1=a['resources'],b['resources'];cpu0,cpu1=r0['host_cpu'],r1['host_cpu']
    total=sum(cpu1[:8])-sum(cpu0[:8]);idle=sum(cpu1[3:5])-sum(cpu0[3:5])
    result_frames=sum(b['channels'][c['stream_id']]['processed']-a['channels'][c['stream_id']]['processed'] for c in plan['channels'])
    valid=all(not s['channels'][c['stream_id']]['redis'].get('missing') for s in (a,b) for c in plan['channels'])
    redis_frames=sum(b['channels'][c['stream_id']]['redis']['entries']-a['channels'][c['stream_id']]['redis']['entries'] for c in plan['channels']) if valid else None
    totals=dict(seconds=dt,result_frames=result_frames,result_fps=result_frames/dt,redis_frames=redis_frames,redis_fps=redis_frames/dt if valid else None,
        publisher_delta={k:b['publisher'].get(k,0)-a['publisher'].get(k,0) for k in ('sent_frames','replaced','expired','send_error','byte_limit','message_limit')},
        publisher_queue=[min(s['publisher']['queued'] for s in samples),max(s['publisher']['queued'] for s in samples)],
        workers=[min(s['publisher']['workers'] for s in samples),max(s['publisher']['workers'] for s in samples)],
        analytics_cpu_cores=(r1['cpu']['usage_usec']-r0['cpu']['usage_usec'])/1e6/(r1['at']-r0['at']),
        host_cpu_percent=100*(total-idle)/total if total else None,
        memory_mib=[min(s['resources']['memory'] for s in samples)/1024**2,max(s['resources']['memory'] for s in samples)/1024**2],
        gpu=[s['resources']['gpu'] for s in samples])
with (R/'summary.csv').open('w') as f:
    w=csv.DictWriter(f,fieldnames=list(rows[0]));w.writeheader();w.writerows(rows)
startup=read('startup-samples.json')
res=read('resource-samples.json')+[s['resources'] for s in samples]
verified=dict(original_runs_preserved=all(final['states'].get(sid,{}).get('state')==st['state'] and final['states'].get(sid,{}).get('run_id')==st['run_id'] for sid,st in plan['states'].items()),
    registry_restored=final['registry']==plan['registry'],settings_preserved=final['original_settings']==plan['settings'],
    temporary_remaining=[sid for sid in plan['temporary'] if sid in final['states']],containers_unchanged=final['inventory']==plan['inventory'],
    sender_unchanged=final['sender']['executionId']==plan['sender']['executionId'],
    config_unchanged=all(hashlib.sha256(Path(x).read_bytes()).hexdigest()==h for x,h in plan['config_hashes'].items()),
    final_workers=final['publisher']['metrics']['workers'],final_publisher_channels=final['publisher']['metrics']['channels'],
    profile_off=not final['publisher']['metrics']['profile']['enabled'],worker_errors=final['publisher']['metrics']['workerErrors'])
summary=dict(window=window['result'],rows=rows,totals=totals,verified=verified,
    startup_peak_verified=max([sum(q['state']=='running' for q in s['channels'].values()) for s in startup] or [3]),
    resource_envelope=dict(memory_max_mib=max(r['memory'] for r in res)/1024**2,host_available_min_gib=min(r['available'] for r in res)/1024**3,
                           gpu_memory_max_mib=max(g[0] for r in res for g in r['gpu'])),
    failure=read('failure.json') if (R/'failure.json').exists() else None)
(R/'analysis.json').write_text(json.dumps(summary,indent=2))
print(json.dumps(dict(window=summary['window'],rows=rows,totals=totals,verified=verified,resource_envelope=summary['resource_envelope']),indent=2))
