import os,subprocess,time,socket,json,urllib.request,signal,pathlib
R=pathlib.Path(__file__).resolve().parent
repo=pathlib.Path(os.environ.get('CAPTURE_REPO','/home/ubuntu/wt/pr-18816'))
f=json.loads((R/'fixtures.json').read_text())
assert subprocess.check_output(['git','-C',str(repo),'rev-parse','HEAD'],text=True).strip()==f['head']
assert not subprocess.check_output(['git','-C',str(repo),'status','--porcelain'],text=True).strip()
base=R/'before'
if not base.exists():
 base.mkdir(); subprocess.run(['git','-C',str(repo),'archive',f['base'],'-o',str(R/'before.tar')],check=True);subprocess.run(['tar','-xf',str(R/'before.tar'),'-C',str(base)],check=True)
for rel in ['node_modules','apps/marketing/node_modules']:
 dest=base/rel
 if not dest.exists(): dest.symlink_to(repo/rel,target_is_directory=True)
procs=[]
def spawn(args,cwd,env,name):
 log=open(R/(name+'.log'),'w');p=subprocess.Popen(args,cwd=cwd,env=env,stdout=log,stderr=subprocess.STDOUT,start_new_session=True);procs.append(p);return p
try:
 # Xvfb -displayfd chooses an unused display, no global process discovery.
 rd,wr=os.pipe(); xvlog=open(R/'xvfb.log','w');xv=subprocess.Popen(['Xvfb','-displayfd',str(wr),'-screen','0','1600x1200x24','-nolisten','tcp'],pass_fds=(wr,),stdout=xvlog,stderr=xvlog,start_new_session=True);procs.append(xv);os.close(wr);display=':'+os.read(rd,100).decode().strip();os.close(rd)
 env=dict(os.environ,DISPLAY=display,ASTRO_DEV_BACKGROUND="1")
 prefs=R/'xdg-config'/'astro';prefs.mkdir(parents=True,exist_ok=True)
 (prefs/'settings.json').write_text(json.dumps({'devToolbar':{'enabled':False},'checkUpdates':{'enabled':False}}))
 env['XDG_CONFIG_HOME']=str(R/'xdg-config')
 ports=[]
 for side,root in [('before',base),('after',repo)]:
  s=socket.socket();s.bind(('127.0.0.1',0));port=s.getsockname()[1];s.close();ports.append(port)
  cwd=root/'apps/marketing';subprocess.run(['node','scripts/stage-install-scripts.mjs'],cwd=cwd,check=True)
  astro=cwd/'node_modules/astro/bin/astro.mjs'
  server=spawn(['node',str(astro),'dev','--host','127.0.0.1','--port',str(port)],cwd,env,side+'-server')
  for i in range(120):
   if server.poll() is not None: raise RuntimeError('Server exited '+side)
   try:
    urllib.request.urlopen(f'http://127.0.0.1:{port}/',timeout=2).read();break
   except Exception: time.sleep(.5)
  else: raise RuntimeError('Server not ready '+side)
 env.update(BEFORE_ORIGIN=f'http://127.0.0.1:{ports[0]}',AFTER_ORIGIN=f'http://127.0.0.1:{ports[1]}')
 subprocess.run(['node',str(R/'capture.mjs')],cwd=R,env=env,check=True)
finally:
 for p in reversed(procs):
  if p.poll() is None:
   os.killpg(p.pid,signal.SIGTERM)
   try:p.wait(timeout=8)
   except subprocess.TimeoutExpired:os.killpg(p.pid,signal.SIGKILL);p.wait()
 (R/'cleanup.json').write_text(json.dumps([{'pid':p.pid,'returncode':p.poll()} for p in procs],indent=2))
