"""CPU-only rule contract checks and explicitly synthetic visual samples."""
from copy import deepcopy
import json
from pathlib import Path
import sys
from PIL import Image, ImageDraw
sys.path.insert(0,'/tests')
from multi_roi_fixtures import request,frame,observation,pose,LEFT,Manager
from app.multi_roi.adapter import normalize
from app.multi_roi.policy import DEFAULT
from app.multi_roi.runtime import RuleRuntime
from app.multi_roi.artifacts import event_snapshot
from app.multi_roi.results import merge
from app.artifact_media import draw_frame
from app.secure_control import ControlService,Start
from app.multi_roi.errors import RoiError
from app.fire_config import FIRE_MODEL

OUT=Path('/review/03_mock_diagrams');OUT.mkdir(parents=True,exist_ok=True)
CHECKS={}
def check(name,value):
    assert value,name
    CHECKS[name]='PASS'
def picture(name,snapshot,title,event=None,scenario=None):
    canvas=Image.new('RGB',(1280,720),'#20262e')
    d=ImageDraw.Draw(canvas);d.line((640,0,640,720),fill='#999999',width=2)
    result=draw_frame(canvas,snapshot,event=event,scenario=scenario)
    d=ImageDraw.Draw(result);d.rectangle((0,0,1279,60),fill='white');d.text((20,15),'SYNTHETIC MOCK ONLY | '+title,fill='black')
    result.save(OUT/(name+'.png'))
    (OUT/(name+'.json')).write_text(json.dumps(snapshot,ensure_ascii=False,indent=2))

d=request('INTRUSION','INTRUSION');d['rois']=[dict(roiId=1,points=LEFT),dict(roiId=2,points=LEFT,outside=True)]
d['rules'][0].update(roiId=1,parameters={'person_confidence':.8})
d['rules'][1].update(roiId=2,parameters={'person_confidence':.2})
r=RuleRuntime(normalize(d,DEFAULT).payload());f=frame()
res=r.person(f,(observation(f,x=.2,score=.5),observation(f,x=.8,score=.5)),(None,None),[1,2],(0,1))
check('inside_outside_independent_thresholds',[x.visible for x in res.commands]==[False,True])
picture('01_outside_and_threshold',res.snapshot(),'left 0.8 hides 0.5; outside-left 0.2 shows 0.5')
r.close()
r=RuleRuntime(normalize(request(),DEFAULT).payload());f=frame()
res=r.person(f,(observation(f,x=.2),observation(f,x=.8)),(None,None),[1,2],(0,1))
check('all_off_person_only_no_events',len(res.commands)==2 and not res.events)
picture('02_all_off_person',res.snapshot(),'all scenarios OFF: person boxes remain, no events');r.close()
d=request('INTRUSION','FIRE');d['rules'][0]['parameters']=dict(dwell_seconds=.1)
r=RuleRuntime(normalize(d,DEFAULT).payload(),clock=lambda:10.)
for i in range(5):
    f=frame(i);person=r.person(f,(observation(f),),(None,),[1],(0,))
    fire=r.provider('fire',f,(observation(f,FIRE_MODEL,x=.8,cls=1),))
    both=merge(f,[person,fire],camera_id=1)
    if person.events:break
check('person_event_generated',bool(person.events))
event=person.events[0].payload();scoped=event_snapshot(both.snapshot(),event)
check('event_hides_other_provider',len(both.snapshot()['objects'])==2 and len(scoped['objects'])==1)
picture('03_live_union',both.snapshot(),'live union includes person and fire')
picture('04_intrusion_event_only',both.snapshot(),'intrusion artifact hides fire provider',event=event,scenario='INTRUSION')
check('old_run_event_not_reinterpreted',event_snapshot(dict(both.snapshot(),runId='changed'),event) is None);r.close()
d=request('INTRUSION','INTRUSION')
for rule in d['rules']:rule['parameters']=dict(dwell_seconds=.1,cooldown_seconds=10)
r=RuleRuntime(normalize(d,DEFAULT).payload(),clock=lambda:10.)
for i in range(5):
    f=frame(i);res=r.person(f,(observation(f),),(None,),[1],(0,))
    if res.events:break
check('rule_events_cooldown_independent',{e.rule_id for e in res.events}=={0,1} and len({e.event_id for e in res.events})==2)
selected=event_snapshot(res.snapshot(),res.events[0].payload())
check('shared_provider_rule_scoped',all(len(o['scenarioResults'])==1 for o in selected['objects']))
picture('05_shared_provider_rule_only',res.snapshot(),'one event rule, even when two rules share one person',event=res.events[0].payload(),scenario='INTRUSION');r.close()
cases=[]
d=request('FIRE');d['rois']=[dict(roiId=0,outside=True)];cases.append(('full_frame_inversion','EMPTY_EFFECTIVE_REGION',d))
d=request('FIRE');d['rois']=[dict(roiId=0,points=LEFT)];cases.append(('reserved_id_partial_region','FULL_FRAME_ID_CONFLICT',d))
d=request('FIRE');d['rules'].append(deepcopy(d['rules'][0]));cases.append(('duplicate_rule_id','DUPLICATE_RULE_ID',d))
d=request(*(['FIRE']*49));cases.append(('rule_limit','RULE_LIMIT_EXCEEDED',d))
for name,expected,d in cases:
    try:normalize(d,DEFAULT);raise AssertionError(name)
    except RoiError as e:check(name,e.code==expected)
manager=Manager();service=ControlService(manager,'server',dict(store=False),roi_policy_path=OUT/'mock-policy.json')
store=service.roi_policy;s=store.status();store.apply(s['revision'],{'mode':'multi_roi'},configured_revision=s['configuredRevision'])
d=request('INTRUSION','LOITERING');a=service.start_camera(Start(json.dumps(d)));starts=manager.starts
reordered=deepcopy(d);reordered['rules'].reverse();service.start_camera(Start(json.dumps(reordered)))
check('list_order_reuses_run',manager.starts==starts)
changed=deepcopy(d);changed['rules'][0]['ruleId']=91;service.start_camera(Start(json.dumps(changed)))
check('rule_id_change_new_run',manager.starts==starts+1)
empty=request();a=service.start_camera(Start(json.dumps(empty)))
check('omitted_rules_removed',not a.group.spec['intrusion']['enabled'] and not a.group.spec['loitering']['enabled'])
before=manager.starts;service.stop_camera(1);service.start_camera(Start(json.dumps(empty)))
check('stop_then_start_new_run',manager.starts==before+1)
(OUT/'checks.json').write_text(json.dumps(dict(scope='CPU_MOCK_ONLY',checks=CHECKS),indent=2))
print(json.dumps({'mock_checks':len(CHECKS),'result':'PASS','pictures':5}))
