from pathlib import Path
import json, csv, math, sys
from PIL import Image, ImageDraw, ImageFont
ROOT=Path(sys.argv[1]).resolve() if len(sys.argv)>1 else Path(__file__).resolve().parents[2]
RAW=ROOT/'tools/keypose_image_sampler/out/v87_stage2_phase3_real_source_frames/mediapipe_candidates/V87_MEDIAPIPE_SOURCE_FRAME_RUN_REPORT.json'
PAD=ROOT/'tools/keypose_image_sampler/out/v88_stage2_phase3_mediapipe_extraction/mediapipe_candidates/V88_MEDIAPIPE_PREPROCESSED_SOURCE_FRAME_RUN_REPORT.json'
OUT=ROOT/'qa/mesh_nex_v88_mediapipe'
OUT.mkdir(parents=True,exist_ok=True)
rank={'PASS_MEDIAPIPE_SOURCE_CANDIDATE':3,'REVIEW_BEFORE_RUNTIME_CONTRACT':2,'FIX_BEFORE_TEST_AUTHORITY':1,'NO_POSE':0}
raw=json.load(open(RAW)); pad=json.load(open(PAD))
by={}
for src_name,doc in [('raw',raw),('padded',pad)]:
    for r in doc['rows']:
        key=(r['exerciseKey'],r['phaseId'])
        r=dict(r); r['runVariant']=src_name
        current=by.get(key)
        if current is None or rank.get(r.get('mediaPipeGateStatus'),-1)>rank.get(current.get('mediaPipeGateStatus'),-1):
            by[key]=r
        elif current is not None and rank.get(r.get('mediaPipeGateStatus'),-1)==rank.get(current.get('mediaPipeGateStatus'),-1):
            if (r.get('candidateMetrics') or {}).get('criticalMeanVisibility',0) > (current.get('candidateMetrics') or {}).get('criticalMeanVisibility',0): by[key]=r
rows=list(by.values())
summary={
 'schema':'NEXT11_V88_MEDIAPIPE_EXTRACT_AND_GATE_V1',
 'base':'V87 real source frames + V88 neutral padding fallback',
 'candidateFrames':len(rows),'framesWithPose':sum(1 for r in rows if r.get('hasPose')),
 'passCandidates':sum(1 for r in rows if r.get('mediaPipeGateStatus')=='PASS_MEDIAPIPE_SOURCE_CANDIDATE'),
 'reviewCandidates':sum(1 for r in rows if r.get('mediaPipeGateStatus')=='REVIEW_BEFORE_RUNTIME_CONTRACT'),
 'fixBeforeTestAuthority':sum(1 for r in rows if r.get('mediaPipeGateStatus')=='FIX_BEFORE_TEST_AUTHORITY'),
 'noPose':sum(1 for r in rows if r.get('mediaPipeGateStatus')=='NO_POSE'),
 'coveredExercises':sorted(set(r['exerciseKey'] for r in rows)),
 'fullyPassExerciseKeys':sorted(set(r['exerciseKey'] for r in rows if r.get('mediaPipeGateStatus')=='PASS_MEDIAPIPE_SOURCE_CANDIDATE')),
 'stillNeedsBetterSourceOrRuntimeReview':sorted(set(r['exerciseKey'] for r in rows if r.get('mediaPipeGateStatus')!='PASS_MEDIAPIPE_SOURCE_CANDIDATE')),
 'unresolvedSourceGapExercises':raw['summary'].get('unresolvedSourceGapExercises',[]),
 'policy':'Candidate-only. PASS here means MediaPipe source-frame quality pass, not Nex approval.'}
(OUT/'V88_MEDIAPIPE_EXTRACT_AND_GATE_REPORT.json').write_text(json.dumps({'summary':summary,'rows':rows},indent=2,ensure_ascii=False),encoding='utf-8')
with open(OUT/'V88_MEDIAPIPE_EXTRACT_AND_GATE_REPORT.csv','w',newline='',encoding='utf-8') as f:
    w=csv.DictWriter(f,fieldnames=['exerciseKey','phaseId','status','runVariant','criticalMin','criticalMean','flags','assetPath']); w.writeheader()
    for r in rows:
        m=r.get('candidateMetrics') or {}; w.writerow({'exerciseKey':r['exerciseKey'],'phaseId':r['phaseId'],'status':r.get('mediaPipeGateStatus'),'runVariant':r.get('runVariant'),'criticalMin':round(m.get('criticalMinVisibility',0),6),'criticalMean':round(m.get('criticalMeanVisibility',0),6),'flags':','.join(r.get('mediaPipeGateFlags') or []),'assetPath':r['assetPath']})
try:
    font=ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf',12); font_b=ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf',13)
except Exception: font=font_b=ImageFont.load_default()
status_color={'PASS_MEDIAPIPE_SOURCE_CANDIDATE':(32,145,76),'REVIEW_BEFORE_RUNTIME_CONTRACT':(224,145,28),'FIX_BEFORE_TEST_AUTHORITY':(190,45,45),'NO_POSE':(110,110,110)}
def fit(path,size):
    im=Image.open(path).convert('RGB'); im.thumbnail(size,Image.LANCZOS); bg=Image.new('RGB',size,(246,246,246)); bg.paste(im,((size[0]-im.width)//2,(size[1]-im.height)//2)); return bg
cards=[]
for r in sorted(rows,key=lambda r:(-rank.get(r.get('mediaPipeGateStatus'),0),r['exerciseKey'],r['phaseId'])):
    card=Image.new('RGB',(620,245),(255,255,255)); d=ImageDraw.Draw(card)
    color=status_color.get(r.get('mediaPipeGateStatus'),(80,80,80)); d.rectangle([0,0,620,24],fill=color)
    d.text((8,4),f"{r.get('mediaPipeGateStatus')} — {r['exerciseKey']}",font=font_b,fill=(255,255,255)); d.text((8,30),f"{r['phaseId']} | {r.get('runVariant')}",font=font,fill=(0,0,0))
    src=ROOT/r['assetPath']; card.paste(fit(src,(270,170)),(8,60))
    ann=r.get('annotatedImage')
    if ann and (ROOT/ann).exists(): card.paste(fit(ROOT/ann,(270,170)),(340,60))
    m=r.get('candidateMetrics') or {}; lines=[f"critMin={m.get('criticalMinVisibility',0):.3f} critMean={m.get('criticalMeanVisibility',0):.3f}",f"flags={','.join(r.get('mediaPipeGateFlags') or [])[:70]}"]
    y=215
    for line in lines: d.text((8,y),line,font=font,fill=(35,35,35)); y+=14
    cards.append(card)
cols=2; w,h=620,245
sheet=Image.new('RGB',(cols*w,math.ceil(len(cards)/cols)*h),(240,240,240))
for i,c in enumerate(cards): sheet.paste(c,((i%cols)*w,(i//cols)*h))
sheet.save(OUT/'V88_MEDIAPIPE_EXTRACT_AND_GATE_CONTACT_SHEET.jpg',quality=92)
print(json.dumps(summary,indent=2))
