import { expect } from "e2e";
import { execFile } from "node:child_process";
import { promisify } from "node:util";
import { readFile, writeFile, watch } from "node:fs/promises";
import { join } from "node:path";
import { APP_DIR, T3_HOME } from "../e2e.config.ts";
import { makeState, runPromise, test, WEB } from "./t3.ts";

const exec = promisify(execFile);
// Only the prerequisite show-ref commands are hostile; all other commands run real Git.
const shim = `#!/usr/bin/python3
import os, sys, signal, time, json
args = sys.argv[1:]
if 'show-ref' in args and any('pool-blocker-' in a for a in args):
    log = os.path.join(os.path.dirname(__file__), 'hostile-events.jsonl')
    def record(event):
        with open(log, 'a') as f:
            f.write(json.dumps({'event': event, 'pid': os.getpid()}) + '\\n')
    def term(sig, frame):
        record('SIGTERM ignored')
    signal.signal(signal.SIGTERM, term)
    record('started')
    time.sleep(120)
    record('natural exit')
    sys.exit(1)
os.execv('/usr/bin/git', ['git'] + args)
`;

async function records(file: string) {
  return (await readFile(file, "utf8").catch(() => "")).trim().split("\n").filter(Boolean).map(line => JSON.parse(line));
}
async function eightStarted(directory: string) {
  const file = join(directory, "hostile-events.jsonl");
  const events = watch(directory, { signal: AbortSignal.timeout(20_000) });
  try {
    if ((await records(file)).filter(row => row.event === "started").length === 8) return;
    for await (const _ of events) {
      if ((await records(file)).filter(row => row.event === "started").length === 8) return;
    }
  } finally { await events.return(); }
}

