#!/usr/bin/env python3
"""Real T3 server/CLI proof. All repositories and data are synthetic and fresh.
Usage: python3 prove.py CHECKOUT OUTPUT_DIRECTORY SIDE
Run inside a terminal to capture the same scripted interaction on both revisions.
"""
import os, sys, subprocess as sp, json, pathlib, time, signal, urllib.request, socket
checkout=pathlib.Path(sys.argv[1]).resolve(); out=pathlib.Path(sys.argv[2]).resolve(); side=sys.argv[3]
setting_on='--setting-off' not in sys.argv[4:]
out.mkdir(parents=True,exist_ok=False)
home=out/'home'; (home/'userdata').mkdir(parents=True)
settings={'defaultModelSelection':{'instanceId':'devReplay','model':'showcase'},'providerInstances':{k:{'driver':k,'enabled':False} for k in ['codex','claudeAgent','cursor','grok','muse','pi','opencode','antigravity']}}
if not setting_on: settings['newWorktreesStartFromOrigin']=False
(home/'userdata/settings.json').write_text(json.dumps(settings))
env={**os.environ,'T3CODE_DEV_SEED':'0','T3CODE_AUTO_BOOTSTRAP_PROJECT_FROM_CWD':'0'}
# Avoid inherited runtime configuration. Only the isolated home is used.
for key in ['T3CODE_HOME','T3CODE_DEV_AUTH_TOKEN','VITE_HTTP_URL','VITE_WS_URL']: env.pop(key,None)
transcript=open(out/'transcript.txt','w')
def emit(s=''):
    print(s,flush=True); transcript.write(s+'\n'); transcript.flush()
def run(args,cwd=checkout):
    r=sp.run([str(x) for x in args],cwd=cwd,env=env,text=True,stdout=sp.PIPE,stderr=sp.PIPE)
    if r.returncode: raise RuntimeError(f'{args[0:3]} exit {r.returncode}: {r.stderr} {r.stdout}')
    return r.stdout.strip()
def git(repo,*args): return run(['git','-C',repo,*args])
def cli(*args): return run(['node','apps/server/src/bin.ts',*args,'--base-dir',home])
def snap(label):
    if os.environ.get('DISPLAY'):
        time.sleep(1)
        a=out/(label+'-stable-a.png'); b=out/(label+'.png')
        sp.run(['import','-window','root',str(a)],check=True)
        time.sleep(.5)
        sp.run(['import','-window','root',str(b)],check=True)
        if a.read_bytes()!=b.read_bytes():
            # Pixel comparison avoids metadata differences.
            if sp.run(['compare','-metric','AE',str(a),str(b),'null:'],stdout=sp.PIPE,stderr=sp.PIPE).returncode: raise RuntimeError('Capture was not stable')
        time.sleep(2)
