import csv
import hashlib
import importlib.util
import json
from pathlib import Path
import yaml

R=Path(__file__).parent
s=importlib.util.spec_from_file_location('ops',R/'ops.py');o=importlib.util.module_from_spec(s);s.loader.exec_module(o);p=o.p
plan=p.read('plan.json');build=p.read('channel3-build.json')
data=p.read('channel-window.json');summary=p.read('channel-window-summary.json')
profile=p.read('channel-publisher.json')['metrics']['profile']['channels']
rows=[]
for src,v in summary.items():
    key=hashlib.sha256(src.encode()).hexdigest();counts=profile[key]['counts']
    samples=[x['channels'][src]['redis'] for x in data['samples']]
    for item in samples:
        f=item['frame'];assert f['sourceId']==src and f['streamId']==p.CHANNELS[src]
        assert all(obj.get('trackId') is None for obj in f['objects'])
    rows.append(dict(channel=src[-2:],result_fps=v['fps']['result'],redis_fps=v['redis_fps'],
        submit_fps=v['fps']['publisher_submit'],queue_min=min(v['queue']),queue_max=max(v['queue']),
        stale=v['counter_delta'].get('stale',0),drop=v['counter_delta'].get('queue_drop',0),
        frame_replaced=counts.get('replaced_by_frame',0),state_replaced=counts.get('replaced_by_state',0),
        stored_min=min(x['length'] for x in samples),stored_max=max(x['length'] for x in samples),
        ttl_min_ms=min(x['pttl'] for x in samples),ttl_max_ms=max(x['pttl'] for x in samples)))
a,b=data['samples'][0]['publisher']['metrics'],data['samples'][-1]['publisher']['metrics']
ra,rb=data['resources'];dt=rb['at']-ra['at']
statistics=dict(publisher_delta={k:b.get(k,0)-a.get(k,0) for k in ('sent_frames','sent_status','replaced','expired','send_error','byte_limit','message_limit')},
    publisher_queue=[min(x['publisher']['metrics']['queued'] for x in data['samples']),max(x['publisher']['metrics']['queued'] for x in data['samples'])],
    cpu_cores=(rb['cpu']['usage_usec']-ra['cpu']['usage_usec'])/1e6/dt,memory_mib=[ra['memory']/1024**2,rb['memory']/1024**2],gpu=[ra['gpu'],rb['gpu']],
    throttle_delta=rb['cpu']['throttled_usec']-ra['cpu']['throttled_usec'])
with (R/'summary.csv').open('w') as f:
    writer=csv.DictWriter(f,fieldnames=list(rows[0]));writer.writeheader();writer.writerows(rows)
cur=p.base.inspect('contextra-analytics-1')
expected=dict(x.split('=',1) for x in plan['analytics']['Config']['Env'])
expected.update(CONTEXTRA_METADATA_WORKER_MODE='channel',CONTEXTRA_METADATA_ACTIVE_WORKER_LIMIT='16')
assert cur['Image']==build['image'] and cur['State']['Health']['Status']=='healthy'
assert dict(x.split('=',1) for x in cur['Config']['Env'])==expected
assert cur['HostConfig']['RestartPolicy']==plan['analytics']['HostConfig']['RestartPolicy']
assert sorted(cur['Mounts'],key=lambda x:x['Destination'])==sorted(plan['analytics']['Mounts'],key=lambda x:x['Destination'])
active=yaml.safe_load(p.ACTIVE.read_text())['services']['analytics'];assert active['image']==build['image']
for name,value in [('CONTEXTRA_IMAGE',build['tag']),('CONTEXTRA_METADATA_WORKER_MODE','channel'),('CONTEXTRA_METADATA_ACTIVE_WORKER_LIMIT','16')]:
    assert name+'='+value in p.ENV.read_text().splitlines()
    if name!='CONTEXTRA_IMAGE':assert active['environment'][name]==value
assert p.sha(p.prior.REG)==plan['registry_hash']
assert p.base.inspect('contextra-redis-1')['Id']==plan['redis']
assert p.prior.ops.prior.prev.sender()['executionId']==plan['sender']['executionId']
inv=p.base.inventory();assert {k:v for k,v in inv.items() if k!='/contextra-analytics-1'}=={k:v for k,v in plan['inventory'].items() if k!='/contextra-analytics-1'}
states={src:p.prior.state(sid) for src,sid in p.CHANNELS.items()}
for src,sid in p.CHANNELS.items():
    st=states[src];assert st['state']=='running' and st['last_error'] is None and st['cleanup_error'] is None
    assert not st['metadata_path']['performance']['detail'] and st['metadata_path']['visual'] is None
    assert p.api('/api/v1/streams/'+sid+'/settings')['configured']==plan['settings'][src]['configured']
pub=p.api('/api/v1/metadata/status')['metrics']
assert pub['workers']==3 and pub['controlWorkers']==1 and not pub['profile']['enabled']
assert not pub['workerErrors'] and not pub['terminalErrors'] and pub['retiringWorkers']==0
cleanup={sid:p.read('channel3-'+sid+'-cleanup.json')['last_cleanup'] for sid in p.CHANNELS.values()}
p.save('final.json',dict(rows=rows,statistics=statistics,states=states,cleanup_before_deploy=cleanup,build=build,publisher=pub,
    settings_preserved=True,registry_preserved=True,sender_redis_others_preserved=True,actual_persistent_aligned=True,metrics_off=True,
    channel_worker_retirement='16-channel isolated fixture and DetResultPath integration PASS; live new workers retained, not stopped'))
print(json.dumps(dict(rows=rows,statistics=statistics,cleanup=cleanup,workers=pub['workers'],control=pub['controlWorkers'],states={s:q['state'] for s,q in states.items()})))