test("Timed-out Git releases its permits so workspace preparation reports a branch conflict", WEB,
  async ({ t3, app, screen, browser }) => {
    const project = await t3.project("git-timeout-demo");
    await exec("git", ["checkout", "-b", "branch-X"], { cwd: project });
    await t3.thread({ project, title: "Git timeout demonstration", messages: ["Ready"] });
    const blockers = [];
    for (let i = 0; i < 4; i++) blockers.push(await t3.project(`pool-slot-${i + 1}`));
    await t3.open();
    await browser.locator('[data-testid="sidebar-row-card"]').first().waitFor({ state: "visible", timeout: 90_000 });
    await browser.locator('[data-testid="sidebar-row-card"]').first().tap();
    const command = [process.execPath, join(APP_DIR, "apps/server/src/bin.ts"), "thread", "start", project, "Prepare a worktree using branch-X", "--title", "Worktree waiting for Git", "--provider", "devReplay", "--model", "multi-turn", "--worktree", "branch-X", "--branch", "branch-X", "--wait", "--json", "--base-dir", T3_HOME];
    const python = `import subprocess, time, json, os, signal, re
command = ${JSON.stringify(command)}
log = ${JSON.stringify(join(T3_HOME, "bin", "hostile-events.jsonl"))}
result_file = ${JSON.stringify(join(project, ".git", "proof-result.json"))}
print("\\033[2J\\033[H", end="", flush=True)
print("GIT TIMEOUT / REAL SERVER MCP", flush=True)
print("8 server-issued Git commands; 5s timeout; SIGTERM ignored", flush=True)
print("MCP: launch worktree thread using checked-out branch-X", flush=True)
start = time.monotonic()
child = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, start_new_session=True)
with open(result_file + '.pid', 'w') as f: f.write(str(child.pid))
output = None
conflict = False
for target in [0, 5, 7, 10, 20]:
    time.sleep(max(0, target - (time.monotonic() - start)))
    rows = [json.loads(line) for line in open(log)]
    pids = {row['pid'] for row in rows if row['event'] == 'started'}
    live = 0
    for pid in pids:
        try: os.kill(pid, 0); live += 1
        except ProcessLookupError: pass
    ignored = sum(row['event'] == 'SIGTERM ignored' for row in rows)
    if child.poll() is not None and output is None:
        out, err = child.communicate()
        output = out + err
        conflict = 'already' in output.lower() and ('branch' in output.lower() or 'worktree' in output.lower())
    state = 'branch-conflict error received' if conflict else ('PENDING' if output is None else 'completed without branch conflict')
    print(f"{target:2}s | hostile Git alive: {live}/8 | SIGTERM ignored: {ignored}/8", flush=True)
    print(f"     MCP launch: {state}", flush=True)
result = {'branchConflict': conflict, 'hostileAlive': live, 'sigtermIgnored': ignored, 'pending': output is None}
print("Observation complete at 20 seconds.", flush=True)
with open(result_file, 'w') as f: json.dump(result, f)
`;
    await writeFile(join(project, ".git", "prove.py"), python);
    await screen.getByRole("button", "Toggle terminal drawer").tap();
    await writeFile(join(T3_HOME, "bin", "git"), shim, { mode: 0o755 });
    try {
      await browser.evaluate(async (workspaces: string[]) => {
        const socket = new WebSocket(`${location.protocol === "https:" ? "wss:" : "ws:"}//${location.host}/ws?orchestrationProtocol=2`);
        await new Promise<void>((resolve, reject) => { socket.onopen = () => resolve(); socket.onerror = reject; });
        socket.onmessage = event => {
          for (const message of String(event.data).split("\n").filter(Boolean)) {
            const frame = JSON.parse(message);
            if (frame._tag === "Ping") socket.send(JSON.stringify({ _tag: "Pong" }) + "\n");
          }
        };
        (window as any).__hostileSocket = socket;
        for (let i = 0; i < 4; i++) socket.send(JSON.stringify({
          _tag: "Request", id: String(800 + i), tag: "vcs.switchRef",
          payload: { cwd: workspaces[i], refName: `pool-blocker-${i}` }, headers: [],
        }) + "\n");
      }, blockers);
      await eightStarted(join(T3_HOME, "bin"));
      await browser.evaluate(() => {
        const threadId = decodeURIComponent(location.pathname.split("/")[2]!);
        (window as any).__hostileSocket.send(JSON.stringify({ _tag: "Request", id: "900", tag: "terminal.write", headers: [], payload: { threadId, terminalId: "term-1", data: "PS1='$ '; python3 .git/prove.py\r" } }) + "\n");
      });
      const resultFile = join(project, ".git", "proof-result.json");
      const changes = watch(join(project, ".git"), { signal: AbortSignal.timeout(35_000) });
      let result: any;
      try {
        for await (const _ of changes) {
          const content = await readFile(resultFile, "utf8").catch(() => "");
          if (content) { result = JSON.parse(content); break; }
        }
      } finally { await changes.return(); }
      await new Promise(resolve => setTimeout(resolve, 500));
      const firstCapture = await app.screenshot("mcp-launch-stable-check");
      await new Promise(resolve => setTimeout(resolve, 500));
      const finalCapture = await app.screenshot("mcp-launch-after-20-seconds");
      const stable = (await readFile(firstCapture)).equals(await readFile(finalCapture));
      console.log(JSON.stringify({ ...result, stableScreenshots: stable }));
      await expect(result.branchConflict).toBe(true);
    } finally {
      const childPid = Number(await readFile(join(project, ".git", "proof-result.json.pid"), "utf8").catch(() => "0"));
      if (childPid > 0) { try { process.kill(-childPid, "SIGTERM"); } catch {} }
      for (const row of await records(join(T3_HOME, "bin", "hostile-events.jsonl"))) {
        if (row.event === "started") { try { process.kill(row.pid, "SIGKILL"); } catch {} }
      }
      await writeFile(join(T3_HOME, "bin", "git"), "#!/bin/sh\nexec /usr/bin/git \"$@\"\n", { mode: 0o755 });
      await browser.evaluate(() => (window as any).__hostileSocket?.close());
    }
  });
