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

import { loginBrowser, requireOperatorPassword } from "./auth.js";
import { approvedJourneyEnvironment, requiredEnvironment } from "./exact-journey.js";

const webUrl = requiredEnvironment(process.env, "AWP_WEB_URL");
const databaseUrl = requiredEnvironment(process.env, "AWP_TEST_POSTGRES_URL");
const playwrightModule = requiredEnvironment(process.env, "AWP_PLAYWRIGHT_MODULE");
const operatorPassword = requireOperatorPassword();
const { factoryRunId, firstTaskId } = approvedJourneyEnvironment();
const deadline = Date.now() + Number(process.env.AWP_GOLIVE_TIMEOUT_MS ?? "120000");

type AttemptRow = {
  agent_run_id: string;
  project_id: string;
  attempt_id: string;
  workspace_id: string;
  status: string;
  reason: string | null;
  provider_id: string | null;
  selection_kind: string | null;
  previous_attempt_id: string | null;
  reference_provider_id: string | null;
  reference_resource_type: string | null;
  reference_native_id: string | null;
  reference_native_revision: string | null;
};

const sql = postgres(databaseUrl, { max: 1 });
let attempts: AttemptRow[] = [];
while (Date.now() < deadline) {
  attempts = await sql<AttemptRow[]>`
    select
      ar.id as agent_run_id,
      fr.project_id,
      a.id as attempt_id,
      a.workspace_id,
      a.status,
      a.reason,
      a.provider_id,
      a.selection_provenance->>'kind' as selection_kind,
      a.selection_provenance->>'previousAttemptId' as previous_attempt_id,
      a.provider_reference->>'providerId' as reference_provider_id,
      a.provider_reference->>'resourceType' as reference_resource_type,
      a.provider_reference->>'nativeId' as reference_native_id,
      a.provider_reference->>'nativeRevision' as reference_native_revision
    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
    where fr.id = ${factoryRunId}
      and fr.task_id = ${firstTaskId}
    order by a.created_at, a.id
  `;
  if (
    attempts.length === 2 &&
    attempts[0]?.status === "terminal" &&
    ["running", "terminal"].includes(attempts[1]?.status ?? "")
  ) {
    break;
  }
  await delay(500);
}

assert.equal(
  attempts.length,
  2,
  "the exact first-Task AgentRun must retain exactly the injected initial Attempt and one retry Attempt",
);
const [initial, retry] = attempts as [AttemptRow, AttemptRow];
assert.equal(initial.agent_run_id, retry.agent_run_id);
assert.equal(initial.project_id, retry.project_id);
assert.equal(
  initial.workspace_id,
  retry.workspace_id,
  "retry must preserve the same durable Workspace",
);
assert.equal(initial.status, "terminal");
assert.equal(initial.reason, "injected non-zero agent exit 17");
assert.equal(initial.selection_kind, "initial");
assert.equal(initial.previous_attempt_id, null);
assert.match(retry.status, /^(running|terminal)$/u);
assert.equal(retry.selection_kind, "retry");
assert.equal(retry.previous_attempt_id, initial.attempt_id);
for (const attempt of attempts) {
  assert.equal(attempt.provider_id, "provider:acp");
  assert.equal(attempt.reference_provider_id, "provider:acp");
  assert.equal(attempt.reference_resource_type, "agent-session");
  assert.ok(attempt.reference_native_id, "native ACP session id must be persisted");
  assert.ok(attempt.reference_native_revision, "native ACP session revision must be persisted");
}
assert.notEqual(
  initial.reference_native_id,
  retry.reference_native_id,
  "retry must be represented by a distinct native ACP session",
);

const replacementEvents = await sql<
  {
    previous_attempt_id: string | null;
    agent_run_id: string | null;
    task_id: string | null;
    workspace_id: string | null;
    immutable_image_digest: string | null;
    provider_native_revision: string | null;
    reconciliation_token: string | null;
  }[]
>`
  select
    payload->>'previousAttemptId' as previous_attempt_id,
    payload->>'agentRunId' as agent_run_id,
    payload->>'taskId' as task_id,
    payload->>'workspaceId' as workspace_id,
    payload->'workspaceProviderDetails'->>'immutableImageDigest' as immutable_image_digest,
    payload->'workspaceProviderReferences'->0->>'nativeRevision' as provider_native_revision,
    payload->>'replacementReconciliationToken' as reconciliation_token
  from business_events
  where type = 'RetryComputeReplaced'
    and aggregate_type = 'Attempt'
    and aggregate_id = ${retry.attempt_id}
    and project_id = ${retry.project_id}
  order by occurred_at, id
`;
assert.equal(
  replacementEvents.length,
  1,
  "retry must have one durable replacement-compute receipt",
);
const replacement = replacementEvents[0]!;
assert.equal(replacement.previous_attempt_id, initial.attempt_id);
assert.equal(replacement.agent_run_id, retry.agent_run_id);
assert.equal(replacement.task_id, firstTaskId);
assert.equal(replacement.workspace_id, retry.workspace_id);
assert.equal(replacement.immutable_image_digest, "true");
assert.ok(
  replacement.provider_native_revision,
  "replacement Pod resource revision must be durable",
);
assert.ok(replacement.reconciliation_token, "replacement reconciliation token must be durable");
await sql.end({ timeout: 5 });

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}/projects/${encodeURIComponent(retry.project_id)}`, {
    waitUntil: "networkidle",
  });
  const run = page.locator(`[data-agent-run-id="${retry.agent_run_id}"]`);
  await run.getByText("Attempt 1", { exact: true }).waitFor();
  await run.getByText("Attempt 2", { exact: true }).waitFor();
  await run.getByText("injected non-zero agent exit 17", { exact: true }).first().waitFor();
  await run.getByText("retry", { exact: true }).first().waitFor();
  assert.equal(await run.locator("[data-attempt-id]").count(), 2);
  process.stdout.write(
    `AC-17 PASS: exact FactoryRun ${factoryRunId} first-Task AgentRun ${retry.agent_run_id} retains exactly two native ACP Attempts plus durable replacement-compute evidence\n`,
  );
} finally {
  await browser.close();
}
