#!/usr/bin/env python3
"""Real CLI observer. Requires a fresh running dev server (T3CODE_DEV_SEED=0).
Usage: T3_PROOF_PROVIDER=... T3_PROOF_MODEL=... python3 driver.py CHECKOUT SIDE OUTPUT_ROOT PROJECT
Runtime identifiers are deliberately never written into public output.
"""
import datetime, json, os, pathlib, sqlite3, subprocess, sys, textwrap, time
root, side, output, project = map(pathlib.Path, sys.argv[1:])
side = str(side)
out = output / (side + '-' + datetime.datetime.now(datetime.timezone.utc).strftime('%Y%m%dT%H%M%SZ'))
out.mkdir(parents=True)
provider, model = os.environ['T3_PROOF_PROVIDER'], os.environ['T3_PROOF_MODEL']
sha = subprocess.check_output(['git','rev-parse','HEAD'],cwd=root,text=True).strip()
cli = ['node',str(root/'apps/server/src/bin.ts')]
location = ['--base-dir',str(root/'.t3')]
db = sqlite3.connect('file:'+str(root/'.t3/userdata/statev2.sqlite')+'?mode=ro',uri=True)
start=time.monotonic(); lines=[]; results=[]; recorder=None
log=(out/'observations.jsonl').open('w')
def clean(s):
    for a,b in [(str(root),'<checkout>'),(str(project),'<synthetic-project>'),(model,'<runtime-model>'),(provider,'<provider>')]: s=s.replace(a,b)
    return s

def emit(s, **data):
    stamp=round(time.monotonic()-start,2)
    lines.append(f'{stamp:7.2f}s  '+clean(s))
    log.write(json.dumps({'elapsed':stamp,'text':clean(s),**data})+'\n'); log.flush()
    print(lines[-1],flush=True)

def paint(case, running):
    content=[f'ISSUE 34688 | {side.upper()} | {sha[:10]}',
       'Real isolated server + real CLI + real provider | synthetic git project',
       'Live command monitor | 1600 x 1000 | dark | 2 fps | real-time (1x)',
       f'Elapsed: {time.monotonic()-start:7.2f}s | Case: {case} | CLI: {running}',
       '','Commands use --wait --json. Runtime selection and local paths omitted.',
       'Observer reads this isolated server database read-only. No test mocks.','']
    body=[]
    for s in lines: body += textwrap.wrap(s, width=125, subsequent_indent='          ') or ['']
    content += body[-33:]
    tmp=out/'display.tmp'; tmp.write_text('\n'.join(content)); tmp.replace(out/'display.txt')

def launch_recording():
    global recorder
    paint('initializing','not started')
    f=(out/'ffmpeg-private.log').open('w')
    recorder=subprocess.Popen(['ffmpeg','-y','-hide_banner','-loglevel','warning','-re','-f','lavfi','-i','color=c=0x101823:s=1600x1000:r=2',
      '-vf',f'drawtext=fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf:textfile={out}/display.txt:reload=1:expansion=none:fontcolor=0xe2eaf4:fontsize=18:line_spacing=2:x=28:y=26',
      '-c:v','libx264','-preset','veryfast','-crf','22','-pix_fmt','yuv420p',str(out/'recording.mp4')],stdout=f,stderr=f,stdin=subprocess.PIPE)

