import subprocess, pathlib, time, json, sqlite3, os, re, signal, sys
import argparse
p=argparse.ArgumentParser()
p.add_argument('--base-checkout',type=pathlib.Path,required=True)
p.add_argument('--head-checkout',type=pathlib.Path,required=True)
p.add_argument('--output-root',type=pathlib.Path,required=True)
a=p.parse_args()
ROOT=a.output_root
ROOT.mkdir(parents=True,exist_ok=True)
RUN=ROOT/('run-'+time.strftime('%Y%m%d-%H%M%S')); RUN.mkdir()
H=a.head_checkout
B=a.base_checkout
results={}
for side,tree,port in [('before',B,18471),('after',H,18472)]:
    lines=[]; events=[]; start=time.monotonic()
    def say(s):
        lines.extend(s.splitlines()); events.append([round(time.monotonic()-start,3),s]); print(s,flush=True)
    env={**os.environ,'T3CODE_DEV_SEED':'0','NO_COLOR':'1'}
    home=RUN/side; home.mkdir(); db=home/'userdata/statev2.sqlite'
    manifest=tree/'apps/server/src/persistence/Migrations.ts'; original=manifest.read_text()
    def cli(args):
        p=subprocess.run(['node','apps/server/src/bin.ts',*args,'--base-dir',str(home)],cwd=tree,env=env,text=True,stdout=subprocess.PIPE,stderr=subprocess.STDOUT,timeout=60)
        return p.returncode,p.stdout
    def snapshot():
        with sqlite3.connect('file:'+str(db)+'?mode=ro',uri=True) as c:
            ledger=c.execute('select migration_id,name from effect_sql_migrations order by migration_id').fetchall()
            schema=c.execute("select type,name,sql from sqlite_master where name not like 'sqlite_%' order by type,name").fetchall()
        return {'ledger':ledger,'schema':schema}
    def reportcmd(args):
        say('$ t3 '+' '.join(args)+' --base-dir <disposable-home>')
        code,out=cli(args)
        if code==0: say('exit 0; command succeeded (credential output withheld)')
        else:
            clean=re.sub(r'\x1b\[[0-9;]*m','',out).replace(str(tree),'<checkout>').replace(str(home),'<disposable-home>')
            say('exit '+str(code)+'\n'+clean.strip())
        return code,out
    log=open(home/'server-private.log','w')
    server=None
    try:
        say('T3 Code CLI / server evidence — '+side.upper())
        say('Commit: '+subprocess.check_output(['git','rev-parse','HEAD'],cwd=tree,text=True).strip())
        say('Environment: Linux; real Node CLI and HTTP server; fresh synthetic T3 home.')
        say('Starting server with migration manifest through ID 80.')
        server=subprocess.Popen(['node','apps/server/src/bin.ts','serve','--host','127.0.0.1','--port',str(port),'--base-dir',str(home)],cwd=tree,env=env,stdout=log,stderr=log)
        import socket
        for _ in range(120):
            if server.poll() is not None: raise RuntimeError('server stopped: '+(home/'server-private.log').read_text()[-4000:])
            try:
                socket.create_connection(('127.0.0.1',port),timeout=1).close()
                break
            except Exception: time.sleep(.5)
        else: raise RuntimeError('server not ready')
        say('Server ready; ownership held by live process.')
        baseline=snapshot(); say('Initial ledger: '+str(len(baseline['ledger']))+' rows; maximum migration ID 80.')
        say('CONTROL: matching server / CLI manifests')
        control1=reportcmd(['auth','session','issue','--json'])[0]
        control2=reportcmd(['pair','--ttl','5m'])[0]
        say('CLI-only fixture: append migration 81_CliEvidenceProbe; CREATE TABLE cli_evidence_probe(value TEXT).')
        patched=original.replace('const migrationEntries = [','const migrationEntries = [\n  [81, "CliEvidenceProbe", Effect.gen(function* () { const sql = yield* SqlClient.SqlClient; yield* sql`CREATE TABLE cli_evidence_probe(value TEXT)`; })],')
        manifest.write_text(patched)
        pending=[]
        for args in [['auth','session','issue','--json'],['pair','--ttl','5m'],['environment','list']]:
            before=snapshot(); code,out=reportcmd(args); after=snapshot()
            same=before==after
            say('Schema + complete migration ledger unchanged: '+str(same).lower())
            say('Ledger maximum ID: '+str(after['ledger'][-1][0])+'; probe table present: '+str(any(row[1]=='cli_evidence_probe' for row in after['schema'])).lower())
            pending.append({'code':code,'ownsError':'ServerOwnsDatabaseError' in out,'unchanged':same})
        final=snapshot(); (RUN/(side+'-ledger.json')).write_text(json.dumps({'before':baseline,'after':final},indent=2))
        say('Original server remains running: '+str(server.poll() is None).lower())
        say('Stop original server. Start a build with conflicting ID 81_ServerEvidenceProbe.')
        server.terminate(); server.wait(timeout=20); server=None
        manifest.write_text(patched.replace('CliEvidenceProbe','ServerEvidenceProbe').replace('cli_evidence_probe','server_evidence_probe'))
        try:
            p=subprocess.Popen(['node','apps/server/src/bin.ts','serve','--host','127.0.0.1','--port',str(port),'--base-dir',str(home)],cwd=tree,env=env,stdout=subprocess.PIPE,stderr=subprocess.STDOUT,text=True)
            server=p
            try:
                out=p.communicate(timeout=12)[0]; code=p.returncode
                say('Conflicting server exit: '+str(code))
                for line in re.sub(r'\x1b\[[0-9;]*m','',out).splitlines():
                    if 'migration history would skip' in line: say(line.replace(str(home),'<disposable-home>'))
            except subprocess.TimeoutExpired:
                say('Conflicting build starts successfully; migration 81_ServerEvidenceProbe recorded.')
                code=None
                say('Ledger tail: '+str(snapshot()['ledger'][-1]))
        finally:
            if server and server.poll() is None: server.terminate(); server.communicate(timeout=20)
            server=None
        results[side]={'control':[control1,control2],'pending':pending,'conflictingStartupExit':code,'ledgerPreserved':baseline==final}
    finally:
        manifest.write_text(original)
        if server and server.poll() is None: server.terminate(); server.wait(timeout=20)
        log.close()
        (RUN/(side+'.txt')).write_text('\n'.join(lines)+'\n')
        (RUN/(side+'-events.json')).write_text(json.dumps(events))
(ROOT/'latest.txt').write_text(str(RUN))
(RUN/'results.json').write_text(json.dumps(results,indent=2))
print(json.dumps(results,indent=2))
