import { expect } from "e2e";
import { spawn, execFileSync } from "node:child_process";
import { readFileSync, mkdirSync, openSync } from "node:fs";
import { createServer } from "node:net";
import { tmpdir } from "node:os";
import { basename, join } from "node:path";
import { test, WEB } from "./t3.ts";
import { observeResources, annotate } from "./routing-proof-observe.ts";
import { APP_DIR, T3_HOME } from "../e2e.config.ts";

const port = () => new Promise<number>(resolve => { const s=createServer(); s.listen(0,"127.0.0.1",()=>{const p=(s.address() as {port:number}).port;s.close(()=>resolve(p));}); });
const delay = (ms:number) => new Promise(r=>setTimeout(r,ms));
const exec = (args:string[]) => execFileSync(process.execPath,["apps/server/src/bin.ts",...args],{cwd:APP_DIR,encoding:"utf8",env:{...process.env,T3CODE_DEV_SEED:"0"}});
function serverPid(home:string) {
 const info=JSON.parse(readFileSync(`${home}/userdata/server-runtime.json`,"utf8")); return info.pid;
}
test("Automatic draft refreshes the machine winner before dispatch",WEB,async({t3,app,screen,browser})=>{
 const capture = async(label:string) => {
  await browser.evaluate(()=>{if(document.activeElement instanceof HTMLElement)document.activeElement.blur();return null;});
  for(let attempt=0;attempt<8;attempt++) {
   const first=await app.screenshot(`settle-${label}-${attempt}-a`);
   await delay(500);
   const second=await app.screenshot(`settle-${label}-${attempt}-b`);
   if(readFileSync(first).equals(readFileSync(second))) {
    console.log(`STABLE ${label}: identical screenshots 500ms apart`);
    return app.screenshot(label);
   }
  }
  throw Error(`The ${label} capture did not settle`);
 };
 const dir=join(process.env.T3_PROOF_SCRATCH ?? join(tmpdir(),basename(new URL("../..",import.meta.url).pathname)),`run-${Date.now()}`);mkdirSync(dir,{recursive:true});
 const home=`${dir}/secondary`;const p=await port();
 const child=spawn(process.execPath,[new URL("./start-secondary.ts",import.meta.url).pathname,"web",home,"127.0.0.1",String(p)],{cwd:APP_DIR,env:{...process.env,T3_SCENARIO_APP_DIR:APP_DIR,T3CODE_DEV_SEED:"0",SCENARIO_PRIMARY_ORIGIN:JSON.parse(readFileSync(`${T3_HOME}/userdata/server-runtime.json`,"utf8")).devUrl.replace(/\/$/,"")},stdio:["ignore",openSync(`${dir}/secondary.log`,"a"),openSync(`${dir}/secondary.log`,"a")]});
 try {
  let ready=false;for(let i=0;i<180;i++){try{ready=(await fetch(`http://127.0.0.1:${p}/.well-known/t3/environment`)).ok;}catch{}if(ready)break;await delay(1000);}if(!ready)throw Error("Secondary server not ready");
  execFileSync("taskset",["-pc","0-6",String(serverPid(T3_HOME))]);
  execFileSync("taskset",["-pc","7",String(serverPid(home))]);
  await browser.addInitScript(()=>{if(!localStorage.getItem("t3code:client-settings:v1"))localStorage.setItem("t3code:client-settings:v1",JSON.stringify({loadBalancingEnabled:true,onboardingCompletedAt:"2026-11-12T00:00:00.000Z"}));});
  await t3.open();
  await screen.getByRole("button","Settings").tap({timeout:120000});
  await app.open("/settings/connections");
  await screen.getByRole("button","Add environment").tap();
  await browser.evaluate(()=>{const input=document.querySelector('input[placeholder="backend.example.com"]') as HTMLInputElement;input.type="password";});
  await browser.locator('input[placeholder="backend.example.com"]').fill(`http://127.0.0.1:${p}${readFileSync(`${home}/pairing-path`,"utf8")}`);
  await screen.getByRole("dialog").getByRole("button","Add environment").tap();
  await delay(3000);
  const workspace=await t3.project("routing-demo",{".git/config":"[remote \"origin\"]\n url = https://example.invalid/scenario/routing-demo.git\n"});
  exec(["project","add",workspace,"--title","routing-demo","--base-dir",home]);
  await app.open("/");
  await screen.getByRole("button","New thread").tap({timeout:120000});
  await screen.getByRole("combobox","Run on").tap();
  await screen.getByRole("option","Auto balance").tap();
  await browser.evaluate(async()=>{
    const {useComposerDraftStore}=await import("/src/composerDraftStore.ts");
    await new Promise<void>((resolve,reject)=>{
      let off=()=>{};
      const timer=setTimeout(()=>{off();reject(new Error("Automatic choice did not settle"));},15000);
      const check=(state:any)=>{if(Object.values(state.draftThreadsByThreadKey).some((d:any)=>d.environmentSelection==="auto" && d.loadBalancedEnvironmentId)){clearTimeout(timer);queueMicrotask(()=>off());resolve();}};
      off=useComposerDraftStore.subscribe(check);check(useComposerDraftStore.getState());
    });
    return true;
  });
  await delay(2000);
  const drafts = await browser.evaluate(async()=>{
    const {useComposerDraftStore}=await import("/src/composerDraftStore.ts");
    return Object.values(useComposerDraftStore.getState().draftThreadsByThreadKey);
  });
  console.log("DRAFT", drafts);
  console.log("COMPOSER",await browser.evaluate(()=>document.body.innerText));
  const ids=[T3_HOME,home].map(h=>readFileSync(`${h}/userdata/environment-id`,"utf8").trim());
  if(!drafts.some((d:any)=>d.environmentSelection==="auto" && d.loadBalancedEnvironmentId===ids[0])) throw Error("Draft did not settle on automatic A");
  const initial=await observeResources(browser,ids);
  const score=(r:any)=>r.cpuCount*(1-r.cpuUtilization)*r.availableMemoryBytes/r.totalMemoryBytes;
  if(score(initial[0])<=score(initial[1])) throw Error("A is not the initial resource winner");
  console.log("INITIAL RESOURCES",JSON.stringify(initial));
  await annotate(browser,`Driver observations\nReal servers · Replay agent\nInitial CPU capacity: A ${initial[0].cpuCount}, B ${initial[1].cpuCount}\nAuto draft selected: A\nWorkspace: current checkout`);
  await capture("initial-draft");
  execFileSync("taskset",["-pc","0",String(serverPid(T3_HOME))]);
  execFileSync("taskset",["-pc","1-7",String(serverPid(home))]);
  await delay(6000);
  const refreshed=await observeResources(browser,ids);
  if(score(refreshed[1])<=score(refreshed[0])) throw Error("B is not the current resource winner");
  console.log("CURRENT RESOURCES",JSON.stringify(refreshed));
  await annotate(browser,`Driver observations\nReal servers · Replay agent\nCPU capacity now: A ${refreshed[0].cpuCount}, B ${refreshed[1].cpuCount}\nCurrent resource winner: B\nDraft originally selected: A\nWaiting 16 seconds before send`);
  await delay(16000);
  await capture("resource-winner-changed");
  await screen.getByTestId("composer-editor").fill("Route this draft to the current resource winner");
  await screen.getByTestId("composer-editor").press("Enter");
  await delay(8000);
  console.log("SENT",await browser.evaluate(()=>document.body.innerText));
  const a=JSON.parse(exec(["thread","list","--project",workspace,"--base-dir",T3_HOME,"--json"]));
  const b=JSON.parse(exec(["thread","list","--project",workspace,"--base-dir",home,"--json"]));
  const destination=b.length?"B":"A";
  console.log("DESTINATIONS",JSON.stringify({a:a.length,b:b.length}));
  await annotate(browser,`Driver observations\nReal servers · Replay agent\nCurrent resource winner: B\nCreated thread on: ${destination}\nThreads: A ${a.length}, B ${b.length}`);
  await capture("dispatched");
  expect(b.length).toBe(1);
 } finally {child.kill("SIGTERM");await new Promise<void>(r=>child.once("exit",()=>r()));}
});
