import json,time,os,traceback
from http.server import HTTPServer,BaseHTTPRequestHandler
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import Select,WebDriverWait
from selenium.webdriver.common.keys import Keys
os.makedirs('/tmp/results',exist_ok=True)
d=None; trial=None; seq=0; started=0; active=0; errors=0
def snapshot():
 global seq
 seq+=1
 elements=d.find_elements(By.CSS_SELECTOR,'a,button,input,select,textarea')
 visible=[e for e in elements if e.is_displayed()]
 globals()['refs']=visible
 info=[]
 for i,e in enumerate(visible):
  info.append(dict(ref=i,tag=e.tag_name,text=e.text[:160],name=e.get_attribute('name'),id=e.get_attribute('id'),type=e.get_attribute('type'),value=e.get_attribute('value'),placeholder=e.get_attribute('placeholder'),href=e.get_attribute('href')))
 path=f'/tmp/results/{trial}-{seq:02}.png'; d.save_screenshot(path)
 return dict(url=d.current_url,title=d.title,text=d.find_element(By.TAG_NAME,'body').text[:14000],elements=info,screenshot=path)
def run(q):
 global d,trial,seq,started,active,errors
 op=q['op']
 if op=='start':
  if d:d.quit()
  trial=q['trial'];seq=0;active=0;errors=0
  b=q['browser'];t=time.monotonic()
  if b=='firefox':
   o=webdriver.FirefoxOptions();o.binary_location='/tmp/firefox/firefox';o.add_argument('-headless');d=webdriver.Firefox(options=o)
  else:
   o=webdriver.ChromeOptions();o.binary_location='/usr/bin/google-chrome' if b=='chrome' else '/usr/bin/brave-browser'
   o.add_argument('--headless=new');o.add_argument('--disable-dev-shm-usage');o.add_argument('--no-first-run');d=webdriver.Chrome(options=o)
  d.set_page_load_timeout(35);d.set_window_size(1280,1000)
  size=d.execute_script('return [window.outerWidth-window.innerWidth,window.outerHeight-window.innerHeight]')
  d.set_window_size(1280+size[0],900+size[1])
  return dict(browser=b,version=d.capabilities['browserVersion'],startup_seconds=time.monotonic()-t,viewport=d.execute_script('return [innerWidth,innerHeight]'))
 if op=='begin':
  started=time.monotonic();t=time.monotonic();d.get(q['url']);active+=time.monotonic()-t;return snapshot()
 if op=='actions':
  for a in q['actions']:
   t=time.monotonic();e=refs[a['ref']];kind=a['kind']
   if kind=='click':e.click()
   elif kind=='fill':e.clear();e.send_keys(a['text'])
   elif kind=='select':Select(e).select_by_visible_text(a['text'])
   elif kind=='enter':e.send_keys(Keys.ENTER)
   active+=time.monotonic()-t
  return snapshot()
 if op=='observe':return snapshot()
 if op=='finish':
  elapsed=time.monotonic()-started
  s=snapshot();result=dict(trial=trial,completed=q['completed'],finding=q.get('finding'),elapsed_seconds=elapsed,webdriver_action_seconds=active,errors=errors,final=s)
  with open('/tmp/results/summary.jsonl','a') as f:f.write(json.dumps(result)+'\n')
  d.quit();d=None;return result
 if op=='quit':
  if d:d.quit()
  return {'stopped':True}
class Handler(BaseHTTPRequestHandler):
 def do_POST(self):
  global errors
  q=json.loads(self.rfile.read(int(self.headers['Content-Length'])))
  try:r=run(q)
  except Exception as e:
   errors+=1;r={'error':str(e),'trace':traceback.format_exc()[-2500:]}
   try:r['state']=snapshot()
   except:pass
  with open('/tmp/results/trace.jsonl','a') as f:f.write(json.dumps({'time':time.time(),'request':q,'response':r})+'\n')
  self.send_response(200);self.end_headers();self.wfile.write(json.dumps(r).encode())
HTTPServer(('127.0.0.1',8765),Handler).serve_forever()
