"""Same real decoded frame + exact saved metadata, no RTSP or new inference."""
import json
from pathlib import Path
import threading
import time
from PIL import Image
from app.artifact_media import packets,draw_frame
import gi
gi.require_version('Gst','1.0');gi.require_version('GstVideo','1.0')
from gi.repository import Gst,GstVideo
Gst.init(None)
ROOT=Path('/artifacts');stage=ROOT/'recovery-fire'
job=json.loads((stage/'job.json').read_text())
snapshots={x['ptsNs']:x for x in job['metadata'] if any(o.get('visible') for o in x['objects'])}
first=next(packets(stage/'capture.bin'));origin=first[0][0]
caps=Gst.Caps.from_string(first[1]);kind=caps.get_structure(0).get_name()
decoder={'video/x-h264':'h264parse ! avdec_h264 max-threads=2','video/x-h265':'h265parse ! avdec_h265 max-threads=2'}[kind]
p=Gst.parse_launch(f'appsrc name=input format=time block=true max-bytes=4194304 ! {decoder} ! videoconvert ! video/x-raw,format=RGB ! appsink name=output sync=false max-buffers=2 drop=false')
source=p.get_by_name('input');sink=p.get_by_name('output');source.set_property('caps',caps)
def feed():
    for h,description,data in packets(stage/'capture.bin'):
        b=Gst.Buffer.new_allocate(None,len(data),None);b.fill(0,data);b.pts=max(0,h[0]-origin)
        b.dts=Gst.CLOCK_TIME_NONE if h[1]==Gst.CLOCK_TIME_NONE else max(0,h[1]-origin);b.duration=h[2]
        if not h[5]:b.set_flags(Gst.BufferFlags.DELTA_UNIT)
        if source.emit('push-buffer',b)!=Gst.FlowReturn.OK:return
    source.emit('end-of-stream')
thread=threading.Thread(target=feed,daemon=True);chosen=None
try:
    p.set_state(Gst.State.PLAYING);thread.start();deadline=time.monotonic()+20
    while time.monotonic()<deadline:
        sample=sink.emit('try-pull-sample',100*Gst.MSECOND)
        if sample is None:
            if sink.get_property('eos'):break
            continue
        buffer=sample.get_buffer();pts=int(buffer.pts)+origin
        if pts not in snapshots:continue
        info=GstVideo.VideoInfo.new_from_caps(sample.get_caps());ok,m=buffer.map(Gst.MapFlags.READ);assert ok
        try:
            raw=b''.join(bytes(m.data[y*info.stride[0]:y*info.stride[0]+info.width*3]) for y in range(info.height))
            chosen=(Image.frombytes('RGB',(info.width,info.height),raw),snapshots[pts]);break
        finally:buffer.unmap(m)
finally:
    p.set_state(Gst.State.NULL);thread.join(3)
assert chosen is not None and not thread.is_alive()
image,snapshot=chosen
out=Path('/review/02_same_frame_camera5');out.mkdir(parents=True,exist_ok=True)
draw_frame(image,None).save(out/'01_source_frame.png')
draw_frame(image,snapshot).save(out/'02_live_union.png')
events={}
for path in ROOT.glob('cameras/5/*/*/event.json'):
    e=json.loads(path.read_text())['event'];events.setdefault(e['ruleId'],e)
events[3]=job['event']
from app.multi_roi.artifacts import event_snapshot
counts={}
for rule,label in ((1,'LEFT'),(2,'OUTSIDE_LEFT'),(3,'FULL_FRAME')):
    e=events[rule];selected=event_snapshot(snapshot,e);assert selected is not None
    assert all(all(r['ruleId']==rule for r in x['scenarioResults']) for x in selected['objects'])
    counts[str(rule)]=len(selected['objects'])
    draw_frame(image,snapshot,event=e,scenario='FIRE').save(out/f'{rule+2:02d}_event_{label}_rule{rule}.png')
(out/'same-frame-evidence.json').write_text(json.dumps(dict(scope='REAL_CAPTURE_OFFLINE_RENDER_NO_NEW_INFERENCE',
    ptsNs=snapshot['ptsNs'],frameSeq=snapshot['frameSeq'],unionObjects=len(snapshot['objects']),eventObjects=counts),indent=2))
print(json.dumps({'same_frame_comparison':'PASS','event_object_counts':counts}))
