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

import {
  createOperatorSession,
  loginBrowser,
  operatorHeaders,
  requireOperatorPassword,
} from "./auth.js";
import { approvedJourneyEnvironment, requiredEnvironment } from "./exact-journey.js";
import { authoritativeRemoteHead } from "./repository-identity.js";

const webUrl = requiredEnvironment(process.env, "AWP_WEB_URL");
const controlPlaneUrl = requiredEnvironment(process.env, "AWP_CONTROL_PLANE_URL");
const databaseUrl = requiredEnvironment(process.env, "AWP_TEST_POSTGRES_URL");
const repository = requiredEnvironment(process.env, "AWP_REPOSITORY_CHECKOUT_PATH");
const defaultBranch = requiredEnvironment(process.env, "AWP_REPOSITORY_DEFAULT_BRANCH");
const playwrightModule = requiredEnvironment(process.env, "AWP_PLAYWRIGHT_MODULE");
const operatorPassword = requireOperatorPassword();
const sessionToken = await createOperatorSession(controlPlaneUrl, operatorPassword);
const { factoryRunId, firstTaskId, planRevisionId, taskIds } = approvedJourneyEnvironment();
const timeoutMs = Number(process.env.AWP_GOLIVE_TIMEOUT_MS ?? "120000");

type PendingReviewRow = {
  change_set_id: string;
  project_id: string;
  review_id: string;
  reviewer_principal_id: string;
  reviewer_agent_run_id: string;
  reviewer_role: string;
  producer_agent_run_id: string;
  producer_principal_id: string;
  producer_role: string;
};

const sql = postgres(databaseUrl, { max: 1 });
const deadline = Date.now() + timeoutMs;
let rows: PendingReviewRow[] = [];
while (Date.now() < deadline) {
  rows = await sql<PendingReviewRow[]>`
    select
      cs.id as change_set_id,
      cs.project_id,
      r.id as review_id,
      r.reviewer_principal_id,
      r.reviewer_agent_run_id,
      rr.role as reviewer_role,
      ar.id as producer_agent_run_id,
      ar.agent_principal_id as producer_principal_id,
      ar.role as producer_role
    from factory_runs fr
    join agent_runs ar
      on ar.factory_run_id = fr.id
     and ar.task_id = fr.task_id
    join attempts a on a.agent_run_id = ar.id
    join change_sets cs
      on cs.producer_attempt_id = a.id
     and cs.task_id = ar.task_id
     and cs.project_id = fr.project_id
    join reviews r on r.change_set_id = cs.id
    join agent_runs rr
      on rr.id = r.reviewer_agent_run_id
     and rr.factory_run_id = fr.id
     and rr.task_id = cs.task_id
    where fr.id = ${factoryRunId}
      and fr.plan_revision_id = ${planRevisionId}
      and fr.task_id = ${firstTaskId}
      and cs.status in ('reviewing', 'verifying')
      and r.status in ('requested', 'reviewing')
    order by cs.created_at, cs.id, r.created_at, r.id
  `;
  if (rows.length === 1) break;
  if (rows.length > 1) break;
  await delay(250);
}
assert.equal(rows.length, 1, "the exact first Task must have one pending independent Review");
const row = rows[0]!;
assert.equal(row.reviewer_role, "reviewer");
assert.equal(row.producer_role, "coder");
assert.notEqual(row.reviewer_agent_run_id, row.producer_agent_run_id);
assert.notEqual(row.reviewer_principal_id, row.producer_principal_id);

const [approvalBefore] = await sql<{ count: number }[]>`
  select count(*)::int as count from approvals
`;

const refused = await fetch(
  `${controlPlaneUrl}/internal/changesets/${encodeURIComponent(row.change_set_id)}/merge`,
  { method: "POST", headers: operatorHeaders(sessionToken) },
);
assert.equal(refused.status, 409);
assert.equal(((await refused.json()) as { error: string }).error, "merge-refused");

const { chromium } = await import(playwrightModule);
const browser = await chromium.launch({ headless: true });
try {
  const page = await browser.newPage();
  await loginBrowser(page, webUrl, operatorPassword);
  await page.goto(
    `${webUrl}/changesets/${encodeURIComponent(row.change_set_id)}?project=${encodeURIComponent(row.project_id)}`,
    { waitUntil: "networkidle" },
  );
  assert.equal(await page.getByRole("button", { name: "Approve ChangeSet" }).count(), 0);
  assert.equal(await page.locator('[data-command="merge"]').count(), 0);
  assert.equal(await page.locator('[data-command="refresh-required-checks"]').count(), 0);
  await page.getByText("Trusted Merge completed successfully", { exact: false }).waitFor({
    timeout: timeoutMs,
  });

  const persisted = await sql<
    { change_set_status: string; review_status: string; disposition: string | null }[]
  >`
    select cs.status as change_set_status, r.status as review_status, r.disposition
    from change_sets cs
    join reviews r on r.change_set_id = cs.id
    where cs.id = ${row.change_set_id}
      and r.id = ${row.review_id}
  `;
  assert.deepEqual(persisted, [
    {
      change_set_status: "merged",
      review_status: "submitted",
      disposition: "approved",
    },
  ]);

  const [approvalAfter] = await sql<{ count: number }[]>`
    select count(*)::int as count from approvals
  `;
  assert.equal(
    approvalAfter?.count,
    approvalBefore?.count,
    "default full-chain trusted Merge must not create an Approval row",
  );

  const taskStates = await sql<{ id: string; status: string }[]>`
    select id, status
    from tasks
    where plan_revision_id = ${planRevisionId}
    order by position
  `;
  assert.deepEqual(
    taskStates,
    [
      { id: taskIds[0], status: "completed" },
      { id: taskIds[1], status: "dispatched" },
      { id: taskIds[2], status: "blocked" },
    ],
    "first trusted Merge must complete Task 1 and automatically dispatch Task 2 only",
  );

  const mergeIdentity = await sql<
    { merge_reference: unknown; resulting_revision: string | null }[]
  >`
    select merge_reference, resulting_revision
    from change_sets
    where id = ${row.change_set_id}
      and project_id = ${row.project_id}
  `;
  assert.equal(mergeIdentity.length, 1);
  assert.ok(mergeIdentity[0]?.merge_reference, "trusted merge reference must be durable");
  assert.ok(mergeIdentity[0]?.resulting_revision, "trusted resulting revision must be durable");

  const remote = execFileSync("git", ["remote", "get-url", "origin"], {
    cwd: repository,
    encoding: "utf8",
  }).trim();
  const remoteHead = authoritativeRemoteHead(
    execFileSync("git", ["ls-remote", "--symref", remote, "HEAD"], { encoding: "utf8" }),
  );
  assert.equal(remoteHead.branchRef, `refs/heads/${defaultBranch}`);
  assert.equal(remoteHead.oid, mergeIdentity[0]!.resulting_revision);
  process.stdout.write(
    `AC-19..AC-21 PASS: exact ChangeSet ${row.change_set_id} refused premature Merge, independent reviewer ${row.reviewer_agent_run_id} approved it, and the control plane auto-merged to ${remoteHead.oid} with no Approval-row delta\n`,
  );
} finally {
  await browser.close();
  await sql.end({ timeout: 5 });
}
