import { expect } from "e2e";
import { execFileSync } from "node:child_process";
import { readFileSync, writeFileSync, symlinkSync } from "node:fs";
import { setTimeout as delay } from "node:timers/promises";
import { test, WEB } from "./t3.ts";
import { T3_HOME } from "../e2e.config.ts";

test("Old displayed revisions settle to Changed for repeated ticks and cached-head reticks", WEB, async ({ t3, app, screen }) => {
  const base = "http://127.0.0.1:34151";
  const token = readFileSync(process.env.PROOF_TOKEN_FILE!, "utf8").trim();
  symlinkSync(process.env.PROOF_TEA_BINARY!, `${T3_HOME}/bin/tea`);
  const api = async (path: string, body: unknown) => {
    const response = await fetch(`${base}/api/v1/${path}`, {
      method: "POST",
      headers: { Authorization: `token ${token}`, "Content-Type": "application/json" },
      body: JSON.stringify(body),
    });
    if (!response.ok) throw new Error(`Forgejo ${path}: ${response.status} ${await response.text()}`);
    return response.json();
  };
  const fixture = async (title: string, suffix: string) => {
    const name = `viewed-${suffix}-${Date.now()}`;
    await api("user/repos", { name, private: false, default_branch: "main" });
    const project = await t3.project(title, {
      "src/greeting.ts": 'export const greeting = "hello";\n',
      "src/numbers.ts": "export const number = 1;\n",
      "src/status.ts": 'export const status = "ready";\n',
    });
    const git = (...args: string[]) => execFileSync("git", args, {
      cwd: project,
      env: { ...process.env, GIT_TERMINAL_PROMPT: "0", PROOF_GIT_TOKEN: token },
      stdio: "pipe",
    }).toString();
    const push = () => git("-c", 'credential.helper=!f() { echo username=reviewer; echo "password=$PROOF_GIT_TOKEN"; }; f', "push", "origin", "HEAD");
    const commit = (message: string) => {
      git("add", ".");
      git("-c", "user.name=Scenario", "-c", "user.email=scenario@example.invalid", "commit", "-m", message);
    };
    git("remote", "add", "origin", `${base}/reviewer/${name}.git`);
    push();
    git("checkout", "-b", "review");
    writeFileSync(`${project}/src/greeting.ts`, 'export const greeting = "hello there";\n');
    writeFileSync(`${project}/src/numbers.ts`, "export const number = 2;\n");
    writeFileSync(`${project}/src/status.ts`, 'export const status = "waiting";\n');
    commit("Three synthetic files for review");
    push();
    await delay(2500);
    await api(`repos/reviewer/${name}/pulls`, {
      title, head: "review", base: "main", body: "Synthetic three-file review. Keep hello there displayed while hello friend is pushed.",
    });
    const thread = await t3.thread({ project, title, messages: ["Prepare review"] });
    return {
      thread,
      url: `/pull-requests?repository=reviewer%2F${name}&number=1&host=127.0.0.1%3A34151&selectedUrl=${encodeURIComponent(`${base}/reviewer/${name}/pulls/1`)}`,
      pushNewGreeting: async () => {
        writeFileSync(`${project}/src/greeting.ts`, 'export const greeting = "hello friend";\n');
        commit("Push newer synthetic greeting");
        push();
        await delay(2500);
      },
    };
  };
  const repeated = await fixture("Repeated tick after push", "repeated");
  const cached = await fixture("Untick and retick after push", "cached");
  const greetingToggle = () => screen.getByRole("checkbox", /Viewed|Changed/).first();
  const revealGreeting = async () => {
    const expand = screen.getByRole("button", "Expand diff").first();
    if (await expand.isVisible()) await expand.tap();
  };
  const open = async (item: typeof repeated) => {
    await t3.open(item.thread);
    await t3.open(item.url);
    await screen.getByRole("button", "Code").tap();
    const maximize = screen.getByRole("button", "Maximize panel");
    if (await maximize.isVisible()) await maximize.tap();
    await greetingToggle().waitFor({ state: "visible" });
    const expand = screen.getByRole("button", "Expand all files");
    if (await expand.isVisible()) await expand.tap();
    await delay(1000);
  };

  // Independent PRs keep the cached-head case from priming the repeated-tick case.
  await open(repeated);
  await app.screenshot("case-1-old-diff-before-push");
  await repeated.pushNewGreeting();
  await greetingToggle().tap();
  await delay(3000);
  await revealGreeting();
  await app.screenshot("case-1-first-tick");
  const firstTickState = await greetingToggle().getAttribute("aria-label");
  await greetingToggle().tap();
  await delay(5000);
  await revealGreeting();
  await app.screenshot("case-1-second-tick");
  await delay(90000);
  await app.screenshot("case-1-second-tick-after-90-seconds");
  const repeatedChecked = await greetingToggle().isChecked();
  const repeatedState = await greetingToggle().getAttribute("aria-label");
  const repeatedZeroCount = await screen.getByText("0 / 3").isVisible();

  await open(cached);
  await app.screenshot("case-2-old-diff-before-first-tick");
  await greetingToggle().tap();
  await delay(2500);
  await revealGreeting();
  await app.screenshot("case-2-viewed-before-push");
  await cached.pushNewGreeting();
  await greetingToggle().tap();
  await delay(1500);
  await greetingToggle().tap();
  await delay(5000);
  await revealGreeting();
  await app.screenshot("case-2-untick-retick-within-60-seconds");
  const cachedChecked = await greetingToggle().isChecked();
  const cachedState = await greetingToggle().getAttribute("aria-label");
  const cachedZeroCount = await screen.getByText("0 / 3").isVisible();

  expect(firstTickState).toBe("Changed");
  expect(repeatedChecked).toBe(false);
  expect(repeatedState).toBe("Changed");
  expect(repeatedZeroCount).toBe(true);
  expect(cachedChecked).toBe(false);
  expect(cachedState).toBe("Changed");
  expect(cachedZeroCount).toBe(true);
});
