#!/usr/bin/env python3
"""Drive the running dev server; record only synthetic state, never config or credentials.
Usage: driver.py CONTROL_PORT PROJECT_ID WORKSPACE OUTPUT_PARENT LABEL COMMIT
The load hook only gates the completion reaction; settlement and provider execution are real.
"""
import sys,json,time,urllib.request,pathlib,uuid,os
port,project,workspace,parent,label,commit=sys.argv[1:]
out=pathlib.Path(parent)/str(time.time_ns());out.mkdir(parents=True)
start=time.monotonic();lines=[];events=[]
def log(text):
    elapsed=round(time.monotonic()-start,3)
    print(f'{elapsed:7.3f}s  {text}',flush=True)
    lines.append(f'{elapsed:7.3f}s  {text}')
    events.append([elapsed,'o',text+'\r\n'])
def call(endpoint,body={}):
    req=urllib.request.Request('http://127.0.0.1:'+port+'/'+endpoint,json.dumps(body).encode(),{'Content-Type':'application/json'})
    try:
        with urllib.request.urlopen(req,timeout=15) as r:return json.load(r)
    except urllib.error.HTTPError as e:raise RuntimeError(e.read().decode())
tid='evidence:'+str(uuid.uuid4());i=0
key={'host':'github.com','repository':'example/watch-evidence','number':7}
selection={'instanceId':'devReplay','model':'multi-turn'}
def cmd(type,**kw):
    global i;i+=1
    return call('command',{'type':type,'commandId':tid+':'+str(i),'threadId':tid,**kw})
def projection():return call('projection',{'threadId':tid})
def summary(p):
    return {'settled':p['thread']['settledAt'] is not None,'runs':[{'ordinal':r['ordinal'],'status':r['status'],'message': 'watch wake' if r['userMessageId']==tid+':wake' else 'active turn'} for r in p['runs']], 'sessions':[{'status':s['status'],'detached':s.get('detachedAt') is not None} for s in p['providerSessions']]}
def display(s):
    runs=' | '.join(r['message']+'='+r['status'] for r in s['runs'])
    sessions=','.join(v['status'] for v in s['sessions']) or 'none'
    return 'settled='+str(s['settled']).lower()+' | '+runs+' | sessions='+sessions
def record(name):
    p=projection();s=summary(p);log(name);log('  '+display(s));return p,s
try:
    log('Environment: real T3 development server, Linux; isolated synthetic database.')
    log('Commit: '+commit+'; '+label)
    log('Replay fixture; completion-promotion gate enabled identically on both commits.')
    call('settings',{'sidebarAutoSettleOnMerge':True,'sidebarAutoSettleAfterDays':None})
    log('Enabled Settle on merge.')
    cmd('thread.create',projectId=project,title='Watch wake race evidence',modelSelection=selection,runtimeMode='full-access',interactionMode='default',branch=None,worktreePath=workspace,createdBy='user',creationSource='web')
    cmd('thread.pull-request.watch',**key,watching=True,link={'url':'https://github.com/example/watch-evidence/pull/7','source':'agent'})
    now=time.strftime('%Y-%m-%dT%H:%M:%S',time.gmtime())+'.000Z'
    snapshot={'state':'open','title':'Synthetic watch target','headBranch':'feature','baseBranch':'main','isDraft':False,'updatedAt':now,'syncedAt':now}
    cmd('thread.pull-request-link.sync',**key,snapshot=snapshot,stack=None)
    watch=projection()['thread']['pullRequests'][0]['watch']
    call('arm',{'threadId':tid})
    cmd('message.dispatch',createdBy='user',creationSource='web',messageId=tid+':active',text='Keep working while the watched pull request changes.',attachments=[],modelSelection=selection,dispatchMode={'type':'start_immediately'})
    log('Started the active replay turn with an open watched pull request.')
    cmd('thread.pull-request-watch.sync',**key,startedAt=watch['startedAt'],watch={**watch,'headSha':'synthetic-merged','wakes':1},wake={'messageId':tid+':wake','text':'The watched pull request has merged. Confirm the result.','notification':{'source':{'kind':'monitor'},'outcome':'updated','summary':'Synthetic pull request merged'}})
    p,s=record('Watch update delivered behind active turn')
    if not any(r['status']=='queued' and r['userMessageId']==tid+':wake' for r in p['runs']):raise RuntimeError('Wake did not queue; reproduction invalid.')
    merged=time.strftime('%Y-%m-%dT%H:%M:%S',time.gmtime())+'.999Z'
    cmd('thread.pull-request-link.sync',**key,snapshot={**snapshot,'state':'merged','mergedAt':merged,'syncedAt':merged},stack=None)
    log('Recorded merged pull request while active turn was in progress.')
    deadline=time.monotonic()+40
    while not call('gate')['reached']:
        if time.monotonic()>deadline:raise RuntimeError('Completion gate was not reached.')
        time.sleep(.05)
    log('Active turn completed naturally; completion reaction paused before queue promotion.')
    record('At completion boundary')
    hold=float(os.environ.get('EVIDENCE_HOLD_SECONDS','4'))
    remaining=hold
    while remaining>0:
        interval=min(30,remaining);time.sleep(interval);remaining-=interval
        if hold>4:record('Still delaying promotion; '+str(int(hold-remaining))+' seconds after completion')
    record('After normal automatic-settlement worker ran, before promotion release')
    call('release');log('Released completion reaction; queued wake may now start.')
    end=time.monotonic()+8;last=None
    while time.monotonic()<end:
        p=projection();s=summary(p)
        if s!=last:log('Observed: '+display(s));last=s
        time.sleep(.1)
    record('Final observed state')
except Exception as e:
    log('INCOMPLETE: '+str(e))
    raise
finally:
    call('release')
    (out/(label+'.txt')).write_text('\n'.join(lines)+'\n')
    header={'version':2,'width':160,'height':36,'title':label+' - real server output','env':{'TERM':'xterm-256color'}}
    (out/(label+'.cast')).write_text('\n'.join([json.dumps(header)]+[json.dumps(e) for e in events])+'\n')
    (out/'metadata.json').write_text(json.dumps({'commit':commit,'side':label,'duration':time.monotonic()-start}))
