import os,sys,json,time,datetime,subprocess,pathlib,re,urllib.request,urllib.error,websocket,hashlib,signal,uuid
BASE=pathlib.Path(os.environ.get('EVIDENCE_CAPTURE_DIR',str(pathlib.Path(__file__).resolve().parent)))
def stamp():return datetime.datetime.now(datetime.timezone.utc).isoformat()
def log(kind,**kw):
 print(json.dumps(dict(at=stamp(),kind=kind,**kw)),flush=True)
class Client:
 def __init__(self,run):
  self.run=pathlib.Path(run);self.port=json.loads((self.run/'environment.json').read_text()).get('port',19683);self.origin='http://127.0.0.1:'+str(self.port);self.n=0
  p=self.run/'cookie.private'
  if p.exists():self.cookie=p.read_text()
  else:
   token=re.findall(r'pairingUrl: .*token=([^\s]+)',(self.run/'server.log').read_text())[-1]
   q=urllib.request.Request(self.origin+'/api/auth/browser-session',data=json.dumps({'credential':token}).encode(),headers={'content-type':'application/json'},method='POST')
   with urllib.request.urlopen(q) as r:self.cookie=r.headers['Set-Cookie'].split(';')[0]
   p.write_text(self.cookie);p.chmod(0o600)
  t=self.http('/api/auth/websocket-ticket',{})['ticket']
  self.ws=websocket.create_connection('ws://127.0.0.1:'+str(self.port)+'/ws?orchestrationProtocol=2&wsTicket='+t,timeout=120)
 def http(self,path,data=None):
  q=urllib.request.Request(self.origin+path,data=None if data is None else json.dumps(data).encode(),headers={'Cookie':self.cookie,'content-type':'application/json','x-t3-orchestration-protocol':'2'})
  try:
   with urllib.request.urlopen(q) as r:return json.load(r)
  except urllib.error.HTTPError as e:raise RuntimeError(e.read().decode())
 def rpc(self,tag,payload):
  self.n+=1;i=str(self.n); self.ws.send(json.dumps({'_tag':'Request','id':i,'tag':tag,'payload':payload,'headers':[]})+'\n')
  while True:
   raw=self.ws.recv()
   for line in raw.splitlines():
    m=json.loads(line)
    if str(m.get('requestId'))==i:
     if m['_tag']=='Exit':
      e=m['exit']
      if e['_tag']!='Success':raise RuntimeError(json.dumps(m))
      return e.get('value')
     if m['_tag']=='Chunk':
      self.ws.send(json.dumps({'_tag':'Ack','requestId':i})+'\n');return m['values']
 def command(self,type,**kw):
  c=dict(type=type,commandId=str(uuid.uuid4()),**({'createdBy':'user','creationSource':'web'} if type in ['thread.create','thread.fork'] else {}),**kw);r=self.rpc('orchestration.dispatchCommand',c);log('command',command=c,result=r);return r
 def activity(self):return self.rpc('server.reportClientActivity',dict(clientId='evidence-driver',clientKind='web',visible=True,focused=True,recentlyInteracted=True,appState='active',scopes=[{'type':'server-config'}],ttlMs=45000,observedAt=stamp()))
 def close(self):self.ws.close()
def start(side,label,port='19683'):
 run=BASE/(datetime.datetime.now().strftime('%Y%m%dT%H%M%S')+'-'+side+'-'+label);run.mkdir()
 checkout=os.environ.get('EVIDENCE_BEFORE_CHECKOUT' if side=='before' else 'EVIDENCE_AFTER_CHECKOUT','/home/ubuntu/wt/pr-26983'+('-before' if side=='before' else ''))
 env=dict(os.environ,T3CODE_DEV_SEED='0',T3CODE_AUTO_BOOTSTRAP_PROJECT_FROM_CWD='0',T3CODE_TRACE_MAX_BYTES='104857600',NODE_OPTIONS='--import='+str(BASE/'observer.mjs'))
 out=open(run/'server.log','w');p=subprocess.Popen(['vp','run','dev:server','--home-dir',str(run/'home'),'--port',port,'--host','127.0.0.1'],cwd=checkout,env=env,stdout=out,stderr=subprocess.STDOUT,start_new_session=True)
 meta=dict(side=side,label=label,spawnPid=p.pid,checkout=checkout,sha=subprocess.check_output(['git','rev-parse','HEAD'],cwd=checkout,text=True).strip(),status=subprocess.check_output(['git','status','--short'],cwd=checkout,text=True),home=str(run/'home'),port=int(port),observerSha256=hashlib.sha256((BASE/'observer.mjs').read_bytes()).hexdigest())
 (run/'environment.json').write_text(json.dumps(meta,indent=2));print(run,flush=True)
 for _ in range(120):
  if 'Listening on' in (run/'server.log').read_text():return
  if p.poll()!=None:raise RuntimeError('server exited')
  time.sleep(1)
 raise RuntimeError('server timeout')
def stop(run):
 run=pathlib.Path(run);m=json.loads((run/'environment.json').read_text());os.killpg(m['spawnPid'],signal.SIGTERM);log('stop',spawnPid=m['spawnPid'])
if __name__=='__main__':
 action=sys.argv[1]
 if action=='start':start(*sys.argv[2:])
 elif action=='stop':stop(sys.argv[2])
 elif action=='probe':
  c=Client(sys.argv[2]);print(c.http('/api/projects'));print(c.rpc('orchestration.getArchivedShellSnapshot',{}));print(c.activity());c.close()
 elif action=='hold':
  c=Client(sys.argv[2]);end=time.monotonic()+float(sys.argv[3]);log('foreground-start')
  while time.monotonic()<end:
   log('activity',result=c.activity());time.sleep(min(15,max(0,end-time.monotonic())))
  c.close();log('foreground-stop')
