import { expect } from "e2e";
import { readFile, writeFile, mkdir, chmod, watch } from "node:fs/promises";
import { execFileSync } from "node:child_process";
import { join } from "node:path";
import { APP_DIR, T3_HOME } from "../e2e.config.ts";
import { test, WEB } from "./t3.ts";

test("Generated conventional commit passes the repository commitlint hook", WEB, async ({ t3, app, screen, browser }) => {
  const workflow = "name: E2E\non:\n  push:\n    branches: [main]\njobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - run: echo test\n";
  const project = await t3.project("commitlint-demo", { ".github/workflows/e2e.yml": workflow });
  const git = (...args: string[]) => execFileSync("git", args, { cwd: project, encoding: "utf8" }).trim();
  git("config", "user.name", "Scenario");
  git("config", "user.email", "scenario@example.invalid");
  git("commit", "--amend", "-m", "chore: initialize test repository");
  git("commit", "--allow-empty", "-m", "ci: add e2e workflow");
  git("commit", "--allow-empty", "-m", "docs: describe test workflow");
  const thread = await t3.thread({ project, title: "Update E2E triggers", messages: ["Prepare the workflow change"] });
  // Replay the exact capitalized model response reported in the issue at the CLI boundary.
  const binary = join(T3_HOME, "bin/no-model");
  await writeFile(binary, `#!/usr/bin/env node\nimport fs from 'node:fs';\nconst args = process.argv.slice(2);\nif (args.includes('list')) { console.log('[]'); process.exit(0); }\nif (args.includes('--version')) { console.log('codex-cli 0.150.0'); process.exit(0); }\nconst index = args.indexOf('--output-last-message');\nif (index < 0) process.exit(1);\nprocess.stdin.resume();\nprocess.stdin.on('end', () => fs.writeFileSync(args[index + 1], JSON.stringify({subject: 'ci: Run E2E on pull requests and manual dispatch instead of pushes', body: '', branch: 'ci/e2e-triggers'})));\n`);
  await chmod(binary, 0o755);
  const settingsPath = join(T3_HOME, "userdata/settings.json");
  const settings = JSON.parse(await readFile(settingsPath, "utf8"));
  settings.sourceControlWritingStyle = { mode: "conventional_commits", customInstructions: "" };
  settings.textGenerationModelSelection = { instanceId: "codex", model: "gpt-6-astra" };
  await writeFile(settingsPath, JSON.stringify(settings));
  const hooks = join(project, ".git/scenario-hooks");
  await mkdir(hooks);
  const commitlintHome = process.env.COMMITLINT_HOME!;
  await writeFile(join(hooks, "config.json"), JSON.stringify({extends: [join(commitlintHome, "node_modules/@commitlint/config-conventional/lib/index.js")]}));
  await writeFile(join(hooks, "commit-msg"), `#!/bin/sh\nnode '${join(commitlintHome, "node_modules/@commitlint/cli/cli.js")}' --config '${join(hooks, "config.json")}' --edit "$1" > '${join(hooks, "result.txt")}' 2>&1\nstatus=$?\ncat '${join(hooks, "result.txt")}' >&2\nprintf '%s' "$status" > '${join(hooks, "exit-code")}'\nexit "$status"\n`);
  await chmod(join(hooks, "commit-msg"), 0o755);
  git("config", "core.hooksPath", hooks);
  await writeFile(join(project, ".github/workflows/e2e.yml"), workflow.replace("  push:\n    branches: [main]", "  pull_request:\n  workflow_dispatch:"));
  await t3.open(thread);
  await browser.evaluate(() => { const style = document.createElement("style"); style.textContent = "*, *::before, *::after { animation: none !important; transition: none !important; }"; document.head.append(style); });
  await browser.evaluate((repo) => { const redact = () => { const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT); let node; while ((node = walker.nextNode())) { if (node.textContent?.includes(repo)) node.textContent = node.textContent.replaceAll(repo, "[test repository]"); } }; new MutationObserver(redact).observe(document.body, {childList: true, subtree: true, characterData: true}); }, project);
  await screen.getByRole("button", "Commit", { exact: true }).tap();
  // A hook result is the milestone: capture the app while the failure toast is still visible.
  const events = watch(hooks);
  try {
    for await (const event of events) {
      if (event.filename === "exit-code") break;
    }
  } finally { await events.return(); }
  const hookExit = await readFile(join(hooks, "exit-code"), "utf8");
  const sha = execFileSync("git", ["rev-parse", "HEAD"], {cwd: APP_DIR, encoding: "utf8"}).trim();
  await writeFile(join(process.env.PROOF_ARTIFACT_DIR!, sha + ".txt"), "App commit: " + sha + "\nModel response replayed: ci: Run E2E on pull requests and manual dispatch instead of pushes\nCommit message passed to hook: " + await readFile(join(project, ".git/COMMIT_EDITMSG"), "utf8") + "\nHook output:\n" + await readFile(join(hooks, "result.txt"), "utf8") + "\nHook exit: " + hookExit + "\n");
  if (hookExit === "0") await screen.getByRole("button", "Publish repository", { exact: true }).waitFor();
  else await screen.getByRole("heading", "Action failed").waitFor();
  // Redact only the synthetic checkout path from the app's error detail.
  await browser.evaluate((repo) => { const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT); let node; while ((node = walker.nextNode())) { if (node.textContent?.includes(repo)) node.textContent = node.textContent.replaceAll(repo, "[test repository]"); } }, project);
  await app.screenshot("generated-commit-result");
  expect(hookExit).toBe("0");
});
