import os,sys,subprocess,time,signal,re,json,urllib.request,pathlib
out=pathlib.Path(sys.argv[3]); out.mkdir(parents=True,exist_ok=True)
for side,cwd in [('before',sys.argv[1]),('after',sys.argv[2])]:
    lines=[f'{side.upper()} — real dev server startup', 'Linux; fresh worktree database; T3CODE_DEV_SEED=0', 'Existing listener: 0.0.0.0:3773', '']
    def clean(s):
        s=re.sub(r'\x1b\[[0-9;]*m','',s)
        s=s.replace(cwd,'<checkout>').replace(os.path.expanduser('~'),'<home>')
        s=re.sub(r'https?://\S*(?:token|pair)\S*','<redacted pairing URL>',s)
        return s
    env=os.environ.copy()
    for k in ['T3CODE_PORT','T3CODE_HOST','T3CODE_LAN','T3CODE_PORT_OFFSET','T3CODE_DEV_INSTANCE']:
        env.pop(k,None)
    env['T3CODE_DEV_SEED']='0'
    cases=[('clean dry-run',{},['dev:server','--dry-run']),('inherited dry-run',{'T3CODE_PORT':'3773','T3CODE_HOST':'0.0.0.0','T3CODE_LAN':'true'},['dev','--dry-run']),('offset zero + inherited',{'T3CODE_PORT':'3773','T3CODE_HOST':'0.0.0.0','T3CODE_LAN':'true','T3CODE_PORT_OFFSET':'0'},['dev:server','--dry-run'])]
    for label,extra,args in cases:
        r=subprocess.run(['node','scripts/dev-runner.ts']+args,cwd=cwd,env=env|extra,stdout=subprocess.PIPE,stderr=subprocess.STDOUT,text=True)
        lines += ['$ '+label,clean(r.stdout).strip(),f'Exit: {r.returncode}','']
    lines+=['$ T3CODE_DEV_SEED=0 T3CODE_PORT=3773 T3CODE_HOST=0.0.0.0 T3CODE_LAN=true node scripts/dev-runner.ts dev:server']
    raw=out/(side+'-raw.log')
    with raw.open('w') as f:
        p=subprocess.Popen(['node','scripts/dev-runner.ts','dev:server'],cwd=cwd,env=env|{'T3CODE_PORT':'3773','T3CODE_HOST':'0.0.0.0','T3CODE_LAN':'true'},stdout=f,stderr=subprocess.STDOUT,start_new_session=True)
    start=time.monotonic(); verified=False; events=[]; previous='' 
    try:
        while time.monotonic()-start<150:
            text=raw.read_text()
            visible='\n'.join(l for l in clean(text).splitlines() if any(w in l for w in ['[dev-runner]','EADDRINUSE','address already']) and 'pair' not in l.lower())
            if visible!=previous:
                events.append([round(time.monotonic()-start,2),visible]); previous=visible
            port=re.search(r'serverPort=(\d+)',text)
            if 'EADDRINUSE' in text: break
            if port and int(port[1])!=3773:
                n=int(port[1])
                ss=subprocess.run(['ss','-H','-ltn',f'sport = :{n}'],capture_output=True,text=True).stdout
                if '127.0.0.1:'+str(n) in ss:
                    try:
                        response=urllib.request.urlopen(f'http://127.0.0.1:{n}/api/health',timeout=2)
                        body=response.read().decode(); status=response.status
                    except urllib.error.HTTPError as e:
                        status=e.code; body=e.read().decode()
                    if status == 503:
                        time.sleep(.5); continue
                    lines += [f'Listener observed: {ss.strip()}',f'HTTP /api/health: {status} {body[:150]}']; verified=True; break
            if p.poll() is not None: break
            time.sleep(.5)
    finally:
        os.killpg(p.pid,signal.SIGTERM)
        try:p.wait(timeout=10)
        except subprocess.TimeoutExpired:os.killpg(p.pid,signal.SIGKILL);p.wait()
    (out/(side+'-timeline.json')).write_text(json.dumps(events))
    log=clean(raw.read_text())
    # Keep the runner selection and the actual server startup/error, omit unrelated warm-up chatter.
    selected=[l for l in log.splitlines() if any(w in l for w in ['[dev-runner]','EADDRINUSE','address already','Listening','listening','Server running','http://127.0.0.1','hostname:','port: 3773']) and 'pair' not in l.lower()]
    lines+=selected
    lines += [f'Real server independently verified on loopback: {verified}']
    (out/(side+'.txt')).write_text('\n'.join(lines)+'\n')
    print('\n'.join(lines),flush=True)
