"""Evaluate frozen PNG pixels only. This script does not invoke product/model code."""
from pathlib import Path
import hashlib, json, sys
import numpy as np
from PIL import Image
from scipy.ndimage import binary_dilation, binary_erosion

ROOT=Path(__file__).resolve().parent
def sha(p): return hashlib.sha256(p.read_bytes()).hexdigest()
def load(name):
    with Image.open(ROOT/'fixtures'/name) as im: return np.asarray(im).copy()
def mask(name):
    a=load(name); assert a.shape==(512,512) and set(np.unique(a).tolist())=={0,255}
    return a==255
def fixture_checks():
    frozen=json.loads((ROOT/'frozen-cases.json').read_text(encoding='utf-8'))
    assert sha(ROOT/'frozen-cases.json')=='5569d699440ca57decb275abfe9353842bc564947b5bb153359ccd8c3ca63522'
    for a in frozen['assets']: assert sha(ROOT/a['path'])==a['sha256']
    y,x=np.indices((512,512)); dy,dx=np.indices((9,9)); disk=(dx-4)**2+(dy-4)**2<=16
    def rectangle(l,t,r,b): return (x>=l)&(x<=r)&(y>=t)&(y<=b)
    def ellipse(l,t,r,b): return (2*x-l-r)**2*(b-t)**2+(2*y-t-b)**2*(r-l)**2 <= (r-l)**2*(b-t)**2
    def rounded(l,t,r,b,radius):
        cx=np.clip(x,l+radius,r-radius); cy=np.clip(y,t+radius,b-radius)
        return rectangle(l,t,r,b)&((x-cx)**2+(y-cy)**2<=radius**2)
    bottle=rounded(170,150,342,420,22)|rounded(216,108,296,170,12)|rectangle(210,76,302,111)
    body=rounded(152,154,322,380,26)
    handle=ellipse(280,185,408,337)&~ellipse(307,211,380,310)
    mug=body|handle
    expected={
      'bottle-reference-mask.png':bottle,
      'bottle-core-mask.png':binary_erosion(bottle,structure=disk,border_value=0),
      'bottle-background-mask.png':~binary_dilation(bottle,structure=disk,border_value=0),
      'mug-reference-mask.png':mug,
      'mug-body-core-mask.png':binary_erosion(body,structure=disk,border_value=0),
      'mug-handle-core-mask.png':binary_erosion(handle,structure=disk,border_value=0)&(x>=330),
      'mug-hole-mask.png':ellipse(314,218,373,303)&(x>=330),
      'mug-background-mask.png':~binary_dilation(mug,structure=disk,border_value=0),
    }
    checks=[]
    for name,ref in expected.items():
        a=mask(name); assert int(a.sum())>0 and np.array_equal(a,ref),name
        checks.append({'path':'fixtures/'+name,'sha256':sha(ROOT/'fixtures'/name),'regionPixels':int(a.sum()),'independentGeometryAndDiskMatch':True})
    assert np.all(mask('mug-hole-mask.png')&~mug == mask('mug-hole-mask.png'))
    return {'originalFreezeHashMatch':True,'all10FixtureHashesMatch':True,'independentMethod':'NumPy vectorized exact integer geometry plus scipy.ndimage radius-4 disk morphology; no fixture renderer rerun and no product/model call.','diskOffsets':int(disk.sum()),'masks':checks}

