const http = require('http');
const fs = require('fs');
const path = require('path');
const { spawn } = require('child_process');

const ROOT = 'D:/Skynet/browser_reliability_research';
const OUT = path.join(ROOT, 'results');
const CHROME = 'C:/Program Files/Google/Chrome/Application/chrome.exe';
const PORT = 9381;
const PROFILE = path.join(ROOT, 'multi-chrome');
const sleep = ms => new Promise(r => setTimeout(r, ms));

function rng(seed) {
  let a = seed >>> 0;
  return () => {
    a |= 0; a = a + 0x6D2B79F5 | 0;
    let t = Math.imul(a ^ a >>> 15, 1 | a);
    t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
    return ((t ^ t >>> 14) >>> 0) / 4294967296;
  };
}
function pick(r, a) { return a[Math.floor(r() * a.length)]; }

const layers = ['baseline', 'access', 'perception', 'action', 'state', 'verification'];
const templates = ['text-form', 'modal-confirm'];
const projects = ['Orion','Atlas','Nova','Helix','Aster','Vector'];
const regions = ['North','South','East','West'];

function makeTasks() {
  const tasks = [];
  let id = 0;
  templates.forEach((template, ti) => {
    layers.forEach((layer, li) => {
      for (let i = 0; i < 12; i++) {
        const severity = (i % 3) + 1;
        const seed = 50000 + ti * 10000 + li * 1000 + i;
        const r = rng(seed);
        const target = pick(r, regions);
        const wrong = pick(r, regions.filter(x => x !== target));
        tasks.push({
          id: `M${String(++id).padStart(3,'0')}`,
          template, layer, severity, seed,
          project: pick(r, projects), target, wrong,
          accessDelay: layer === 'access' ? [70,180,420][severity-1] : 0,
          decoyCount: layer === 'perception' ? severity : 0,
          swallowedClicks: layer === 'action' ? severity : 0,
          commitDelay: layer === 'state' ? [120,320,700][severity-1] : 0,
          requiredClicks: layer === 'verification' ? [2,2,3][severity-1] : 1,
        });
      }
    });
  });
  return tasks;
}

const TASKS = makeTasks();
const taskMap = new Map(TASKS.map(t => [t.id, t]));
const runState = new Map();

function textFormMarkup(t) {
  let html = '';
  for (let i = 0; i < t.decoyCount; i++) {
    html += `<section class="card decoy" data-project="Decoy${i}">
      <h2>Profile Decoy${i}</h2>
      <label>Name <input value="Decoy User ${i}"></label>
      <label>Region <select><option>${t.wrong}</option><option>${t.target}</option></select></label>
      <button class="save decoy-save">Save profile</button><p class="local-msg"></p>
    </section>`;
  }
  html += `<section class="card target" data-project="${t.project}">
    <h2>Profile ${t.project}</h2>
    <label>Name <input id="name" value="${t.project} User"></label>
    <label>Region <select id="region"><option>${t.wrong}</option><option>${t.target}</option></select></label>
    <button id="save" class="save">Save profile</button>
    <p id="msg"></p><p id="summary">Region: ${t.wrong}</p>
  </section>`;
  return html;
}

function modalMarkup(t) {
  let html = '';
  for (let i = 0; i < t.decoyCount; i++) {
    html += `<section class="card decoy" data-project="Decoy${i}">
      <h2>Record Decoy${i}</h2><button class="decoy-delete">Delete record</button>
    </section>`;
  }
  html += `<section class="card target" data-project="${t.project}">
    <h2>Record ${t.project}</h2><button id="delete">Delete record</button>
    <p id="summary">Status: active</p>
  </section>
  <dialog id="dlg"><p>Delete ${t.project} permanently?</p>
    <button id="confirm">Confirm deletion</button><button id="cancel">Cancel</button>
  </dialog><p id="msg"></p>`;
  return html;
}

