import importlib.util
import json
from pathlib import Path
import subprocess
import time

root=Path(__file__).parent
spec=importlib.util.spec_from_file_location('medium4_task',root/'run.py')
m=importlib.util.module_from_spec(spec);spec.loader.exec_module(m)
plan=m.p.read('plan.json');final=m.p.read('final.json')
assert not final['failed'] and final['instance']==4
states=m.states();added=plan['channels'][3:]
assert len(added)==13 and all(m.p.quiescent(states[r['stream_id']]) for r in added)
assert all(states[r['stream_id']]['child_pid'] is None for r in added)
assert all(m.load_preset(m.PRESETS/(r['stream_id']+'.yaml'))['camera']['source_id']==r['source_id'] for r in added)
old=[m.p.read('ascent-'+r['stream_id']+'-first-frame.json')['state']['child_pid'] for r in added]
current=m.p.base.inspect('contextra-analytics-1')
assert current['Image']==plan['analytics']['Image'] and current['Id']==plan['analytics']['Id']
for field in ('NanoCpus','CpuQuota','CpusetCpus','Memory','MemorySwap','RestartPolicy'):
    assert current['HostConfig'][field]==plan['analytics']['HostConfig'][field]
code="""import json,sys,urllib.request
from pathlib import Path
pids=json.loads(sys.argv[1]);assert all(not Path('/proc/'+str(p)).exists() for p in pids)
r=urllib.request.urlopen('http://127.0.0.1:8000/v2/models/RFDETRMedium/versions/2/ready',timeout=5)
print(json.dumps(dict(removed_children=len(pids),model_ready=r.status)))
"""
out=subprocess.run(['docker','exec','contextra-analytics-1','python3','-c',code,json.dumps(old)],capture_output=True,text=True,timeout=10)
assert out.returncode==0,out.stderr
checks=json.loads(out.stdout)
rows=plan['channels'][:3];a=m.sample(rows);time.sleep(5);b=m.sample(rows);dt=b['at']-a['at']
recovery=[]
for row in rows:
    sid=row['stream_id'];x,y=a['channels'][sid],b['channels'][sid]
    assert x['run_id']==y['run_id'] and y['state']=='running' and y['error'] is None
    rx,ry=x['redis'],y['redis']
    recovery.append(dict(stream_id=sid,seconds=dt,result_fps=(y['processed']-x['processed'])/dt,
        redis_fps=(ry['entries']-rx['entries'])/(ry['at']-rx['at']),queue=y['queue'],stale=y['stale']-x['stale']))
frames={r['stream_id']:m.rt.redis_sample(r,frame=True) for r in rows}
for row in rows:
    sid=row['stream_id'];frame=frames[sid]['frame']
    assert frame['sourceId']==row['source_id'] and frame['runId']==b['channels'][sid]['run_id']
    assert frame['frameTime']['rtpClockRate']==90000 and frame['coordinateSpace']=='SOURCE_FRAME'
    assert all(o['trackId'] is None and not o['eventIds'] for o in frame['objects'])
m.p.save('final-frame-samples.json',frames)
checks.update(recovery=recovery,registered_added=13,added_stopped=13,active=3,
    preset_directory=str(m.PRESETS),manifest=str(m.MANIFEST),publisher=m.api('/api/v1/metadata/status')['metrics'],
    settings_preserved=True,instance=4,resources=m.guard(),trace=m.triton('v2/trace/setting'))
assert checks['publisher']['workers']==3 and checks['trace']['trace_level']==['OFF']
assert all(not s.get('metadata_path',{}).get('performance',{}).get('detail',False) for s in m.states().values())
checks['evidence_bytes']=sum(p.stat().st_size for p in root.rglob('*') if p.is_file())
assert checks['evidence_bytes']<30*1024**2
m.p.save('readback.json',checks)
summaries=[m.p.read('stage'+str(n)+'-summary.json') for n in (4,8,12,16)]
m.p.save('outcome.json',dict(last_accepted_stage=8,sixteen='target_not_met',ascent_runs=1,
    preparation_corrections=1,instance=4,instance_preserved_reason='Stable execution and eight-channel acceptance; no startup/resource failure or downstream regression.',
    next='Bounded Triton execution scheduling observation to distinguish instance usage and request wait; no new sixteen-channel repeat first.',
    summaries=summaries,readback=checks))
print(json.dumps(dict(checks=checks,stage_summary=[dict(stage=s['stage'],accepted=s['accepted'],
    result_range=[min(r['result_fps'] for r in s['rows']),max(r['result_fps'] for r in s['rows'])],
    redis_range=[min(r['redis_fps'] for r in s['rows']),max(r['redis_fps'] for r in s['rows'])],
    result_sum=sum(r['result_fps'] for r in s['rows']),triton_fps=s['triton']['inference_fps'],
    queue_ms=s['triton']['stats']['queue']['mean_ms'],infer_ms=s['triton']['stats']['compute_infer']['mean_ms'],
    replaced=s['publisher_delta']['replaced']) for s in summaries]),indent=2))
