import json,statistics,itertools,collections,sys
from pathlib import Path
P=Path(sys.argv[1]) if len(sys.argv)>1 else Path(__file__).parent
d=json.loads((P/'observation.json').read_text());rows=d['redis']['rows'];ps=sorted(set(r['provider'] for r in rows))
def rid(r):return tuple(map(int,r['redis_id'].split('-')))
def ms(r):return rid(r)[0]
def stats(xs):
 if not xs:return dict(n=0)
 xs=sorted(xs)
 return dict(n=len(xs),mean=statistics.mean(xs),median=statistics.median(xs),min=xs[0],max=xs[-1],p95=xs[max(0,__import__('math').ceil(len(xs)*.95)-1)])
by={p:[r for r in rows if r['provider']==p] for p in ps}
summary={}
for p,rs in by.items():
 a=d['before']['providers'][p]['metadata_path'];b=d['after']['providers'][p]['metadata_path']
 pa=d['before']['providers'][p]['publisher_metrics'];pb=d['after']['providers'][p]['publisher_metrics']
 ta=a['provider_timing'][p]['selected_to_response'];tb=b['provider_timing'][p]['selected_to_response']
 n=tb['count']-ta['count']
 summary[p]=dict(messages=len(rs),fps=len(rs)/d['redis']['seconds'],runs=len(set(r['run'] for r in rs)),
   redis_interval_ms=stats([ms(y)-ms(x) for x,y in zip(rs,rs[1:])]),
   rtp_available=sum(r['rtp'] is not None for r in rs),reordered=sum(y['seq']<=x['seq'] for x,y in zip(rs,rs[1:])),
   sequence_skips=sum(max(0,y['seq']-x['seq']-1) for x,y in zip(rs,rs[1:])),
   object_count=stats([r['objects'] for r in rs]),tracked_objects=stats([r['tracked'] for r in rs]),
   selected_response_ms=(tb['sum_ms']-ta['sum_ms'])/n if n else None,
   selected_response_over100_pct=100*(tb['over_100ms']-ta['over_100ms'])/n if n else None,
   completed_queue_expired=b.get('completed_queue_expired',0)-a.get('completed_queue_expired',0),
   sender_delta={k:pb.get(k,0)-pa.get(k,0) for k in ('sent_frames','replaced','expired','send_error')})
maps={p:{} for p in ps};duplicates={p:0 for p in ps}
for r in rows:
 if r['rtp'] is None:continue
 key=(r['clock'],r['rtp']);m=maps[r['provider']]
 if key in m:duplicates[r['provider']]+=1
 else:m[key]=r
common=set.intersection(*(set(v) for v in maps.values())) if maps else set()
orders=collections.Counter();spans=[];lags={p:[] for p in ps};ties=0
for k in common:
 group=sorted([maps[p][k] for p in ps],key=rid)
 orders['>'.join(r['provider'] for r in group)]+=1
 times=[ms(r) for r in group];spans.append(times[-1]-times[0]);ties+=len(set(times))<len(times)
 for r in group:lags[r['provider']].append(ms(r)-times[0])
pairs={}
for a,b in itertools.combinations(ps,2):
 keys=set(maps[a])&set(maps[b]);diff=[ms(maps[b][k])-ms(maps[a][k]) for k in keys]
 pairs[a+'->'+b]=dict(exact_rtp_groups=len(keys),b_minus_a_arrival_ms=stats(diff),
   a_first=sum(rid(maps[a][k])<rid(maps[b][k]) for k in keys),b_first=sum(rid(maps[b][k])<rid(maps[a][k]) for k in keys),
   same_millisecond=sum(ms(maps[a][k])==ms(maps[b][k]) for k in keys))
# Pose-centered nearest media-time comparison, excluding two seconds at both read-window edges.
anchor=[r for r in by.get('pose',[]) if 2000<=r['read_offset_ms']<d['redis']['seconds']*1000-2000 and r['rtp'] is not None]
nearest={};triple_exact=0;complete_nearest=0
for p in ps:
 if p=='pose':continue
 delta=[];exact=0;missing=0
 for a in anchor:
  candidates=[r for r in by[p] if r['clock']==a['clock'] and r['rtp'] is not None]
  if not candidates:missing+=1;continue
  def dt(r):return ((r['rtp']-a['rtp']+2**31)%2**32-2**31)*1000/a['clock']
  n=min(candidates,key=lambda r:abs(dt(r)));v=dt(n)
  if abs(v)>100:missing+=1;continue
  delta.append(v);exact+=v==0
 nearest[p]=dict(pose_denominator=len(anchor),matched_within100ms=len(delta),exact=exact,missing=missing,rtp_delta_ms=stats(delta))
for a in anchor:
 key=(a['clock'],a['rtp']);triple_exact+=all(key in maps[p] for p in ps)
out=dict(seconds=d['redis']['seconds'],providers=summary,duplicate_rtp=duplicates,
 exact_triples=len(common),order_counts=dict(orders),first_counts={p:sum(n for o,n in orders.items() if o.split('>')[0]==p) for p in ps},
 triple_arrival_span_ms=stats(spans),first_to_provider_ms={p:stats(v) for p,v in lags.items()},
 triples_with_millisecond_ties=ties,pairs=pairs,pose_centered=nearest,
 pose_centered_exact_all=dict(n=triple_exact,denominator=len(anchor)),
 note='Stream IDs order Redis commits; no claim about Web rendering or pixel identity. RTP equality is within one camera/run and clock rate; SSRC unavailable.')
with (P/'summary.json').open('x') as f:json.dump(out,f,indent=2)
print(json.dumps(out,indent=2))
