import { expect } from "e2e";
import * as Effect from "effect/Effect";
import * as FileSystem from "effect/FileSystem";
import * as Path from "effect/Path";
import { T3_HOME } from "../e2e.config.ts";
import { runPromise, test, WEB } from "./t3.ts";

const agent = "import readline from 'node:readline';\nimport {randomUUID} from 'node:crypto';\nconst sessions=new Map();\nconst send=m=>process.stdout.write(JSON.stringify({jsonrpc:'2.0',...m})+'\\n');\nasync function callMcp(server,name,args){\n const headers={accept:'application/json, text/event-stream','content-type':'application/json'};\n let url=server.url;\n if(server.type==='stdio'||server.command){\n  const env=Object.fromEntries(server.env.map(x=>[x.name,x.value]));\n  url=env.T3_ACP_MCP_ENDPOINT;headers.authorization=env.T3_ACP_MCP_AUTHORIZATION;\n }else for(const h of server.headers??[])headers[h.name]=h.value;\n async function rpc(m){\n  const r=await fetch(url,{method:'POST',headers,body:JSON.stringify({jsonrpc:'2.0',...m})});\n  if(r.headers.get('mcp-session-id'))headers['mcp-session-id']=r.headers.get('mcp-session-id');\n  const body=await r.text();\n  if(!r.ok)throw new Error('MCP HTTP '+r.status);\n  if(!body)return;\n  const json=body.startsWith('event:')||body.startsWith('data:')?body.split('\\n').filter(x=>x.startsWith('data:')).map(x=>JSON.parse(x.slice(5))).find(x=>x.id===m.id):JSON.parse(body);\n  return json;\n }\n await rpc({id:1,method:'initialize',params:{protocolVersion:'2025-06-18',capabilities:{},clientInfo:{name:'permission-repro',version:'1'}}});\n await rpc({method:'notifications/initialized'});\n const response=await rpc({id:2,method:'tools/call',params:{name,arguments:args}});\n if(response.error)throw new Error(JSON.stringify(response.error));\n return response.result;\n}\nasync function handle(m){\n const p=m.params??{};\n switch(m.method){\n case 'initialize':return {protocolVersion:1,agentCapabilities:{loadSession:true,mcpCapabilities:{http:true},promptCapabilities:{embeddedContext:true}},authMethods:[],agentInfo:{name:'permission-repro-replay',version:'1'}};\n case 'session/new':case 'session/load':{\n  const id=p.sessionId??randomUUID();sessions.set(id,p);\n  return {...(m.method==='session/new'?{sessionId:id}:{}),models:{currentModelId:'multi-turn',availableModels:[{modelId:'multi-turn',name:'Deterministic MCP reproduction'}]}};\n }\n case 'session/prompt':{\n  const text=p.prompt.filter(x=>x.type==='text').map(x=>x.text).join('\\n');\n  const marker=text.match(/REPRO_JSON:(\\{[^\\n]+\\})/);\n  let message='READY';\n  if(marker){\n   const input=JSON.parse(marker[1]);\n   const server=sessions.get(p.sessionId).mcpServers.find(x=>x.name==='t3-code');\n   const result=await callMcp(server,input.name,input.arguments);\n   message='Actual MCP response for `'+input.name+'`\\n\\n'+(result.content??[]).filter(x=>x.type==='text').map(x=>x.text).join('\\n');\n  }\n  send({method:'session/update',params:{sessionId:p.sessionId,update:{sessionUpdate:'agent_message_chunk',content:{type:'text',text:message}}}});\n  return {stopReason:'end_turn'};\n }\n case 'authenticate':case 'session/set_model':case 'session/set_mode':case 'session/close':return {};\n default:throw new Error('Unknown method '+m.method);\n }\n}\nreadline.createInterface({input:process.stdin}).on('line',line=>{const m=JSON.parse(line);if(m.id==null)return;handle(m).then(result=>send({id:m.id,result}),e=>send({id:m.id,error:{code:-32603,message:e.message}}));});\n";
const setup = Effect.gen(function* () {
 const fs = yield* FileSystem.FileSystem;
 const path = yield* Path.Path;
 const settingsPath=path.join(T3_HOME,"userdata/settings.json");
 const settings=JSON.parse(yield* fs.readFileString(settingsPath));
 const agentPath=path.join(T3_HOME,"bin/permission-repro.mjs");
 yield* fs.writeFileString(agentPath,agent);
 settings.defaultRuntimeMode="full-access";
 settings.providerInstances.devReplay={...settings.providerInstances.devReplay,config:{source:"local",commandPath:process.execPath,commandArgs:[agentPath]}};
 yield* fs.writeFileString(settingsPath,JSON.stringify(settings));
 return {supervised:Effect.gen(function*(){
  const current=JSON.parse(yield* fs.readFileString(settingsPath));
  yield* fs.writeFileString(settingsPath,JSON.stringify({...current,defaultRuntimeMode:"approval-required"}));
 })};
});

test("Cross-thread refusals must not call unrelated sibling threads child and parent", WEB, async ({t3,app,screen,browser})=>{
 const settings=await runPromise(setup);
 const project=await t3.project("Permission reproduction");
 const target=await t3.thread({project,title:"Independent target — Full access",messages:["Reply with just the word READY."]});
 await runPromise(settings.supervised);
 const results:string[]=[];
 const tools=[
  {name:"t3_thread_send",extra:{mode:"auto",message:"CROSS_THREAD_PROBE"}},
  {name:"t3_thread_interrupt",extra:{}},
  {name:"t3_thread_update",extra:{action:"rename",title:"SHOULD NOT RENAME"}},
 ];
 for(const tool of tools){
  const caller=await t3.thread({project,title:tool.name+" — Supervised sibling",messages:["Call once. REPRO_JSON:"+JSON.stringify({name:tool.name,arguments:{threadId:target.threadId,...tool.extra}})]});
  await t3.open(caller);
  await screen.getByText("Actual MCP response for",{exact:false}).waitFor({state:"visible",timeout:60000});
  await app.screenshot(tool.name+"-refusal");
  results.push(await browser.evaluate(()=>document.body.innerText));
 }
 await t3.open(target);
 await screen.getByText("READY",{exact:true}).waitFor({state:"visible",timeout:60000});
 await app.screenshot("target-unchanged");
 // This checks the incorrect relationship label, independent of the added advice.
 for(const text of results)expect(text).not.toMatch(/Child runtime mode[\s\S]*parent mode/);
});
