import { expect } from "e2e";
import { execFileSync } from "node:child_process";
import { readFileSync, writeFileSync, mkdtempSync, readdirSync } from "node:fs";
import { join } from "node:path";
import { test, WEB } from "./t3.ts";

// Real Git repositories; no mocked Git output or RPC results.
test("translated Git explains a rejected push", WEB, async ({ t3, app, screen, browser }) => {
  const git = (cwd: string, ...args: string[]) => execFileSync("git", args, { cwd, encoding: "utf8" });
  const project = await t3.project("Translated push", {"README.md": "Initial\n"});
  const fixture = mkdtempSync(join(process.env.PROOF_SCRATCH!, "run-"));
  const remote = join(fixture, "remote.git");
  const other = join(fixture, "other");
  git(fixture, "init", "--bare", "--initial-branch=main", remote);
  git(project, "remote", "add", "origin", remote);
  git(project, "push", "-u", "origin", "main");
  git(fixture, "clone", remote, other);
  writeFileSync(join(project, "local.txt"), "Local change\n");
  git(project, "add", ".");
  git(project, "-c", "user.name=Scenario", "-c", "user.email=scenario@example.invalid", "commit", "-m", "Local change");
  const thread = await t3.thread({ project, title: "Translated push rejection", messages: ["Ready"] });
  await t3.open(thread);
  await screen.getByRole("button", "Push", {exact:true}).tap();
  const confirm = screen.getByRole("button", "Push to main", {exact:true});
  await confirm.waitFor({state:"visible"});
  writeFileSync(join(other, "remote.txt"), "Remote change\n");
  git(other, "add", ".");
  git(other, "-c", "user.name=Scenario", "-c", "user.email=scenario@example.invalid", "commit", "-m", "Remote change");
  git(other, "push", "origin", "main");
  const before = [git(project,"rev-parse","HEAD"),git(remote,"rev-parse","main")];
  // Verify the real server inherited the locale; persist only the requested non-secret values.
  const servers = readdirSync("/proc").filter(p => /^\d+$/.test(p)).flatMap(p => {
    try { const cmd = readFileSync(`/proc/${p}/cmdline`,"utf8"); const env = readFileSync(`/proc/${p}/environ`,"utf8").split("\0");
      return (cmd.includes("node") && env.some(e => e.startsWith("T3CODE_HOME="))) && env.includes("LANGUAGE=de") ? [env.filter(e=>/^(LANG|LC_ALL|LANGUAGE|LOCPATH|GIT_TEXTDOMAINDIR)=/.test(e))] : [];
    } catch { return []; }
  });
  if (!servers.length) throw new Error("Server locale was not inherited");
  // Redact only the private repository path in rendered errors, including the recording.
  await browser.evaluate((repository: string) => {
    const redact = () => {
      const nodes = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
      while (nodes.nextNode()) {
        const node = nodes.currentNode;
        if (node.nodeValue?.includes(repository)) node.nodeValue = node.nodeValue.replaceAll(repository, "[synthetic repository]");
      }
    };
    const style = document.createElement("style");
    style.textContent = '[data-slot="toast-description"] { -webkit-line-clamp: unset !important; max-height: none !important; overflow: visible !important; }';
    document.head.append(style);
    const watched = new WeakSet<WebSocket>();
    const send = WebSocket.prototype.send;
    WebSocket.prototype.send = function(data) {
      if (!watched.has(this)) {
        watched.add(this);
        this.addEventListener("message", event => {
          if (typeof event.data === "string" && event.data.includes("push_rejected_non_fast_forward"))
            (window as unknown as Record<string, unknown>).proofReason = true;
        });
      }
      return send.call(this, data);
    };
    new MutationObserver(redact).observe(document.body, {childList:true, subtree:true, characterData:true});
  }, project);
  await confirm.tap();
  await screen.getByText("Action failed", {exact:true}).waitFor({state:"visible"});
  // Capture after the toast enters, then verify two frames 500 ms apart are identical.
  await browser.mouse.move(1100, 130);
  await new Promise(resolve => setTimeout(resolve, 2000));
  let previous = await app.screenshot("settling");
  let stable = false;
  for (let i=0; i<6; i++) {
    await new Promise(resolve => setTimeout(resolve, 500));
    const next = await app.screenshot(`stability-${i}`);
    const pixels = (path: string) => execFileSync("convert", [path, "rgba:-"], {maxBuffer: 8_000_000});
    if (pixels(previous).equals(pixels(next))) { stable = true; break; }
    previous = next;
  }
  if (!stable) throw new Error("Capture did not settle");
  await app.screenshot("translated-push-rejection");
  const after = [git(project,"rev-parse","HEAD"),git(remote,"rev-parse","main")];
  writeFileSync(join(fixture, "verification.json"), JSON.stringify({
    localeInherited: servers.length > 0,
    refsUnchanged: JSON.stringify(before) === JSON.stringify(after),
    reasonSeenOnWire: await browser.evaluate(() => (window as unknown as Record<string, unknown>).proofReason === true),
    renderedError: await browser.evaluate(() => document.querySelector('[data-slot="toast-description"]')?.textContent),
  }, null, 2));
  if (JSON.stringify(before)!==JSON.stringify(after)) throw new Error("Rejected push changed refs");
  await expect(screen.getByText("The remote has commits this branch doesn't have. Pull or rebase, then push again.", {exact:false})).toBeVisible();
});
