from ops import *
import yaml

FALL=dict(person_score=.15,joint_score=.10,down_angle_deg=50.)
FIGHT=dict(person_score=.15,joint_score=.10,pair_distance_ratio=1.8,
    pair_close_hold_seconds=.1,arm_motion_window_seconds=1.,arm_motion_ratio=.10,
    toward_opponent_ratio=.01,required_bursts=1)
assert not (HERE/'migration.json').exists()
assert inspect('contextra-ai-gateway-1')['State']['Running']
cmd(['docker','stop','--signal=SIGTERM','--timeout=-1','contextra-ai-gateway-1'],timeout=90)
assert not inspect('contextra-ai-gateway-1')['State']['Running']
rows=api('/api/v1/streams')['streams']
save('stop-intent.json',{'streams':[r['stream_id'] for r in rows if r['state']!='stopped']})
for r in rows:
    if r['state']!='stopped' or r.get('child_pid') or r.get('cleanup_pending'):
        request('/api/v1/streams/'+r['stream_id']+'/stop',{},timeout=80)
deadline=time.monotonic()+90
while True:
    try:no_channels();break
    except AssertionError:
        if time.monotonic()>deadline:raise RuntimeError('STOP_REAP_INCOMPLETE')
        time.sleep(2)
deadline=time.monotonic()+45
while True:
    state=ledger()
    if not state['reservations']:break
    if time.monotonic()>deadline:raise RuntimeError('ARTIFACT_NOT_QUIESCENT')
    time.sleep(2)
save('ledger-quiescent.json',state)
source=Path('/data/contextra/runtime-state/streams_runtime.yaml')
shutil.copyfile(source,PRIVATE/'streams-stopped.before')
raw=yaml.safe_load(source.read_text());expected=json.loads(json.dumps(raw))
changed=[]
for row in expected['added']:
    patch={}
    for name,values in [('fall',FALL),('fight',FIGHT)]:
        if name in row and row[name] is not None:
            if name=='fall':assert 'test_skip_down_hold' in row[name]
            patch[name]=values
            row[name].update(values)
    if not patch:continue
    sid=row['stream_id'];path='/api/v1/streams/'+sid+'/settings'
    before=api(path)['configured']
    result=request(path,dict(expected_revision=before['revision'],**patch),method='PATCH')
    after=api(path)['configured']
    for name,values in patch.items():
        old=dict(before[name]);old.update(values)
        assert after[name]==old,(sid,name,'unexpected setting change')
    for name in before:
        if name not in patch and name!='revision':assert after[name]==before[name],name
    assert after['revision']!=before['revision']
    request(path,dict(expected_revision=after['revision'],**patch),method='PATCH')
    assert api(path)['configured']['revision']==after['revision']
    changed.append(dict(stream_id=sid,sections=list(patch),skip_hold=after['fall']['test_skip_down_hold']))
actual=yaml.safe_load(source.read_text())
assert actual==expected,'Persistent file changed outside approved fields'
no_channels()
assert ledger()==state,'Unexpected ledger mutation during settings migration'
save('migration.json',dict(changed=changed,fall=sum('fall'in r['sections'] for r in changed),
    fight=sum('fight'in r['sections'] for r in changed),all_stopped=True,cas_noop=True,unrelated_preserved=True))
print(json.dumps(dict(stopped=len(rows),fall=sum('fall'in r['sections'] for r in changed),fight=sum('fight'in r['sections'] for r in changed))))