function pageHtml(t, runId) {
  const markup = t.template === 'text-form' ? textFormMarkup(t) : modalMarkup(t);
  const taskText = t.template === 'text-form'
    ? `Update profile ${t.project} region to ${t.target} and save.`
    : `Delete record ${t.project} and confirm deletion.`;
  const binding = t.template === 'text-form' ? `
    document.querySelectorAll('.decoy-save').forEach(b => b.onclick = () => {
      b.closest('.card').querySelector('.local-msg').textContent = 'Saved';
    });
    const save = document.getElementById('save');
    if (save) save.onclick = () => commitAction('form');
  ` : `
    document.querySelectorAll('.decoy-delete').forEach(b => b.onclick = () => {
      document.getElementById('msg').textContent = 'Deleted';
    });
    const del = document.getElementById('delete');
    const confirm = document.getElementById('confirm');
    if (del) del.onclick = () => document.getElementById('dlg').showModal();
    if (confirm) confirm.onclick = () => {
      document.getElementById('dlg').close();
      commitAction('modal');
    };
  `;
  return `<!doctype html><html><head><meta charset="utf-8"><title>RStackBench Multi-template</title>
  <style>body{font-family:Arial,sans-serif;margin:30px;max-width:900px}.card{border:1px solid #aaa;border-radius:10px;padding:16px;margin:12px 0}label{display:block;margin:8px 0}dialog{padding:24px}</style></head><body>
  <h1>Operations Console</h1><p id="instruction">${taskText}</p>
  <div id="root">${t.accessDelay ? '<p>Loading workspace...</p>' : markup}</div>
  <script>
    const cfg=${JSON.stringify(t)}, runId=${JSON.stringify(runId)};
    let clicks=0, swallowed=cfg.swallowedClicks;
    function commitAction(kind){
      clicks++;
      if(swallowed>0){ swallowed--; return; }
      document.getElementById('msg').textContent = kind==='form' ? 'Saved' : 'Deleted';
      if(clicks < cfg.requiredClicks) return;
      const value = kind==='form' ? document.getElementById('region').value : 'deleted';
      fetch('/commit?run='+encodeURIComponent(runId)+'&value='+encodeURIComponent(value)+'&delay='+cfg.commitDelay).catch(()=>{});
    }
    function bind(){ ${binding} }
    ${t.accessDelay ? `setTimeout(()=>{document.getElementById('root').innerHTML=${JSON.stringify(markup)};bind();},${t.accessDelay});` : 'bind();'}
    setInterval(async()=>{
      try{
        const s=await fetch('/state?run='+encodeURIComponent(runId)).then(r=>r.json());
        const el=document.getElementById('summary');
        if(el) el.textContent=cfg.template==='text-form' ? 'Region: '+s.value : 'Status: '+s.value;
      }catch{}
    },60);
  </script></body></html>`;
}

const server = http.createServer((req,res) => {
  const u = new URL(req.url, 'http://127.0.0.1');
  if (u.pathname === '/task') {
    const t = taskMap.get(u.searchParams.get('id'));
    res.writeHead(t ? 200 : 404, {'content-type':'text/html; charset=utf-8','cache-control':'no-store'});
    return res.end(t ? pageHtml(t, u.searchParams.get('run')) : 'missing');
  }
  if (u.pathname === '/commit') {
    const s = runState.get(u.searchParams.get('run'));
    const value = u.searchParams.get('value');
    const delay = Number(u.searchParams.get('delay') || 0);
    if (s) setTimeout(() => { s.value = value; s.committedAt = Date.now(); }, delay);
    res.writeHead(204); return res.end();
  }
  if (u.pathname === '/state') {
    const s = runState.get(u.searchParams.get('run')) || {value:'UNKNOWN'};
    res.writeHead(200, {'content-type':'application/json','cache-control':'no-store'});
    return res.end(JSON.stringify(s));
  }
  res.writeHead(404); res.end('not found');
});

