import { expect } from "e2e";
import { execFileSync } from "node:child_process";
import { test, WEB } from "./t3.ts";

const org = process.env.AZURE_DEVOPS_TEST_ORG!.replace(/\/$/, "");
const projectName = process.env.AZURE_DEVOPS_TEST_PROJECT!;
const repoId = "f02aa55b-3908-4340-8d1d-5b2d91bba4c6";
const repository = `t3code-factory-test/${projectName}/_git/t3-factory-test`;
const remote = `${org}/${projectName}/_git/t3-factory-test`;
const prUrl = `${remote}/pullrequest/5`;
const pause = (ms: number) => new Promise(r => setTimeout(r, ms));
async function azure(path: string, body?: unknown, method?: string) {
  const response = await fetch(`${org}/${projectName}/_apis/${path}${path.includes("?") ? "&" : "?"}api-version=${path.startsWith("policy/evaluations") ? "7.1-preview.1" : "7.1"}`, {
    method: method ?? (body === undefined ? "GET" : "POST"),
    headers: { Authorization: `Basic ${Buffer.from(`:${process.env.AZURE_DEVOPS_EXT_PAT}`).toString("base64")}`, "Content-Type": "application/json" },
    ...(body === undefined ? {} : { body: JSON.stringify(body) }),
  });
  if (!response.ok) throw new Error(`Azure ${response.status}: ${await response.text()}`);
  return response.json();
}
async function evaluations() {
  return (await azure(`policy/evaluations?artifactId=${encodeURIComponent('vstfs:///CodeReview/CodeReviewId/d18445d6-86b1-4b1d-88aa-bc6d45d8b9b0/5')}`)).value;
}
async function queue(outcome: string) {
  const definition = await azure("build/definitions/1");
  definition.variables.proofOutcome.value = outcome;
  await azure("build/definitions/1", definition, "PUT");
  const evaluation = (await evaluations()).find((e: any) => e.configuration.id === 2);
  await azure(`policy/evaluations/${evaluation.evaluationId}`, {}, "PATCH");
}
async function settled() {
  for (let i = 0; i < 90; i++) {
    const evaluation = (await evaluations()).find((e: any) => e.configuration.id === 2);
    if (["approved", "rejected", "broken"].includes(evaluation.status)) return evaluation.status;
    await pause(2000);
  }
  throw new Error("Azure validation did not settle");
}

