import { expect } from "e2e";
import { execFileSync } from "node:child_process";
import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
import { test, WEB } from "./t3.ts";
import { T3_HOME, APP_DIR } from "../e2e.config.ts";

const pause = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
test("Overlapping explicit refreshes share one follow-up", WEB, async ({t3, app, browser}) => {
  const project = await t3.project("Refresh fixture");
  const git = (...args: string[]) => execFileSync("git", ["-C", project, ...args], {encoding:"utf8"});
  git("remote", "add", "origin", "https://github.com/juliusmarminge/acme-corp-lib.git");
  git("fetch", "--depth=1", "origin", "main");
  git("reset", "--hard", "origin/main");
  git("branch", "--set-upstream-to=origin/main", "main");
  await browser.addInitScript(() => {
    const w = window as any;
    w.refreshEvidence = {requests: [], replies: []};
    const Original = window.WebSocket;
    window.WebSocket = class extends Original {
      constructor(...args: ConstructorParameters<typeof WebSocket>) {
        super(...args);
        this.addEventListener("message", event => {
          for (const line of String(event.data).split("\n")) {
            try { const message = JSON.parse(line); w.refreshEvidence.replies.push({at: Date.now(), message}); } catch {}
          }
        });
      }
      send(data: any) {
        for (const line of String(data).split("\n")) {
          try { const message = JSON.parse(line); if(message.tag === "vcs.refreshStatus") w.refreshEvidence.requests.push({at: Date.now(), message}); } catch {}
        }
        return super.send(data);
      }
    };
  });
  const thread = await t3.thread({project, title:"Overlapping status refresh", messages:["Show the fixture status"], model:"multi-turn"});
  await t3.open(thread);
  await browser.evaluate(() => {
    (window as any).evidenceWindows = [window, ...[1,2,3].map(i => window.open(location.href, `refresh-${i}`))];
  });
  await pause(25_000);
  const ready = await browser.evaluate(() => (window as any).evidenceWindows.map((w: Window) => ({ready:!!w.document.querySelector('[aria-label="Git actions"]'), title:w.document.title})));
  if(ready.some((w:any) => !w.ready)) throw new Error(`Windows did not reach Git actions: ${JSON.stringify(ready)}`);
  console.log("READY",JSON.stringify(ready));
  const start = Date.now();
  await browser.evaluate(async () => {
    const windows = (window as any).evidenceWindows;
    const offsets = [0, 500, 1000, 1200];
    await Promise.all(windows.map(async (w: any, i: number) => {
      await new Promise(resolve => setTimeout(resolve, offsets[i]));
      w.refreshEvidence = {requests:[], replies:[]};
      const button = w.document.querySelector('[aria-label="Git action options"]');
      const group = w.document.querySelector('[aria-label="Git actions"]');
      if(button && !button.matches(':disabled,[aria-disabled="true"]')) button.click();
      else { group.setAttribute("tabindex", "-1"); group.focus(); }
    }));
  });
  await pause(15_000);
  const wire = await browser.evaluate(() => JSON.parse(JSON.stringify((window as any).evidenceWindows.map((w:any) => ({requests:w.refreshEvidence.requests, replies:w.refreshEvidence.replies.filter((r:any) => w.refreshEvidence.requests.some((q:any) => String(q.message.id) === String(r.message.requestId)))})))));
  await app.screenshot("overlapping-refreshes");
  const lines = readFileSync(`${T3_HOME}/userdata/logs/server.trace.ndjson`, "utf8").trim().split("\n").map(line => JSON.parse(line));
  const spans = lines.filter(s => Number(BigInt(s.startTimeUnixNano ?? "0") / 1000000n) >= start && /refreshStatus|invalidateStatus|invalidateRemoteStatus|GitWorkflowService.remoteStatus|GitHubSourceControlProvider|GitHubGraphQlAuth.execute/.test(s.name ?? ""));
  const explicitIds = new Set(spans.filter(s => s.name === "VcsStatusBroadcaster.refreshStatus").map(s => s.traceId));
  const explicitSpans = spans.filter(s => explicitIds.has(s.traceId) || s.name === "ws.rpc.vcs.refreshStatus");
  const counts = Object.fromEntries([...new Set(explicitSpans.map(s => s.name))].map(name => [name,explicitSpans.filter(s=>s.name===name).length]));
  const sha = execFileSync("git", ["-C",APP_DIR,"rev-parse","HEAD"],{encoding:"utf8"}).trim();
  const dir = `${process.env.T3_PROOF_OUTPUT_DIR}/${sha}-${start}`;
  mkdirSync(dir,{recursive:true});
  const disconnectStart = Date.now();
  await browser.evaluate(() => {
    for(const w of (window as any).evidenceWindows) {
      w.document.activeElement?.blur();
      w.refreshEvidence = {requests:[], replies:[]};
    }
  });
  const trigger = browser.evaluate(async () => {
    const windows = (window as any).evidenceWindows;
    await Promise.all(windows.map(async (w:any,i:number) => {
      await new Promise(resolve => setTimeout(resolve,[0,500,1000,1200][i]));
      const button = w.document.querySelector('[aria-label="Git action options"]');
      const group = w.document.querySelector('[aria-label="Git actions"]');
      if(button && !button.matches(':disabled,[aria-disabled="true"]')) button.click();
      else { group.setAttribute("tabindex","-1"); group.focus(); }
    }));
    const waiting = JSON.parse(JSON.stringify(windows[1].refreshEvidence.requests));
    windows[1].close();
    return {at:Date.now(), waiting};
  });
  await pause(700);
  writeFileSync(`${project}/refresh-proof.txt`, "Fresh status while callers wait.\n");
  const disconnected = await trigger;
  await pause(12_000);
  const survivors = await browser.evaluate(() => JSON.parse(JSON.stringify((window as any).evidenceWindows.filter((w:any)=>!w.closed).map((w:any)=>({requests:w.refreshEvidence.requests,replies:w.refreshEvidence.replies.filter((r:any)=>w.refreshEvidence.requests.some((q:any)=>String(q.message.id)===String(r.message.requestId)))})))));
  await app.screenshot("fresh-status-after-waiter-disconnect");
  const safeWire = (clients:any[]) => clients.map((c:any) => ({
    requests:c.requests.map((r:any)=>({at:r.at,id:r.message.id,tag:r.message.tag,traceId:r.message.traceId})),
    replies:c.replies.map((r:any)=>({at:r.at,id:r.message.requestId,exit:r.message.exit._tag,status:{isRepo:r.message.exit.value?.isRepo,refName:r.message.exit.value?.refName,hasWorkingTreeChanges:r.message.exit.value?.hasWorkingTreeChanges,files:r.message.exit.value?.workingTree?.files.map((f:any)=>({path:f.path})),pr:r.message.exit.value?.pr}}))
  }));
  const evidence = {sha,counts,clients:safeWire(wire),spans:explicitSpans.map(({name,startTimeUnixNano,endTimeUnixNano,durationMs,spanId,parentSpanId,traceId,exit})=>({name,startTimeUnixNano,endTimeUnixNano,durationMs,spanId,parentSpanId,traceId,exit:exit?._tag})),disconnect:{startedAt:disconnectStart,closedAt:disconnected.at,waitingRequests:disconnected.waiting.map((r:any)=>({at:r.at,id:r.message.id,tag:r.message.tag})),survivors:safeWire(survivors)}};
  writeFileSync(`${dir}/refresh-evidence.json`, JSON.stringify(evidence,null,2));
  console.log("REFRESH EVIDENCE",JSON.stringify(evidence));
  await browser.evaluate(() => { (window as any).evidenceWindows.slice(1).forEach((w:Window)=>{if(!w.closed)w.close();}); });
  expect(counts["ws.rpc.vcs.refreshStatus"]).toBe(4);
  expect(counts["invalidateStatus"]).toBe(2);
  expect(survivors.every((c:any)=>c.replies.some((r:any)=>r.message.exit?._tag === "Success" && r.message.exit.value?.hasWorkingTreeChanges === true))).toBe(true);
});