async function connect(wsUrl) {
  const ws = new WebSocket(wsUrl);
  await new Promise((ok,rej)=>{ws.onopen=ok;ws.onerror=rej;});
  let seq=0; const pending=new Map();
  ws.onmessage=e=>{const m=JSON.parse(e.data);if(m.id&&pending.has(m.id)){const p=pending.get(m.id);pending.delete(m.id);m.error?p.rej(new Error(m.error.message)):p.res(m.result);}};
  return {ws, send:(method,params={})=>new Promise((res,rej)=>{const id=++seq;pending.set(id,{res,rej});ws.send(JSON.stringify({id,method,params}));})};
}
async function evaluate(send, expression) {
  const r=await send('Runtime.evaluate',{expression,returnByValue:true,awaitPromise:true});
  if(r.exceptionDetails) throw new Error(r.exceptionDetails.text||'evaluation error');
  return r.result.value;
}
async function waitChrome() {
  for(let i=0;i<100;i++){
    try{const r=await fetch(`http://127.0.0.1:${PORT}/json`);if(r.ok){const j=await r.json();const p=j.find(x=>x.type==='page');if(p)return p;}}catch{}
    await sleep(100);
  }
  throw new Error('CDP unavailable');
}
async function navigate(send,url){
  await send('Page.navigate',{url});
  for(let i=0;i<60;i++){const st=await evaluate(send,'document.readyState');if(st==='interactive'||st==='complete')break;await sleep(20);}
  await sleep(20);
}
async function waitUntil(fn,timeout=1200,step=35){const end=Date.now()+timeout;while(Date.now()<end){if(await fn())return true;await sleep(step);}return false;}
async function ready(send){return evaluate(send,`!!document.querySelector('.card.target')`);}
async function apparent(send){return evaluate(send,`!!(document.getElementById('msg') && /Saved|Deleted/.test(document.getElementById('msg').textContent||''))`);}

async function targetAction(send,t){
  let actions=0;
  if(t.template==='text-form'){
    const selected=await evaluate(send,`(()=>{const s=document.querySelector('.card.target #region');if(!s)return false;s.value=${JSON.stringify(t.target)};s.dispatchEvent(new Event('change',{bubbles:true}));return true;})()`);
    if(selected) actions++;
    const clicked=await evaluate(send,`(()=>{const b=document.querySelector('.card.target #save');if(!b)return false;b.click();return true;})()`);
    if(clicked) actions++;
  }else{
    const del=await evaluate(send,`(()=>{const b=document.querySelector('.card.target #delete');if(!b)return false;b.click();return true;})()`);
    if(del) actions++;
    await sleep(15);
    const conf=await evaluate(send,`(()=>{const b=document.querySelector('#dlg[open] #confirm');if(!b)return false;b.click();return true;})()`);
    if(conf) actions++;
  }
  return actions;
}
async function firstMatchAction(send,t){
  let actions=0;
  if(t.template==='text-form'){
    const selected=await evaluate(send,`(()=>{const s=document.querySelector('select');if(!s)return false;s.value=${JSON.stringify(t.target)};s.dispatchEvent(new Event('change',{bubbles:true}));return true;})()`);
    if(selected)actions++;
    const clicked=await evaluate(send,`(()=>{const b=document.querySelector('button.save');if(!b)return false;b.click();return true;})()`);
    if(clicked)actions++;
  }else{
    const del=await evaluate(send,`(()=>{const b=document.querySelector('button.decoy-delete,button#delete');if(!b)return false;b.click();return true;})()`);
    if(del)actions++;
    await sleep(15);
    const conf=await evaluate(send,`(()=>{const b=document.querySelector('#dlg[open] #confirm');if(!b)return false;b.click();return true;})()`);
    if(conf)actions++;
  }
  return actions;
}
async function summaryMatches(send,t){
  const expected=t.template==='text-form' ? `Region: ${t.target}` : 'Status: deleted';
  return evaluate(send,`(document.getElementById('summary')||{}).textContent===${JSON.stringify(expected)}`);
}