print('\033[?25l',end='',flush=True)
sha=git(checkout,'rev-parse','HEAD')
emit('T3 Code | real server + CLI | '+side.upper())
emit('Commit: '+sha)
emit('Environment: Linux, isolated fresh T3 home, Replay (dev) / showcase')
emit('Synthetic Git fixture; no live user data; same driver on both sides.\n')
# Fixed identity and dates make the fixture commit IDs identical on both sides.
env.update({'GIT_AUTHOR_NAME':'Proof','GIT_AUTHOR_EMAIL':'proof@example.invalid','GIT_COMMITTER_NAME':'Proof','GIT_COMMITTER_EMAIL':'proof@example.invalid','GIT_AUTHOR_DATE':'2026-01-01T00:00:00Z','GIT_COMMITTER_DATE':'2026-01-01T00:00:00Z'})
remote=out/'remote.git'; author=out/'publisher'; project=out/'project'
run(['git','init','--bare','--initial-branch=main',remote]); run(['git','clone',remote,author])
(author/'version.txt').write_text('v1 (old)\n'); git(author,'add','.'); git(author,'commit','-m','v1 (old)'); git(author,'push','origin','main')
run(['git','clone',remote,project]); git(project,'switch','-c','other')
v1=git(project,'rev-parse','main')
(author/'version.txt').write_text('v2 (new on remote)\n'); git(author,'commit','-am','v2 (new on remote)'); git(author,'push','origin','main'); v2=git(author,'rev-parse','HEAD')
emit('$ git log -1 --format="%h %s" main                  # project')
emit(git(project,'log','-1','--format=%h %s','main'))
emit('$ git log -1 --format="%h %s" origin/main           # project')
emit(git(project,'log','-1','--format=%h %s','origin/main'))
emit('$ git log -1 --format="%h %s" main                  # remote')
emit(git(remote,'log','-1','--format=%h %s','main'))
emit('Primary checkout: '+git(project,'branch','--show-current'))
s=socket.socket(); s.bind(('127.0.0.1',0)); port=s.getsockname()[1]; s.close()
serverlog=open(out/'server-private.log','w')
server=sp.Popen(['node','scripts/dev-runner.ts','dev:server','--home-dir',str(home),'--no-seed','--port',str(port),'--dev-url',f'http://127.0.0.1:{port+1}'],cwd=checkout,env=env,stdout=serverlog,stderr=sp.STDOUT)
results=[]
try:
    emit('\nStarting this commit\'s real dev server with T3CODE_DEV_SEED=0 ...')
    for i in range(240):
        if server.poll() is not None: raise RuntimeError('Server exited; inspect private startup log')
        try:
            urllib.request.urlopen(f'http://127.0.0.1:{port}/.well-known/t3/environment',timeout=1).read(); break
        except Exception: time.sleep(.5)
    else: raise RuntimeError('Server startup timed out')
    emit('Server ready. '+('settings.json has no start-from-origin override (default: ON).' if setting_on else 'settings.json sets newWorktreesStartFromOrigin=false (OFF).'))
    cli('project','add',str(project),'--title','Worktree origin proof')
    emit('$ t3 project add <synthetic-project> --title "Worktree origin proof"\nProject registered.\n')
    emit('$ t3 thread start <project> go --worktree main --provider devReplay --model showcase --json')
    emit('Sends workspaceStrategy: {"type":"worktree","baseRef":"main"}')
    launch=json.loads(cli('thread','start',str(project),'go','--worktree','main','--provider','devReplay','--model','showcase','--json'))
    emit('Launch accepted: threadId='+launch['threadId'])
    # Read the authoritative thread through the real CLI; provisioning is async.
    for i in range(120):
        thread=json.loads(cli('thread','show',launch['threadId'],'--messages','0','--json'))['thread']
        wt=thread.get('worktreePath')
        if wt and pathlib.Path(wt).exists(): break
        time.sleep(.25)
    else: raise RuntimeError('Worktree was not provisioned: '+json.dumps(thread))
    actual=git(wt,'rev-parse','HEAD'); tracking=git(project,'rev-parse','origin/main')
    emit('$ git -C <new-worktree> log -1 --format="%h %s"')
    emit(git(wt,'log','-1','--format=%h %s'))
    emit('$ git -C <new-worktree> show HEAD:version.txt')
    emit(git(wt,'show','HEAD:version.txt'))
    emit('$ git -C <project> log -1 --format="%h %s" origin/main')
    emit(git(project,'log','-1','--format=%h %s','origin/main'))
    emit('\nExpected: '+('worktree HEAD = remote v2; origin/main fetched to v2.' if setting_on else 'worktree HEAD = local v1; no fetch.'))
    correct=actual==(v2 if setting_on else v1) and tracking==(v2 if setting_on else v1)
    emit('Observed: '+('REMOTE v2, tracking ref updated' if actual==v2 and tracking==v2 else 'LOCAL v1, tracking ref stale')+(' -- correct' if correct else ' -- BUG REPRODUCED'))
    results.append({'case':'default-omitted','settingOn':setting_on,'actual':actual,'tracking':tracking,'expected':v2 if setting_on else v1,'threadId':launch['threadId']})
    snap('default-on-omitted' if setting_on else 'setting-off-omitted')
    # Explicit true is the original triage control; omit again after fetching.
    token=cli('auth','session','issue','--subject','mcp-client','--token-only')
    session=None; seq=0
    def rpc(method,params):
        global session,seq
        seq+=1
        headers={'Authorization':'Bearer '+token,'Content-Type':'application/json','Accept':'application/json, text/event-stream','MCP-Protocol-Version':'2025-06-18'}
        if session: headers['Mcp-Session-Id']=session
        req=urllib.request.Request(f'http://127.0.0.1:{port}/mcp',data=json.dumps({'jsonrpc':'2.0','id':seq,'method':method,'params':params}).encode(),headers=headers)
        response=urllib.request.urlopen(req,timeout=90)
        session=response.headers.get('Mcp-Session-Id') or session
        raw=response.read().decode()
        messages=[json.loads(line[5:].strip()) for line in raw.splitlines() if line.startswith('data:')]
        if not messages: messages=[json.loads(raw)]
        message=next(m for m in messages if m.get('id')==seq)
        if 'error' in message: raise RuntimeError(json.dumps(message['error']))
        return message['result']
    rpc('initialize',{'protocolVersion':'2025-06-18','capabilities':{},'clientInfo':{'name':'worktree-proof','version':'1'}})
    project_id=thread['projectId']
    def control(name,override,expected):
        emit('\033[2J\033[H'+'T3 Code | real server + MCP | '+side.upper()+' | '+name)
        emit('Commit: '+sha)
        emit('Environment: Linux, fresh isolated home, synthetic Git, Replay (dev) / showcase\n')
        strategy={'type':'worktree','baseRef':'main'}
        if override is not None: strategy['startFromOrigin']=override
        args={'projectId':project_id,'title':name,'message':'go','modelSelection':{'instanceId':'devReplay','model':'showcase'},'workspaceStrategy':strategy}
        emit('POST /mcp tools/call t3_thread_launch')
        emit('Effective default setting: '+('ON' if setting_on else 'OFF'))
        emit('workspaceStrategy: '+json.dumps(strategy))
        response=rpc('tools/call',{'name':'t3_thread_launch','arguments':args})
        if response.get('isError'): raise RuntimeError(json.dumps(response))
        launched=response['structuredContent']
        emit('Launch accepted: threadId='+launched['threadId'])
        for _ in range(60):
            t=json.loads(cli('thread','show',launched['threadId'],'--messages','0','--json'))['thread']
            path=t.get('worktreePath')
            if path and pathlib.Path(path).exists(): break
            time.sleep(.25)
        else: raise RuntimeError('Control worktree not provisioned')
        emit('\n$ git -C <new-worktree> log -1 --format="%h %s"')
        emit(git(path,'log','-1','--format=%h %s'))
        emit('$ git -C <new-worktree> show HEAD:version.txt')
        emit(git(path,'show','HEAD:version.txt'))
        emit('$ git -C <project> log -1 --format="%h %s" origin/main')
        emit(git(project,'log','-1','--format=%h %s','origin/main'))
        actual=git(path,'rev-parse','HEAD')
        emit('\nExpected: '+('remote v2' if expected==v2 else 'local v1'))
        emit('Observed: '+('remote v2' if actual==v2 else 'local v1')+(' -- correct' if actual==expected else ' -- BUG REPRODUCED'))
        results.append({'case':name,'actual':actual,'tracking':git(project,'rev-parse','origin/main'),'expected':expected})
        snap(name)
    control('explicit-true',True,v2)
    control('omitted-after-fetch',None,v2 if setting_on else v1)
    control('explicit-false',False,v1)
    (out/'results.json').write_text(json.dumps({'side':side,'commit':sha,'v1':v1,'v2':v2,'cases':results},indent=2))
except Exception as error:
    (out/'error-private.txt').write_text(repr(error))
    emit('Proof driver stopped; inspect local error log.')
    time.sleep(4)
    raise
finally:
    server.send_signal(signal.SIGINT)
    try: server.wait(timeout=20)
    except sp.TimeoutExpired: server.terminate(); server.wait(timeout=10)
    serverlog.close(); transcript.close()
