import { expect } from "e2e";
import { readFile, writeFile, mkdir } from "node:fs/promises";
import { join } from "node:path";
import { DatabaseSync } from "node:sqlite";
import { test, WEB, makeState, runPromise } from "./t3.ts";
import { T3_HOME } from "../e2e.config.ts";

// A deterministic ACP peer: no model or account, real protocol and adapter.
const peer = `#!/usr/bin/env node
import {createInterface} from 'node:readline';
const rl = createInterface({input:process.stdin});
const mode=process.argv[2], reason=process.argv[3];
const send=x=>process.stdout.write(JSON.stringify({jsonrpc:'2.0',...x})+'\\n');
const update=(sessionId,update)=>send({method:'session/update',params:{sessionId,update}});
rl.on('line',line=>{ const m=JSON.parse(line); if(m.id===undefined)return;
 const reply=result=>send({id:m.id,result}); const p=m.params||{};
 switch(m.method){
 case 'initialize': return reply(mode==='fallback'?{protocolVersion:1,agentInfo:{name:'Stop reason replay',version:'1'},agentCapabilities:{},authMethods:[]}:{protocolVersion:2,info:{name:'Stop reason replay',version:'1'},capabilities:{session:{}},authMethods:[]});
 case 'session/new': return reply({sessionId:'root'});
 case 'session/prompt': {
 const root=p.sessionId;
 if(mode==='fallback'){
 update(root,{sessionUpdate:'agent_message_chunk',content:{type:'text',text:'hello from mock'}});
 return reply({stopReason:reason});
 }
 update(root,{sessionUpdate:'user_message',messageId:'user-1',content:[{type:'text',text:'Please write a short greeting'}]});
 reply({messageId:'user-1'});
 update(root,{sessionUpdate:'state_update',state:'running'});
 if(mode==='native'||mode==='claude'){
 update(root,{sessionUpdate:'tool_call_update',toolCallId:'agent-1',name:'Agent',title:'Review child',kind:'think',status:'in_progress',rawInput:{prompt:'Review the code',description:'Review child',run_in_background:true}});
 update(root,{sessionUpdate:'subagent_update',sessionId:'child-1',state:{state:'running'}});
 update('child-1',{sessionUpdate:'session_message',messageId:'agent-1',senderSessionId:root,recipientSessionId:'child-1',content:[{type:'text',text:'Review the code'}]});
 update(root,{sessionUpdate:'tool_call_update',toolCallId:'agent-1',status:'completed',_meta:{claudeCode:{toolResponse:{isAsync:true}}}});
 update('child-1',{sessionUpdate:'state_update',state:'running'});
 if(mode==='native') update('child-1',{sessionUpdate:'state_update',state:'idle',stopReason:reason,...(reason==='error'?{error:{code:-32000,message:'Synthetic child failure'}}:{})});
 else update(root,{sessionUpdate:'subagent_update',sessionId:'child-1',state:{state:'idle',stopReason:reason,...(reason==='error'?{error:{code:-32000,message:'Synthetic child failure'}}:{})}});
 }
 update(root,{sessionUpdate:'tool_call_update',toolCallId:'root-tool',name:'Bash',title:'Read package',kind:'execute',status:'in_progress',rawInput:{command:'cat package.json'}});
 update(root,{sessionUpdate:'agent_message_chunk',content:{type:'text',text:'hello from mock'}});
 setTimeout(()=>update(root,{sessionUpdate:'state_update',state:'idle',stopReason:reason,...(reason==='error'?{error:{code:-32000,message:'Synthetic child failure'}}:{})}),700);
 return;
 }
 default:return reply({});
 }
});
`;

