import os,sys,time,json,subprocess,pathlib,re,signal,datetime
root=pathlib.Path(__file__).resolve().parent
run=root/datetime.datetime.now().strftime('run-%Y%m%d-%H%M%S');run.mkdir()
checkouts=sys.argv[1:]
for side,checkout in zip(['before','after'],checkouts):
    folder=run/side;folder.mkdir();home=folder/'home';project=folder/'gone';project.mkdir()
    env=dict(os.environ,T3CODE_DEV_SEED='0')
    cli=['node', 'apps/server/src/bin.ts']
    transcript=[]; events=[]; recording_start=time.monotonic()
    def say(s):
        print(s,flush=True);transcript.append(s);events.append([round(time.monotonic()-recording_start,3), "o", s+"\r\n"])
    def command(args):
        p=subprocess.run(cli+args+['--base-dir',str(home)],cwd=checkout,env=env,text=True,capture_output=True)
        if p.returncode: raise RuntimeError(p.stderr)
        return p.stdout.strip()
    def start():
        log=open(folder/'server.log','a')
        p=subprocess.Popen(cli+['serve','--host','127.0.0.1','--port','47911','--base-dir',str(home)],cwd=checkout,env=env,stdout=log,stderr=log)
        for i in range(120):
            if p.poll() is not None:raise RuntimeError('server exited')
            if (home/'userdata/server-runtime.json').exists():
                time.sleep(2);return p
            time.sleep(.25)
        raise RuntimeError('server did not start')
    def stop(p):
        p.terminate()
        try:p.wait(timeout=15)
        except subprocess.TimeoutExpired:p.kill();p.wait()
    sha=subprocess.check_output(['git','rev-parse','HEAD'],cwd=checkout,text=True).strip()
    say(f'{side.upper()} | commit {sha}')
    say('Real T3 server + CLI; fresh isolated home; seeding disabled; loopback port 47911.')
    server=start()
    try:
        result=command(['project','add','--title','gone',str(project)])
        project_id=re.search(r'Added project ([^ ]+)',result)[1]
        say('$ t3 project add --title gone <temporary-folder>\nAdded synthetic project gone.')
    finally:stop(server)
    project.rmdir()
    say('Stopped server; deleted the empty project folder; restarted same home.')
    trace=home/'userdata/logs/server.trace.ndjson'
    offset=trace.stat().st_size
    server=start();started=time.monotonic()
    def spans():
        with trace.open() as f:
            f.seek(offset)
            rows=[json.loads(l) for l in f if l.strip()]
        return [r for r in rows if r.get('name')=='ProjectFaviconResolver.resolvePathUncached' and r.get('exit',{}).get('_tag')=='Failure']
    try:
        samples=[]
        for number,target in enumerate([0,7,14,21,28,56]):
            time.sleep(max(0,started+target-time.monotonic()))
            if number:
                out=command(['thread','list','--project',project_id])
                say(f'+{target:02}s $ t3 thread list --project <gone>\n{out}')
                time.sleep(.7)
            count=len(spans());samples.append(count)
            say(f'+{target:02}s failed resolvePathUncached spans since restart: {count}')
        selected=spans()
        clean=[]
        for r in selected:
            text=json.dumps(r).replace(str(project),'<deleted-project-folder>').replace(checkout,'<checkout>')
            text=text.replace(str(home),'<isolated-home>')
            clean.append(json.loads(text))
        (folder/'favicon-spans.json').write_text(json.dumps(clean,indent=2))
        say('Counts: '+ ' -> '.join(map(str,samples)))
        say('All counted failures are real resolver spans; inspect attached span export for normalize-workspace cause.')
        (folder/'terminal.cast').write_text(json.dumps(dict(version=2,width=110,height=40,timestamp=int(time.time()),title='Missing-project favicon cache: '+side))+'\n'+'\n'.join(json.dumps(e) for e in events)+'\n')
        (folder/'transcript.txt').write_text('\n'.join(transcript)+'\n')
        (folder/'summary.json').write_text(json.dumps(dict(side=side,sha=sha,counts=samples),indent=2))
    finally:stop(server)
print('Evidence directory:',run)
