import { createServer } from 'node:http';
import { readdirSync, readFileSync, writeFileSync } from 'node:fs';
import { expect } from 'e2e';
import { test, WEB } from './t3.ts';

// Only the synthetic replay process for this scenario's thread is inspected; credentials stay in memory.
function replayConnection(threadId:string) {
  for(const pid of readdirSync('/proc').filter(p=>/^\d+$/.test(p))) {
    try {
      const env=Object.fromEntries(readFileSync(`/proc/${pid}/environ`,'utf8').split('\0').map(x=>{const i=x.indexOf('=');return [x.slice(0,i),x.slice(i+1)]}));
      if(env.T3CODE_THREAD_ID===threadId && env.T3_ACP_MCP_ENDPOINT && env.T3_ACP_MCP_AUTHORIZATION)
        return {endpoint:env.T3_ACP_MCP_ENDPOINT,authorization:env.T3_ACP_MCP_AUTHORIZATION};
    } catch {}
  }
  throw new Error('The scenario replay process did not expose its MCP connection');
}

test('Browser navigation escapes a script that never yields', WEB, async ({t3, app, screen, browser}) => {
  const hits:string[]=[];
  const loads:Record<string,number>={};
  const results:any[]=[];
  const pause=(ms:number)=>new Promise(r=>setTimeout(r,ms));
  const server=createServer((req,res)=>{
    const u=new URL(req.url!,'http://fixture');
    res.setHeader('Cache-Control','no-store');
    if(u.pathname==='/mark'){hits.push(u.searchParams.get('id')!);res.end('ok');return;}
    const id=u.searchParams.get('id')??'page';
    const load=loads[id]=(loads[id]??0)+1;
    const busy=u.pathname==='/busy';
    const slow=u.pathname==='/slow';
    res.setHeader('Content-Type','text/html; charset=utf-8');
    res.end(`<!doctype html><html><head><title>${busy?'Busy page':'Destination ready'} ${id} load ${load}</title><style>body{font:22px system-ui;background:#132036;color:white;padding:30px}h1{color:${busy?'#ffb86b':'#73efb6'}}</style></head><body><h1>${busy?'Busy page: script never yields':'Destination ready'}</h1><p>Case: ${id}</p><p>Load ${load}</p><p id="state">${busy?'Blocking after four seconds':'Startup script running'}</p><script>fetch('/mark?id=loaded-${id}-${load}');${busy?`setTimeout(()=>{navigator.sendBeacon('/mark?id=hung-${id}');while(true){}},4000)`:`${slow?'const until=performance.now()+1500;while(performance.now()<until){};':''}window.done=true;document.getElementById('state').textContent='window.done = true: startup completed';fetch('/mark?id=done-${id}')`}</script></body></html>`);
  });
  await new Promise<void>(r=>server.listen(0,'0.0.0.0',r));
  const port=(server.address() as any).port;
  const origin=`http://localhost:${port}`;
  let tabId:string|undefined;
  let proofThreadId:string;
  let session:string|undefined;
  let connection:{endpoint:string;authorization:string};
  let seq=0;
  async function rpc(method:string,params:any){
    const response=await fetch(connection.endpoint,{method:'POST',headers:{'Content-Type':'application/json',Accept:'application/json, text/event-stream',Authorization:connection.authorization,...(session?{'mcp-session-id':session}:{})},body:JSON.stringify({jsonrpc:'2.0',id:++seq,method,params})});
    const sid=response.headers.get('mcp-session-id');if(sid)session=sid;
    const text=await response.text();
    const payload=text.startsWith('event:')||text.startsWith('data:')?JSON.parse(text.split('\n').find(x=>x.startsWith('data:'))!.slice(5)):JSON.parse(text);
    if(payload.error)throw new Error(JSON.stringify(payload.error));
    return payload.result;
  }
  async function tool(name:string,args:any={}){
    const result=await rpc('tools/call',{name,arguments:args});
    if(result.isError) return {error:result.content?.filter((x:any)=>x.type==='text').map((x:any)=>x.text).join('\n')};
    return result.structuredContent??JSON.parse(result.content.find((x:any)=>x.type==='text').text);
  }
  async function fresh(url:string){
    if(tabId)await tool('t3_preview_close',{tabId});
    const opened=await tool('preview_open',{url:`${origin}/normal?id=setup`,open:false,reuseExistingTab:false});
    if(opened.error)throw new Error(opened.error);
    tabId=opened.tabId;
    await browser.evaluate("async ({threadId,tabId}) => { const { useRightPanelStore } = await import('/src/rightPanelStore.ts'); const {environmentId} = await(await fetch('/.well-known/t3/environment')).json(); useRightPanelStore.getState().openBrowser({environmentId,threadId},tabId); }",{threadId:proofThreadId,tabId});
    await pause(900);
    await tool('preview_resize',{tabId,mode:'freeform',width:520,height:600});
    await nav(url);
    await pause(200);
  }
  async function nav(url:string,readiness='load'){return tool('preview_navigate',{tabId,url,readiness,timeoutMs:5000});}
  async function label(text:string){await browser.evaluate((text)=>{let d=document.getElementById('proof-note');if(!d){d=document.createElement('div');d.id='proof-note';document.body.append(d);}Object.assign(d.style,{position:'fixed',left:'280px',bottom:'8px',right:'8px',zIndex:'999999',background:'#18283f',color:'white',font:'14px monospace',padding:'10px',border:'1px solid #73efb6',pointerEvents:'none'});d.textContent=text;},text);}
  async function takeControl(){
    if(await browser.evaluate(()=>Array.from(document.querySelectorAll('button')).some(b=>b.textContent?.trim()==='Take control')))
      await screen.getByRole('button','Take control',{exact:true}).tap();
  }
  async function stableShot(id:string){
    await browser.mouse.move(600,300);
    await pause(600);
    let previous=await app.screenshot(`${id}-stability-0`);
    for(let i=1;i<=8;i++){
      await pause(500);const current=await app.screenshot(i===8?id:`${id}-stability-${i}`);
      if(readFileSync(previous).equals(readFileSync(current))) {await app.screenshot(id);return;}
      previous=current;
    }
  }
  async function record(id:string,ok:boolean,detail:any){results.push({id,ok,...detail});await label(`${id}: ${ok?'PASS':'FAIL'} | ${JSON.stringify(detail)}`);await stableShot(id);}

  try{
    const project=await t3.project('Navigation proof');
    const thread=await t3.thread({project,title:'Busy page recovery',model:'question',messages:['Prepare browser navigation proof']});
    proofThreadId=thread.threadId;
    connection=replayConnection(thread.threadId);
    await rpc('initialize',{protocolVersion:'2025-06-18',capabilities:{},clientInfo:{name:'navigation-proof',version:'1'}});
    await browser.addInitScript(() => {
      const Native=window.WebSocket;
      (window as any).__proofSockets=[];
      (window as any).WebSocket=class extends Native {
        constructor(url:string|URL,protocols?:string|string[]){super(url,protocols);if(String(url).includes('/api/preview-stream/ws'))(window as any).__proofSockets.push(this);}
      };
    });
    await t3.open(thread);
    await pause(3000);
    await t3.open(thread);
    await screen.getByRole('button','Toggle right panel',{exact:true}).waitFor({state:'visible'});
    await browser.evaluate("async () => {await import('/src/rightPanelStore.ts');}");
    await pause(3000);
    await browser.evaluate(()=>{const s=document.createElement('style');s.textContent='*,*::before,*::after{animation:none!important;transition:none!important;caret-color:transparent!important}';document.head.append(s);});
    for(const delay of [4400,5200,6100]){
      for(const readiness of ['none','load']){
        const id=`agent-same-${readiness}-${delay}`;
        await fresh(`${origin}/busy?id=${id}`);
        await pause(delay);
        await label(`${id}: busy page, navigating with timeoutMs 5000`);
        const start=Date.now();const reply=await nav(`${origin}/normal?id=${id}`,readiness);const elapsed=Date.now()-start;
        await pause(Math.max(0,5000-elapsed));
        const status=await tool('preview_status',{tabId});
        await record(id,hits.includes(`done-${id}`)&&status.url===`${origin}/normal?id=${id}`,{elapsedMs:elapsed,url:status.url,available:status.available,error:reply.error??null});
      }
      const id=`agent-cross-slow-${delay}`;
      await fresh(`${origin}/busy?id=${id}`);await pause(delay);
      const start=Date.now();const reply=await nav(`http://127.0.0.1:${port}/slow?id=${id}`,'load');
      await pause(1800);const status=await tool('preview_status',{tabId});
      await record(id,hits.includes(`done-${id}`),{elapsedMs:Date.now()-start,url:status.url,startupCompleted:hits.includes(`done-${id}`),error:reply.error??null});
    }
    for(const action of ['address','back','forward','reload','hard-reload','stream-hard-reload']){
      const id=`viewer-${action}`;
      if(action==='back'){
        await fresh(`${origin}/normal?id=${id}-target`);await nav(`${origin}/busy?id=${id}`);
      }else if(action==='forward'){
        await fresh(`${origin}/busy?id=${id}`);await nav(`${origin}/normal?id=${id}-target`);
        await takeControl();await screen.getByRole('button','Back',{exact:true}).tap();await pause(1000);
      }else await fresh(`${origin}/busy?id=${id}`);
      await pause(5200);
      await takeControl();
      const prior=loads[id]??0;
      const targetCount=hits.filter(x=>x===`done-${id}-target`).length;
      await label(`${id}: independently reopened busy page; invoking ${action}`);
      if(action==='address'){
        const address=screen.getByRole('textbox','Search or enter URL');await address.fill(`${origin}/normal?id=${id}-target`);await address.press('Enter');
      }else if(action==='back'||action==='forward')await screen.getByRole('button',action==='back'?'Back':'Forward',{exact:true}).tap();
      else if(action==='reload')await screen.getByRole('button','Refresh',{exact:true}).tap();
      else if(action==='hard-reload'){
        await screen.getByRole('button','Preview menu',{exact:true}).tap();await screen.getByText('Hard reload',{exact:true}).tap();
      }else if(action==='stream-hard-reload'){
        await browser.evaluate(()=>{const socket=(window as any).__proofSockets.filter((s:WebSocket)=>s.readyState===1).at(-1);if(!socket)throw new Error('Viewer stream is not connected');socket.send(JSON.stringify({type:'reload',ignoreCache:true}));});
      }
      await pause(3000);
      const status=await tool('preview_status',{tabId});
      const ok=action.includes('reload')?hits.includes(`loaded-${id}-${prior+1}`):status.url===`${origin}/normal?id=${id}-target`;
      await record(id,ok,{url:status.url,available:status.available,loadsBefore:prior,loadsAfter:loads[id]??0});
    }
    await label(`Navigation results: ${results.filter(r=>r.ok).length}/${results.length} passed`);
    await stableShot('navigation-summary');
    console.log('NAVIGATION_RESULTS '+JSON.stringify(results));
    await expect(results.filter(r=>!r.ok).map(r=>r.id)).toEqual([]);
  }finally{
    if(tabId)await tool('t3_preview_close',{tabId}).catch(()=>{});
    server.closeAllConnections();await new Promise<void>(r=>server.close(()=>r()));
  }
});