test("ACP refused and limit-stopped turns and children report failure", WEB, async ({ t3, app, screen, browser }) => {
  const bin=join(T3_HOME,"bin"); await mkdir(bin,{recursive:true});
  const executable=join(bin,"claude-agent-acp"); await writeFile(executable,peer,{mode:0o755});
  const cases=[
    {id:"refusal",mode:"idle",reason:"refusal",message:"The agent refused this request"},
    {id:"tokens",mode:"idle",reason:"max_tokens",message:"output token limit"},
    {id:"requests",mode:"idle",reason:"max_turn_requests",message:"request limit for this turn"},
    {id:"fallback",mode:"fallback",reason:"refusal",message:"The agent refused this request"},
    {id:"native",mode:"native",reason:"refusal",message:"The agent refused this request"},
    {id:"claude",mode:"claude",reason:"max_tokens",message:"output token limit"},
    {id:"failedReply",mode:"native",reason:"error",message:"Synthetic child failure"},
  ];
  // settings.json is the supported watched configuration; thread data uses the real CLI.
  const settingsPath=join(T3_HOME,"userdata/settings.json");
  const settings=JSON.parse(await readFile(settingsPath,"utf8"));
  for(const c of cases) settings.providerInstances[c.id]={driver:"acpRegistry",displayName:c.id+" relay",enabled:true,config:{source:"local",commandPath:executable,commandArgs:[c.mode,c.reason],customModels:["default"]}};
  settings.environmentLabel="Scenario";
  await writeFile(settingsPath,JSON.stringify(settings));
  const project=await t3.project("ACP stop reasons",{"package.json":"{}\n"});
  await t3.open("/settings/providers");
  await screen.getByText("refusal relay",{exact:true}).first().waitFor({state:"visible"});
  const cli=makeState(T3_HOME).cli;
  const observed=[];
  for(const c of cases){
    const result=await runPromise(cli(["thread","start",project,"Please write a short greeting","--title",c.id+" stop","--provider",c.id,"--model","default","--wait","--json"],[0,1]));
    const launched=JSON.parse(result.stdout);
    await t3.open({threadId:launched.threadId});
    await screen.getByText("hello from mock",{exact:true}).first().waitFor({state:"visible"});
    if (await browser.evaluate(()=>!!document.querySelector('[aria-label="Toggle thread details panel"][aria-pressed="true"]'))) await screen.getByRole("button","Toggle thread details panel").tap();
    if(await browser.evaluate(()=>Array.from(document.querySelectorAll("button")).some(b=>b.textContent?.startsWith("Worked for")))) await screen.getByRole("button",/^Worked for/).tap();
    if(await browser.evaluate(()=>!!document.querySelector('[aria-label="Dismiss error"]'))) await screen.getByRole("button","Dismiss error").tap();
    const body=await browser.evaluate(()=>document.body.innerText);
    await app.screenshot(c.id+"-turn");
    observed.push({case:c.id,failed:launched.status==="failed",reason:body.includes(c.message)});
    if(c.mode==="native"||c.mode==="claude"){
      const db=new DatabaseSync(join(T3_HOME,"userdata/statev2.sqlite"),{readOnly:true});
      const child=db.prepare("select child_thread_id, status, payload_json from orchestration_v2_projection_subagents where thread_id = ?").get(launched.threadId);
      if(!child) throw new Error("No child was projected");
      const replies=db.prepare("select payload_json from orchestration_v2_projection_messages where thread_id = ? and role = 'assistant'").all(child.child_thread_id);
      const childState=JSON.parse(String(child.payload_json));
      observed.push({case:c.id+" child",failed:child.status==="failed",reason:String(childState.result).includes(c.message),noInventedReply:replies.length===0});
      db.close();
      await t3.open({threadId:String(child.child_thread_id)});
      await screen.getByText("Review the code",{exact:true}).first().waitFor({state:"visible"});
      if (await browser.evaluate(()=>!!document.querySelector('[aria-label="Toggle thread details panel"][aria-pressed="true"]'))) await screen.getByRole("button","Toggle thread details panel").tap();
      await app.screenshot(c.id+"-child");
    }
  }
  await expect(observed).toEqual(cases.flatMap(c=>[
    {case:c.id,failed:true,reason:true},
    ...(c.mode==="native"||c.mode==="claude"?[{case:c.id+" child",failed:true,reason:true,noInventedReply:true}]:[])
  ]));
});
