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 { controlPlaneServiceRuntime, killAndRestartControlPlane } from "./control-plane-service.js";
import { approvedJourneyEnvironment, requiredEnvironment } from "./exact-journey.js";
import { authoritativeRemoteHead, canonicalGitHubRepository } from "./repository-identity.js";

const databaseUrl = requiredEnvironment(process.env, "AWP_TEST_POSTGRES_URL");
const repository = requiredEnvironment(process.env, "AWP_REPOSITORY_CHECKOUT_PATH");
const repositoryUrl = requiredEnvironment(process.env, "AWP_REPOSITORY_REMOTE_URL");
const defaultBranch = requiredEnvironment(process.env, "AWP_REPOSITORY_DEFAULT_BRANCH");
const runtime = controlPlaneServiceRuntime();
const { factoryRunId, taskIds } = approvedJourneyEnvironment();
const restartTaskId = taskIds[1];

const sql = postgres(databaseUrl, { max: 1 });
try {
  const candidateDeadline = Date.now() + runtime.timeoutMs;
  let candidates: Array<{ change_set_id: string; review_id: string }> = [];
  while (Date.now() < candidateDeadline) {
    candidates = await sql<{ change_set_id: string; review_id: string }[]>`
      select cs.id as change_set_id, r.id as review_id
      from factory_runs fr
      join projects p on p.id = fr.project_id
      join agent_runs ar
        on ar.factory_run_id = fr.id
       and ar.task_id = ${restartTaskId}
      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
      where fr.id = ${factoryRunId}
        and p.repository_url = ${repositoryUrl}
        and cs.status in ('reviewing', 'verifying', 'ready-to-merge')
        and r.status in ('requested', 'reviewing', 'submitted')
      order by cs.created_at, cs.id, r.created_at, r.id
    `;
    if (candidates.length > 0) break;
    await delay(100);
  }
  assert.equal(
    candidates.length,
    1,
    "canonical Task 2 must have exactly one published non-merged ChangeSet before AC-27 restart",
  );
  const candidate = candidates[0]!;

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

  const restart = await killAndRestartControlPlane(runtime);

  const reviewDeadline = Date.now() + runtime.timeoutMs;
  let mergedAutomatically = false;
  while (Date.now() < reviewDeadline) {
    const states = await sql<
      {
        change_set_status: string;
        review_status: string;
        disposition: string | null;
        reviewer_role: string | null;
      }[]
    >`
      select cs.status as change_set_status, r.status as review_status, r.disposition, rr.role as reviewer_role
      from change_sets cs
      join reviews r on r.change_set_id = cs.id
      left join agent_runs rr on rr.id = r.reviewer_agent_run_id
      where cs.id = ${candidate.change_set_id}
        and r.id = ${candidate.review_id}
    `;
    if (
      states.length === 1 &&
      states[0]?.change_set_status === "merged" &&
      states[0].review_status === "submitted" &&
      states[0].disposition === "approved" &&
      states[0].reviewer_role === "reviewer"
    ) {
      mergedAutomatically = true;
      break;
    }
    await delay(100);
  }
  assert.ok(
    mergedAutomatically,
    "independent review/check reconciliation must resume and auto-merge across control-plane restart",
  );

  const [approvalAfter] = await sql<{ count: number }[]>`
    select count(*)::int as count from approvals
  `;
  assert.equal(
    approvalAfter?.count,
    approvalBefore?.count,
    "default restart convergence must not invent an Approval gate",
  );

  const mergeIdentity = await sql<
    { merge_reference: unknown; resulting_revision: string | null }[]
  >`
    select merge_reference, resulting_revision
    from change_sets
    where id = ${candidate.change_set_id}
      and task_id = ${restartTaskId}
  `;
  assert.equal(mergeIdentity.length, 1);
  assert.ok(mergeIdentity[0]?.merge_reference);
  assert.ok(mergeIdentity[0]?.resulting_revision);

  const [mergeEventCount] = await sql<{ count: number }[]>`
    select count(*)::int as count
    from business_events
    where type = 'MergeCompleted'
      and aggregate_type = 'ChangeSet'
      and aggregate_id = ${candidate.change_set_id}
  `;
  assert.equal(
    mergeEventCount?.count,
    1,
    "restart convergence must complete trusted Merge exactly once",
  );

  const remote = execFileSync("git", ["remote", "get-url", "origin"], {
    cwd: repository,
    encoding: "utf8",
  }).trim();
  assert.equal(canonicalGitHubRepository(remote), canonicalGitHubRepository(repositoryUrl));
  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-27 PASS: deployed control plane ${runtime.service} restarted ${restart.priorPid}->${restart.replacementPid} before canonical Task 2 merge; review/check reconciliation merged ChangeSet ${candidate.change_set_id} exactly once at ${remoteHead.oid} with no Approval-row delta\n`,
  );
} finally {
  await sql.end({ timeout: 5 });
}
