import assert from "node:assert/strict";
import { execFileSync } from "node:child_process";
import { setTimeout as delay } from "node:timers/promises";

const webUrl = process.env.AWP_WEB_URL;
const controlPlaneUrl = process.env.AWP_CONTROL_PLANE_URL;
const databaseUrl = process.env.AWP_TEST_POSTGRES_URL;
const repositoryUrl = process.env.AWP_PROJECT_REMOTE_URL;
const playwrightModule = process.env.AWP_PLAYWRIGHT_MODULE;
if (!webUrl) throw new Error("AWP_WEB_URL is required");
if (!controlPlaneUrl) throw new Error("AWP_CONTROL_PLANE_URL is required");
if (!databaseUrl) throw new Error("AWP_TEST_POSTGRES_URL is required");
if (!repositoryUrl) throw new Error("AWP_PROJECT_REMOTE_URL is required");
if (!playwrightModule) throw new Error("AWP_PLAYWRIGHT_MODULE is required");

async function post(path: string, body?: unknown): Promise<Record<string, unknown>> {
  const response = await fetch(controlPlaneUrl + path, {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify(body ?? {}),
  });
  const payload = (await response.json()) as Record<string, unknown>;
  assert.ok(response.ok, `${path} failed: ${JSON.stringify(payload)}`);
  return payload;
}

function psql(query: string): string {
  return execFileSync("psql", [databaseUrl!, "-At", "-c", query], {
    encoding: "utf8",
  }).trim();
}

async function waitForSql(query: string, timeout = 30_000): Promise<string> {
  const deadline = Date.now() + timeout;
  while (Date.now() < deadline) {
    const value = psql(query);
    if (value) return value;
    await delay(100);
  }
  throw new Error(`timed out waiting for SQL evidence: ${query}`);
}

const suffix = Date.now().toString(36);
const created = await post("/internal/projects", {
  name: `AC-23 live transitions ${suffix}`,
  repositoryUrl,
});
const project = created.project as { id: string };
await post(`/internal/projects/${project.id}/vision`, { summary: "Observe every live transition" });
await post(`/internal/projects/${project.id}/goals`, {
  title: "Live observability",
  successCriteria: ["All six stages appear without manual reload"],
});
const planned = await post(`/internal/projects/${project.id}/plans`, {
  title: "Live transition plan",
  taskTitles: ["Produce candidate", "Verify candidate", "Finish plan"],
});
const plan = planned.plan as { id: string };
const tasks = planned.tasks as Array<{ id: string }>;
assert.equal(tasks.length, 3);

const { chromium } = await import(playwrightModule);
const browser = await chromium.launch({ headless: true });
try {
  const page = await browser.newPage();
  await page.goto(`${webUrl}/projects/${encodeURIComponent(project.id)}`, {
    waitUntil: "networkidle",
  });

  await post(`/internal/projects/${project.id}/plans/${plan.id}/approve`);
  const escapedProject = project.id.replaceAll("'", "''");
  const taskId = await waitForSql(
    `select id from tasks where project_id = '${escapedProject}' and status = 'dispatched' limit 1`,
  );
  await page
    .locator(`[data-task-id="${taskId}"]`)
    .getByText("dispatched", { exact: true })
    .waitFor({ timeout: 5_000 });

  const factoryRunId = await waitForSql(
    `select id from factory_runs where project_id = '${escapedProject}' and status in ('starting','running') order by created_at desc limit 1`,
  );
  await page.locator(`[data-factory-run-id="${factoryRunId}"]`).waitFor({ timeout: 5_000 });

  const agentRunId = await waitForSql(
    `select ar.id from agent_runs ar join factory_runs fr on fr.id = ar.factory_run_id where fr.project_id = '${escapedProject}' and ar.status = 'active' limit 1`,
  );
  await page.locator(`[data-agent-run-id="${agentRunId}"]`).waitFor({ timeout: 5_000 });

  const changeSetId = await waitForSql(
    `select id from change_sets where project_id = '${escapedProject}' order by created_at desc limit 1`,
  );
  await page.locator(`[data-change-set-id="${changeSetId}"]`).waitFor({ timeout: 5_000 });

  const reviewId = await waitForSql(
    `select id from reviews where change_set_id = '${changeSetId.replaceAll("'", "''")}' limit 1`,
  );
  await page.locator(`[data-review-id="${reviewId}"]`).waitFor({ timeout: 5_000 });

  await post(`/internal/reviews/${reviewId}/approve`);
  await post(`/internal/changesets/${changeSetId}/merge`);
  await waitForSql(
    `select id from change_sets where id = '${changeSetId.replaceAll("'", "''")}' and status = 'merged'`,
  );
  await page
    .locator(`[data-change-set-id="${changeSetId}"]`)
    .getByText("merged", { exact: true })
    .waitFor({ timeout: 5_000 });

  assert.equal(await page.locator("[data-live-connection]").textContent(), "Live control plane");
  process.stdout.write(
    `AC-23 PASS: open Project page observed six authoritative transitions without manual reload for ${project.id}\n`,
  );
} finally {
  await browser.close();
}