test("Azure required build validation renders checks and wakes its watcher", WEB, async ({ t3, app, screen, browser }) => {
  const workspace = await t3.project("Azure validation proof", { "README.md": "Synthetic Azure PR validation proof" });
  const git = (...args: string[]) => execFileSync("git", args, { cwd: workspace, stdio: "pipe" });
  git("remote", "add", "origin", remote);
  git("config", "credential.helper", '!f() { echo username=pat; echo "password=$AZURE_DEVOPS_EXT_PAT"; }; f');
  git("-c", 'credential.helper=!f() { echo username=pat; echo "password=$AZURE_DEVOPS_EXT_PAT"; }; f', "fetch", "origin", "proof31146-change", "proof31146-base");
  git("checkout", "-B", "proof31146-change", "origin/proof31146-change");
  const thread = await t3.thread({ project: workspace, title: "Watch Azure build validation", messages: ["Watch this PR while required build validation runs."] });
  await queue("fail");
  await t3.open(thread);
  await browser.evaluate(() => {
    (window as any).proofRpc = (tag: string, payload: unknown) => new Promise((resolve, reject) => {
      const ws = new WebSocket(`${location.origin.replace(/^http/, "ws")}/ws?orchestrationProtocol=2`);
      const timer = setTimeout(() => { ws.close(); reject(new Error(`RPC timeout: ${tag}`)); }, 45000);
      ws.onopen = () => ws.send(JSON.stringify({ _tag: "Request", id: "1", tag, payload, headers: [] }));
      ws.onerror = () => { clearTimeout(timer); reject(new Error(`RPC socket: ${tag}`)); };
      ws.onmessage = event => {
        const decoded = JSON.parse(event.data);
        for (const response of Array.isArray(decoded) ? decoded : [decoded]) {
          if (response._tag === "Ping") ws.send(JSON.stringify({ _tag: "Pong" }));
          if (response._tag !== "Exit" || String(response.requestId) !== "1") continue;
          clearTimeout(timer); ws.close();
          if (response.exit._tag === "Success") resolve(response.exit.value);
          else reject(new Error(JSON.stringify(response.exit)));
        }
      };
    });
  });
  const projects: any = await browser.evaluate(async () => (await fetch("/api/projects")).json());
  const project = (projects.projects ?? projects).find((p: any) => p.workspaceRoot === workspace);
  const ref = { projectId: project.id, host: "dev.azure.com", repository, number: 5 };
  await browser.evaluate(async (command) => (window as any).proofRpc("orchestration.dispatchCommand", command), {
    type: "thread.pull-request.watch", commandId: crypto.randomUUID(), threadId: thread.threadId,
    host: "dev.azure.com", repository, number: 5, watching: true, link: { url: prUrl, source: "agent" },
  });
  await browser.evaluate(() => new Promise<void>((resolve, reject) => {
    const ready = () => document.body.innerText.includes("first fixture turn complete");
    if (ready()) { resolve(); return; }
    const timer = setTimeout(() => { observer.disconnect(); reject(new Error("Thread did not render")); }, 60000);
    const observer = new MutationObserver(() => { if (ready()) { clearTimeout(timer); observer.disconnect(); resolve(); } });
    observer.observe(document.body, { childList: true, subtree: true });
  }));
  await app.screenshot("pending-validation-watch");
  await settled();
  await pause(125000);
  const failedDetail: any = await browser.evaluate(async ref => (window as any).proofRpc("pullRequests.detail", ref), ref);
  const failedThread: any = await browser.evaluate(async id => (await fetch(`/api/orchestration/threads/${id}`, { headers: { "x-t3-orchestration-protocol": "2" } })).json(), thread.threadId);
  await app.screenshot("failed-validation-wake");
  await app.open(`/pull-requests?selectedUrl=${encodeURIComponent(prUrl)}&selectedProjectId=${project.id}&repository=${encodeURIComponent(repository)}&number=5&host=dev.azure.com&q=Azure%20build%20validation%20proof`);
  await pause(8000);
  await screen.getByRole("button", /^Checks$/).tap();
  await pause(1000);
  await app.screenshot("failed-validation-checks");
  await queue("success");
  await settled();
  await pause(125000);
  // Read the same real server detail and thread after the second watch refresh.
  await app.open(`/${(await browser.evaluate(async () => (await fetch('/.well-known/t3/environment')).json()) as any).environmentId}/${thread.threadId}`);
  await pause(5000);
  await browser.evaluate(() => { (window as any).proofReady = true; });
  const passedThread: any = await browser.evaluate(async id => (await fetch(`/api/orchestration/threads/${id}`, { headers: { "x-t3-orchestration-protocol": "2" } })).json(), thread.threadId);
  await app.screenshot("passed-validation-wake");
  await app.open(`/pull-requests?selectedUrl=${encodeURIComponent(prUrl)}&selectedProjectId=${project.id}&repository=${encodeURIComponent(repository)}&number=5&host=dev.azure.com&q=Azure%20build%20validation%20proof`);
  await pause(8000);
  await screen.getByRole("button", /^Checks$/).tap();
  await pause(1000);
  await app.screenshot("passed-validation-checks");
  await expect(screen.getByText("All checks passed", { exact: true })).toBeVisible();
  await expect(failedDetail.checks.some((c: any) => c.status === "failure" && c.required)).toBe(true);
  await expect(JSON.stringify(failedThread).includes("Checks failed")).toBe(true);
  await expect(JSON.stringify(passedThread).includes("required check passed")).toBe(true);
});