def run(case, message, thread=None):
    before=set(x[0] for x in db.execute('select thread_id from orchestration_v2_projection_threads'))
    args=cli+['thread','send' if thread else 'start',thread or str(project),message,'--wait','--json']+location
    if not thread: args += ['--provider',provider,'--model',model]
    emit(f'BEGIN {case}: t3 thread '+('send <thread>' if thread else 'start <project>')+' --wait --json')
    emit('Prompt: '+message)
    t=time.monotonic(); p=subprocess.Popen(args,cwd=project,stdout=subprocess.PIPE,stderr=subprocess.PIPE,text=True)
    seen={}; returned=False; reply=None; status=None; observed_wake=False; settled=None
    while time.monotonic()-t < 300:
        if not thread:
            new=[x[0] for x in db.execute('select thread_id from orchestration_v2_projection_threads') if x[0] not in before]
            if new: thread=new[0]
        rows=[]
        if thread:
            rows=db.execute('select run_id,ordinal,status,requested_at,completed_at from orchestration_v2_projection_runs where thread_id=? order by ordinal',(thread,)).fetchall()
            for rid,ordinal,st,requested,completed in rows:
                key=('run',rid)
                if seen.get(key)!=st:
                    seen[key]=st; emit(f'Observed run {ordinal}: {st}',case=case,run=ordinal,status=st,requestedAt=requested,completedAt=completed)
            for mid,rid,text in db.execute("select message_id,run_id,json_extract(payload_json,'$.text') from orchestration_v2_projection_messages where thread_id=? and role='assistant' and streaming=0 order by created_at",(thread,)):
                key=('message',mid)
                if key not in seen:
                    seen[key]=True
                    ordinal=next((r[1] for r in rows if r[0]==rid),'?')
                    emit(f'Assistant run {ordinal}: '+(text or ''),case=case)
            for iid,typ,st,payload in db.execute("select turn_item_id,type,status,payload_json from orchestration_v2_projection_turn_items where thread_id=? and type in ('dynamic_tool','command_execution','notification') order by ordinal",(thread,)):
                # Only report the relevant background calls, without provider payload metadata.
                if 'background' in payload or 'sleep 45' in payload:
                    key=('item',iid)
                    if seen.get(key)!=st:
                        seen[key]=st; emit(f'Background-related {typ}: {st}',case=case)
        if not returned and p.poll() is not None:
            stdout,stderr=p.communicate(); duration=round(time.monotonic()-t,2); returned=True
            (out/(case+'-stdout.json')).write_text(clean(stdout)); (out/(case+'-stderr.txt')).write_text(clean(stderr))
            try: result=json.loads(stdout); reply=result.get('reply'); status=result.get('status'); result_run=result.get('runId')
            except Exception: reply=clean(stdout+stderr); status='parse-error'; result_run=None
            ordinal=next((r[1] for r in rows if r[0]==result_run),None)
            emit(f'CLI EXIT {p.returncode} after {duration}s | status={status} | returned run={ordinal}',case=case)
            emit('CLI reply: '+str(reply),case=case)
            results.append(dict(case=case,elapsed=duration,exitCode=p.returncode,status=status,returnedRun=ordinal,reply=reply))
        is_background=case!='plain-ready'
        observed_wake=bool(rows and rows[-1][1] >= (3 if case=='send-background' else 2)) if is_background else False
        terminal=bool(rows and rows[-1][2] in ('completed','failed','interrupted','cancelled'))
        if returned and terminal and (observed_wake or not is_background):
            if settled is None:
                settled=time.monotonic(); emit(f'END {case}: CLI returned; '+('wake run is terminal.' if is_background else 'plain reply is terminal.'))
            if time.monotonic()-settled>=3: break
        paint(case,'returned' if returned else 'RUNNING; no CLI result yet')
        time.sleep(.25)
    else:
        emit('TIMEOUT: 300s observation ceiling reached; evidence is incomplete.')
        if p.poll() is None: p.terminate(); p.wait()
    paint(case,'returned' if returned else 'timeout')
    return thread

try:
    emit('Starting continuous capture; no attempts are hidden or spliced.')
    launch_recording()
    seed=run('plain-ready','Reply exactly READY.')
    run('start-background','Run `sleep 45` in the background (run_in_background), call t3_background_wait on its task ID, and end your turn. When it finishes, reply DONE.')
    run('send-background','Run `sleep 45` in the background (run_in_background), call t3_background_wait on its task ID, and end your turn. When it finishes, reply DONE.',seed)
    emit('All scripted cases finished.'); paint('finished','returned'); time.sleep(3)
finally:
    if recorder:
        recorder.communicate(b'q\n',timeout=15)
    (out/'summary.json').write_text(json.dumps({'side':side,'sha':sha,'results':results},indent=2))
    log.close()
    print('OUTPUT '+str(out),flush=True)