async function runPolicy(send,base,t,policy,runId){
  const started=Date.now(); let actions=0,retries=0,seen=false;
  await navigate(send,`${base}/task?id=${t.id}&run=${encodeURIComponent(runId)}`);
  if(policy==='selector-only'){
    actions += await firstMatchAction(send,t);
    await sleep(35); seen=await apparent(send);
  } else {
    const ok=await waitUntil(()=>ready(send),1400,35);
    if(ok){
      for(let k=0;k<4;k++){
        actions += await targetAction(send,t); if(k>0)retries++;
        if(await waitUntil(()=>apparent(send),180,30)){seen=true;break;}
        await sleep(45);
      }
      if(policy==='verification-aware' && seen){
        let verified=await waitUntil(()=>summaryMatches(send,t),950,65);
        for(let k=0;k<3&&!verified;k++){
          actions += await targetAction(send,t); retries++;
          verified=await waitUntil(()=>summaryMatches(send,t),950,65);
        }
      }
    }
  }
  await sleep(policy==='verification-aware'?30:55);
  const expected=t.template==='text-form'?t.target:'deleted';
  const vts=runState.get(runId).value===expected;
  return {policy,taskId:t.id,template:t.template,layer:t.layer,severity:t.severity,vts,apparent:seen,falseCompletion:seen&&!vts,actions,retries,latencyMs:Date.now()-started};
}

function aggregate(rows){
  const result={};
  for(const template of templates){
    for(const policy of ['selector-only','semantic-recovery','verification-aware']){
      const g=rows.filter(r=>r.template===template&&r.policy===policy);
      result[`${template}|${policy}`]={
        n:g.length,
        verified:g.filter(r=>r.vts).length,
        verifiedRate:g.filter(r=>r.vts).length/g.length,
        apparent:g.filter(r=>r.apparent).length,
        falseCompletions:g.filter(r=>r.falseCompletion).length,
        meanActions:g.reduce((s,r)=>s+r.actions,0)/g.length,
        meanLatencyMs:g.reduce((s,r)=>s+r.latencyMs,0)/g.length,
        byLayer:Object.fromEntries(layers.map(layer=>{
          const z=g.filter(r=>r.layer===layer);
          return [layer,{n:z.length,verified:z.filter(r=>r.vts).length,rate:z.filter(r=>r.vts).length/z.length}];
        }))
      };
    }
  }
  return result;
}

async function main(){
  fs.mkdirSync(OUT,{recursive:true});
  fs.writeFileSync(path.join(OUT,'multitemplate_tasks.json'),JSON.stringify(TASKS,null,2));
  await new Promise(ok=>server.listen(0,'127.0.0.1',ok));
  const base=`http://127.0.0.1:${server.address().port}`;
  try{fs.rmSync(PROFILE,{recursive:true,force:true});}catch{}
  const cp=spawn(CHROME,[`--remote-debugging-port=${PORT}`,`--user-data-dir=${PROFILE}`,'--headless=new','--disable-gpu','--no-first-run','about:blank'],{stdio:'ignore',windowsHide:true});
  const target=await waitChrome();
  const c=await connect(target.webSocketDebuggerUrl); const send=c.send;
  await send('Page.enable'); await send('Runtime.enable');
  const rows=[];
  for(const policy of ['selector-only','semantic-recovery','verification-aware']){
    for(let i=0;i<TASKS.length;i++){
      const t=TASKS[i]; const runId=`${policy}-${t.id}-${Date.now()}-${i}`;
      runState.set(runId,{value:t.template==='text-form'?t.wrong:'active',committedAt:null});
      rows.push(await runPolicy(send,base,t,policy,runId));
      runState.delete(runId);
      if((i+1)%24===0) console.log(`${policy} ${i+1}/${TASKS.length} VTS=${rows.filter(x=>x.policy===policy&&x.vts).length}`);
    }
  }
  const summary=aggregate(rows);
  fs.writeFileSync(path.join(OUT,'multitemplate_runs.jsonl'),rows.map(x=>JSON.stringify(x)).join('\n')+'\n');
  fs.writeFileSync(path.join(OUT,'multitemplate_summary.json'),JSON.stringify(summary,null,2));
  c.ws.close(); try{cp.kill();}catch{}; server.close();
  console.log(`DONE tasks=${TASKS.length} runs=${rows.length}`);
  console.log(JSON.stringify(summary));
}
main().catch(e=>{console.error(e);try{server.close();}catch{};process.exit(1);});
