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

// Synthetic ACP peer: no usage_update and no context-window metadata.
const agent = String.raw`
const fs = require('node:fs');
const readline = require('node:readline');
const store = process.argv[2];
const send = x => process.stdout.write(JSON.stringify({jsonrpc:'2.0',...x})+'\n');
const read = () => {try{return JSON.parse(fs.readFileSync(store,'utf8'))}catch{return {next:0,sessions:{}}}};
const save = d => fs.writeFileSync(store,JSON.stringify(d));
readline.createInterface({input:process.stdin}).on('line', line => {
 const m=JSON.parse(line); if(m.id==null)return;
 const p=m.params||{}; let result={}; const d=read();
 if(m.method==='initialize') result={protocolVersion:1,agentCapabilities:{loadSession:true,promptCapabilities:{embeddedContext:true}},authMethods:[],agentInfo:{name:'history-replay',version:'1'}};
 if(m.method==='session/new'||m.method==='session/load') {
  const id=m.method==='session/load'?p.sessionId:'conversation-'+(++d.next);
  d.sessions[id]??={turns:0}; save(d);
  result={...(m.method==='session/new'?{sessionId:id}:{}),models:{currentModelId:'history',availableModels:[{modelId:'history',name:'History replay'}]}};
 }
 if(m.method==='session/prompt') {
  const s=d.sessions[p.sessionId]; s.turns++; d.original??=p.sessionId; save(d);
  const update=u=>send({method:'session/update',params:{sessionId:p.sessionId,update:u}});
  const last=JSON.stringify(p.prompt).includes('Check native conversation');
  if(!last){
   const toolCallId='output-'+s.turns;
   update({sessionUpdate:'tool_call',toolCallId,kind:'execute',title:'Read synthetic build output',status:'in_progress',rawInput:{command:'synthetic-output'}});
   update({sessionUpdate:'tool_call_update',toolCallId,status:'completed',content:[{type:'content',content:{type:'text',text:('The build completed successfully and checked each source file for consistent results. ').repeat(500)}}]});
  }
  update({sessionUpdate:'agent_message_chunk',content:{type:'text',text:last?('Native conversation: '+(p.sessionId===d.original?'ORIGINAL':'REPLACEMENT')+'\n\nPrompts received in this native conversation: '+s.turns+'.\n\nNo token usage or context window was reported.'):'History turn '+s.turns+' complete.'}});
  result={stopReason:'end_turn'};
 }
 send({id:m.id,result});
});
`;

test('Switching back to an ACP agent retains its native conversation with 104 KB of stored output', WEB, async ({t3,app,screen,browser}) => {
 const fixture=join(T3_HOME,'history-peer'); await mkdir(fixture,{recursive:true});
 const executable=join(fixture,'agent.cjs'); await writeFile(executable,agent);
 const {cli}=makeState(T3_HOME);
 const call=async(tag:string,payload:unknown)=> browser.evaluate(async ({tag,payload})=>{
  return await new Promise((resolve,reject)=>{
   const ws=new WebSocket(location.origin.replace(/^http/,'ws')+'/ws?orchestrationProtocol=2');
   const timer=setTimeout(()=>{ws.close();reject(new Error('RPC timed out: '+tag));},30000);
   ws.onopen=()=>ws.send(JSON.stringify({_tag:'Request',id:'1',tag,payload,headers:[]}));
   ws.onmessage=ev=>{const messages=JSON.parse(ev.data); for(const m of (Array.isArray(messages)?messages:[messages])) {
    if(m._tag==='Ping')ws.send(JSON.stringify({_tag:'Pong'}));
    if(m._tag==='Exit'&&m.requestId==='1'){clearTimeout(timer);ws.close();if(m.exit._tag==='Success')resolve(m.exit.value);else reject(new Error(JSON.stringify(m.exit)));}
   }};
  });
 },{tag,payload} as any);
 const project=await t3.project('Handoff budget demo');
 await t3.open();
 await screen.getByText('Handoff budget demo',{exact:true}).first().waitFor({state:'visible'});
 await call('server.updateSettings',{patch:{},providerInstanceMutation:{operation:'create',instanceId:'historyReplay',instance:{driver:'acpRegistry',displayName:'History replay',enabled:true,config:{source:'local',commandPath:process.execPath,commandArgs:[executable,join(fixture,'sessions.json')]}}}});
 const turn=async(args:string[])=> JSON.parse((await runPromise(cli(['thread',...args,'--wait','--json']))).stdout);
 const started=await turn(['start',project,'Generate history 1','--title','ACP history handoff','--provider','historyReplay','--model','history']);
 const id=started.threadId;
 for(let n=2;n<=13;n++)await turn(['send',id,'Generate history '+n]);
 await screen.getByText('ACP history handoff',{exact:true}).first().tap();
 await screen.getByRole('textbox').waitFor({state:'visible'});
 await call('orchestration.dispatchCommand',{type:'thread.model-selection.set',commandId:crypto.randomUUID(),threadId:id,modelSelection:{instanceId:'devReplay',model:'multi-turn'}});
 await turn(['send',id,'Switch away for one turn']);
 await call('orchestration.dispatchCommand',{type:'thread.model-selection.set',commandId:crypto.randomUUID(),threadId:id,modelSelection:{instanceId:'historyReplay',model:'history'}});
 await screen.getByRole('textbox').fill('Check native conversation');
 await screen.getByRole('textbox').press('Enter');
 // Wait for either actual native-session identity before taking paired evidence.
 await screen.getByText('No token usage or context window was reported.',{exact:false}).waitFor({state:'visible'});
 await app.screenshot('native-conversation-after-switch');
 await expect(screen.getByText('Native conversation: ORIGINAL',{exact:false})).toBeVisible();
});