def evaluate(case_id):
    frozen=json.loads((ROOT/'frozen-cases.json').read_text(encoding='utf-8'))
    case=next(c for c in frozen['cases'] if c['id']==case_id)
    short='primary' if case_id.endswith('primary') else 'boundary'
    prefix='bottle' if short=='primary' else 'mug'
    receipt=json.loads((ROOT/short/'http-receipt.json').read_text(encoding='utf-8'))
    inp=load(prefix+'-input.png'); source_file=ROOT/'fixtures'/(prefix+'-input.png')
    original_hash=next(a['sha256'] for a in frozen['assets'] if a['path']=='fixtures/'+prefix+'-input.png')
    facts={'httpStatus':receipt.get('status'),'originalInputSha256':original_hash,'afterInputSha256':sha(source_file),'inputUnchanged':sha(source_file)==original_hash,'responseSha256':sha(ROOT/short/'response.bin')}
    try:
        with Image.open(ROOT/short/'response.bin') as im:
            im.load(); facts.update(format=im.format,mode=im.mode,width=im.width,height=im.height)
            out=np.asarray(im).copy()
        valid=facts['format']=='PNG' and facts['mode']=='RGBA' and out.shape==(512,512,4)
    except Exception as exc:
        facts['decodeError']=type(exc).__name__+': '+str(exc); valid=False
    facts['validRgbaPng']=valid
    if not valid:
        conditions=[{'condition':c,'verdict':'failed' if i==0 else 'unverified','observed':'Native HTTP response is not a decodable 512x512 RGBA PNG; metrics cannot be computed from an absent/invalid product output.'} for i,c in enumerate(case['passConditions'])]
        return {'caseId':case_id,'facts':facts,'conditions':conditions,'outcome':'failed'}
    alpha=out[:,:,3]; ref=mask(prefix+'-reference-mask.png'); pred=alpha>=128
    inter=int(np.count_nonzero(pred&ref)); union=int(np.count_nonzero(pred|ref)); assert union>0
    rgb_equal=bool(np.array_equal(inp,out[:,:,:3]))
    facts.update(minAlpha=int(alpha.min()),maxAlpha=int(alpha.max()),rgbExactlyEqual=rgb_equal,rgbDifferentPixels=int(np.count_nonzero(np.any(inp!=out[:,:,:3],axis=2))),foregroundThreshold=128,iou={'intersectionPixels':inter,'unionPixels':union,'fraction':inter/union,'requiredMinimum':0.90},evaluatedPixels=512*512)
    def fraction(name,op,threshold,required):
        region=mask(name); total=int(region.sum()); assert total>0
        good=int(np.count_nonzero((alpha>=threshold if op=='>=' else alpha<=threshold)&region))
        return {'mask':'fixtures/'+name,'maskSha256':sha(ROOT/'fixtures'/name),'operator':op,'alphaThreshold':threshold,'matchingPixels':good,'regionPixels':total,'fraction':good/total,'requiredMinimum':required}
    facts['regions']={}
    if short=='primary':
        facts['regions']['foregroundCore']=fraction('bottle-core-mask.png','>=',245,.95)
        facts['regions']['background']=fraction('bottle-background-mask.png','<=',10,.95)
        answers=[receipt['status']==200 and facts['inputUnchanged'] and facts['minAlpha']<=10 and facts['maxAlpha']>=245,inter/union>=.90,all(v['fraction']>=v['requiredMinimum'] for v in facts['regions'].values()),rgb_equal]
        observations=[f"HTTP {receipt['status']}; PNG {facts['width']}x{facts['height']} {facts['mode']}; alpha range {facts['minAlpha']}–{facts['maxAlpha']}; input hash unchanged={facts['inputUnchanged']}.",f'Full-image IoU={inter}/{union}={inter/union:.12f}; required >=0.90.',f"Foreground core alpha>=245: {facts['regions']['foregroundCore']['matchingPixels']}/{facts['regions']['foregroundCore']['regionPixels']}={facts['regions']['foregroundCore']['fraction']:.12f}; exterior background alpha<=10: {facts['regions']['background']['matchingPixels']}/{facts['regions']['background']['regionPixels']}={facts['regions']['background']['fraction']:.12f}; each required >=0.95.",f"All 262144 RGB pixels exactly equal input={rgb_equal}; differing RGB pixels={facts['rgbDifferentPixels']}. Explicit frozen putalpha=true was retained."]
    else:
        facts['regions']['bodyCore']=fraction('mug-body-core-mask.png','>=',245,.95)
        facts['regions']['handleCore']=fraction('mug-handle-core-mask.png','>=',245,.90)
        facts['regions']['hole']=fraction('mug-hole-mask.png','<=',10,.95)
        facts['regions']['background']=fraction('mug-background-mask.png','<=',10,.95)
        answers=[receipt['status']==200 and facts['inputUnchanged'] and rgb_equal,inter/union>=.90,all(facts['regions'][k]['fraction']>=facts['regions'][k]['requiredMinimum'] for k in ('bodyCore','handleCore')),all(facts['regions'][k]['fraction']>=.95 for k in ('hole','background'))]
        def regional(keys): return '; '.join(f"{k}: {facts['regions'][k]['matchingPixels']}/{facts['regions'][k]['regionPixels']}={facts['regions'][k]['fraction']:.12f}, required >={facts['regions'][k]['requiredMinimum']}" for k in keys)
        observations=[f"HTTP {receipt['status']}; PNG {facts['width']}x{facts['height']} {facts['mode']}; every RGB pixel equal={rgb_equal}, differing={facts['rgbDifferentPixels']}; input hash unchanged={facts['inputUnchanged']}.",f'Full-image IoU={inter}/{union}={inter/union:.12f}; required >=0.90.',regional(('bodyCore','handleCore')),regional(('hole','background'))]
    conditions=[{'condition':c,'verdict':'passed' if answers[i] else 'failed','observed':observations[i]} for i,c in enumerate(case['passConditions'])]
    return {'caseId':case_id,'facts':facts,'conditions':conditions,'outcome':'passed' if all(answers) else 'failed'}

if __name__=='__main__':
    if len(sys.argv)>1 and sys.argv[1]=='preflight':
        result=fixture_checks(); path=ROOT/'preparation'/'independent-fixture-check.json'
    else:
        result={'fixtureCheck':fixture_checks(),'cases':[evaluate(c) for c in ('rembg-primary','rembg-boundary')]}; path=ROOT/'metrics.json'
    path.write_text(json.dumps(result,indent=2)+'\n',encoding='utf-8')
    print(json.dumps({'output':str(path),'cases':[(c['caseId'],c['outcome']) for c in result.get('cases',[])],'productOrModelCalls':0}))
