import { expect } from "e2e";
import fs from "node:fs";
import {setTimeout as delay} from "node:timers/promises";
import { PNG } from "pngjs";
import { test, WEB } from "./t3.ts";
import { T3_HOME } from "../e2e.config.ts";

// Synthetic clock advances inactivity without waiting a day. Only the server is shifted.
const clock = "/tmp/settlement-proof/clock-offset";
test("Parent settlement drains idle child before inactivity threshold", WEB, async ({t3, app, browser, screen}) => {
 const capture = async(label:string) => {
   await browser.evaluate(()=>{const style=document.createElement('style');style.textContent='*,*::before,*::after{animation:none!important;transition:none!important;caret-color:transparent!important}';document.head.append(style);});
   for(let attempt=0;attempt<4;attempt++) {
     const a=await app.screenshot(`${label}-stability-a-${attempt}`);
     await delay(500);
     const b=await app.screenshot(`${label}-stability-b-${attempt}`);
     if(PNG.sync.read(fs.readFileSync(a)).data.equals(PNG.sync.read(fs.readFileSync(b)).data)) return app.screenshot(label);
   }
   throw new Error(`The view did not stabilize: ${label}`);
 };
 fs.writeFileSync(clock, "0");
 const project = await t3.project("Settlement demonstration");
 await t3.thread({project,title:"Parent awaiting approval",model:"question",messages:["Wait for my approval while the child completes"]});
 await t3.thread({project,title:"Independent inactivity control",messages:["Finish the control task"]});
 await t3.open();
 await screen.getByRole('button','Parent awaiting approval, Input, Settlement demonstration').textContent();
 const origin = await browser.evaluate(()=>location.origin);
 const pairing = new URL(fs.readFileSync(`${T3_HOME}/pairing-path`, 'utf8'),origin);
 const credential = new URLSearchParams(pairing.hash.slice(1)).get('token') ?? pairing.searchParams.get('token')!;
 const auth = await fetch(`${origin}/oauth/token`,{method:'POST',body:new URLSearchParams({grant_type:'urn:ietf:params:oauth:grant-type:token-exchange',subject_token:credential,subject_token_type:'urn:t3:params:oauth:token-type:environment-bootstrap',requested_token_type:'urn:ietf:params:oauth:token-type:access_token'})}).then(r=>r.json());
 const bearer = auth.access_token;
 if(!bearer) throw new Error('Synthetic driver could not authenticate');
 const rpc = async (tag:string, payload:any):Promise<any> => browser.evaluate(async ({tag,payload,bearer}) => {
   const ticket = await fetch('/api/auth/websocket-ticket',{method:'POST',headers:{authorization:`Bearer ${bearer}`,'x-t3-orchestration-protocol':'2'}}).then(r=>r.json());
   const descriptor = await fetch('/.well-known/t3/environment').then(r=>r.json());
   const socket = new WebSocket(`${location.origin.replace(/^http/,'ws')}/ws?orchestrationProtocol=${descriptor.orchestrationProtocolVersion ?? 2}&wsTicket=${encodeURIComponent(ticket.ticket)}`);
   return await new Promise((resolve,reject)=>{
     const timer = setTimeout(()=>{socket.close();reject(new Error('RPC response timeout '+tag));},20000);
     socket.onopen=()=>socket.send(JSON.stringify({_tag:'Request',id:'1',tag,payload,headers:[]}));
     socket.onerror=()=>{clearTimeout(timer);reject(new Error('RPC connection failed'));};
     socket.onmessage=e=>{ const data=JSON.parse(e.data); for(const m of Array.isArray(data)?data:[data]) {
       if(m._tag==='Ping') socket.send(JSON.stringify({_tag:'Pong'}));
       if(m._tag==='Exit' && String(m.requestId)==='1'){clearTimeout(timer);socket.close();m.exit._tag==='Success'?resolve(m.exit.value):reject(new Error(JSON.stringify(m.exit)));}
     }};
   });
 },{tag,payload,bearer});
 const shell = await fetch(`${origin}/api/orchestration/shell`,{headers:{authorization:`Bearer ${bearer}`,'x-t3-orchestration-protocol':'2'}}).then(r=>r.json());
 fs.writeFileSync('/tmp/settlement-proof/shell-private.json',JSON.stringify(shell));
 const threads = shell.threads;
 const parent = threads.find((t:any)=>t.title==='Parent awaiting approval');
 const control = threads.find((t:any)=>t.title==='Independent inactivity control');
 const command = (type:string,threadId:string,extra:any={}) => rpc('orchestration.dispatchCommand',{type,threadId,commandId:crypto.randomUUID(),...extra});
 const projection = (threadId:string) => rpc('orchestration.getThreadProjection',{threadId});
 const p = await projection(parent.id);
 const run = p.runs.at(-1);
 await rpc('orchestration.dispatchCommand',{
   type:'delegated_task.request',commandId:crypto.randomUUID(),parentThreadId:parent.id,parentRunId:run.id,parentNodeId:run.rootNodeId,
   task:'Complete this synthetic child task',title:'Hidden idle child',modelSelection:{instanceId:'devReplay',model:'multi-turn'},runtimeMode:'full-access',interactionMode:'default',createdBy:'user',creationSource:'mcp',completionWake:'settled_only'
 });
 let childId:string='';
 await expect.poll(async()=>{
   const parentProjection = await projection(parent.id);
   childId = parentProjection.subagents[0]?.childThreadId ?? '';
   if(!childId)return false;
   const c = await projection(childId);
   return c.runs.some((r:any)=>r.status==='completed');
 }).toBe(true);
 const descriptor = await browser.evaluate(async()=>fetch('/.well-known/t3/environment').then(r=>r.json()));
 await app.open(`/threads/${descriptor.environmentId}/${childId}`);
 await screen.getByText('Complete this synthetic child task',{exact:false}).first().tap();
 await capture('before-parent-settlement-auto-enabled');
 await rpc('server.updateSettings',{patch:{sidebarAutoSettleAfterDays:1,sidebarAutoSettleOnMerge:false}});
 const pp=await projection(parent.id);
 const request=pp.runtimeRequests.find((r:any)=>r.status==='pending');
 await command('runtime-request.respond',parent.id,{requestId:request.id,answers:{schema_vs_ui_flexibility:'Strict schemas (Recommended)'}});
 await expect.poll(async()=>(await projection(parent.id)).runs.at(-1).status).toBe('completed');
 await command('thread.settle',parent.id);
 try {await expect.poll(async()=>(await projection(childId)).thread.settledOverride,{timeout:4000}).toBe('settled');}catch{}
 const follows=(await projection(childId)).thread.settledOverride==='settled';
 expect.soft(follows).toBe(true);
 await capture('after-parent-settlement-auto-enabled');
});
