import assert from "node:assert/strict";
import { setTimeout as delay } from "node:timers/promises";
import postgres from "postgres";
import { approvedJourneyEnvironment, requiredEnvironment } from "./exact-journey.js";

const databaseUrl = requiredEnvironment(process.env, "AWP_TEST_POSTGRES_URL");
const repositoryUrl = requiredEnvironment(process.env, "AWP_REPOSITORY_REMOTE_URL");
const accountId = requiredEnvironment(process.env, "AWP_AGENT_ACCOUNT_ID");
const model = requiredEnvironment(process.env, "AWP_AGENT_MODEL");
const { projectId, planId, planRevisionId, factoryRunId, taskIds } = approvedJourneyEnvironment();
const timeoutMs = Number(process.env.AWP_GOLIVE_TIMEOUT_MS ?? "240000");

const sql = postgres(databaseUrl, { max: 1 });
try {
  const projects = await sql<{ repository_url: string }[]>`
    select repository_url from projects where id = ${projectId}
  `;
  assert.deepEqual(projects, [{ repository_url: repositoryUrl }]);

  const factoryRuns = await sql<{ id: string; account_id: string; model: string }[]>`
    select id, account_id, model
    from factory_runs
    where project_id = ${projectId}
      and plan_revision_id = ${planRevisionId}
    order by id
  `;
  assert.deepEqual(factoryRuns, [{ id: factoryRunId, account_id: accountId, model }]);

  const deadline = Date.now() + timeoutMs;
  let completed = false;
  while (Date.now() < deadline) {
    const planRows = await sql<{ status: string }[]>`
      select status
      from plans
      where id = ${planId}
        and project_id = ${projectId}
    `;
    const taskRows = await sql<{ id: string; status: string }[]>`
      select id, status
      from tasks
      where project_id = ${projectId}
        and plan_revision_id = ${planRevisionId}
      order by position
    `;
    const runRows = await sql<{ status: string }[]>`
      select status from factory_runs where id = ${factoryRunId}
    `;
    if (
      planRows.length === 1 &&
      planRows[0]?.status === "completed" &&
      runRows.length === 1 &&
      runRows[0]?.status === "completed" &&
      taskRows.length === 3 &&
      taskRows.every((task) => task.status === "completed")
    ) {
      assert.deepEqual(
        taskRows.map((task) => task.id),
        [...taskIds],
        "terminal Plan must retain the exact three owner-authored Tasks in order",
      );
      completed = true;
      break;
    }
    await delay(100);
  }
  assert.ok(completed, `AC-22 timed out completing canonical Plan ${planId}`);

  const dispatchEvidence = await sql<{ task_id: string; dispatch_events: number }[]>`
    select ar.task_id, count(event.id)::int as dispatch_events
    from agent_runs ar
    join business_events event
      on event.aggregate_type = 'AgentRun'
     and event.aggregate_id = ar.id
     and event.type = 'AgentRunQueued'
     and event.payload->>'taskId' = ar.task_id
    where ar.factory_run_id = ${factoryRunId}
    group by ar.task_id
    order by min(ar.created_at), ar.task_id
  `;
  assert.deepEqual(
    dispatchEvidence.map((row) => row.task_id),
    [...taskIds],
    "every canonical Task must have durable dispatch evidence under the same FactoryRun",
  );
  assert.ok(
    dispatchEvidence.every((row) => row.dispatch_events === 1),
    "each canonical Task must be dispatched exactly once",
  );

  const mergedChangeSets = await sql<{ task_id: string; count: number }[]>`
    select cs.task_id, count(cs.id)::int as count
    from change_sets cs
    join attempts a on a.id = cs.producer_attempt_id
    join agent_runs ar on ar.id = a.agent_run_id
    where ar.factory_run_id = ${factoryRunId}
      and cs.status = 'merged'
    group by cs.task_id
    order by min(cs.created_at), cs.task_id
  `;
  assert.deepEqual(
    mergedChangeSets.map((row) => row.task_id),
    [...taskIds],
    "all three canonical Tasks must terminate through trusted merged ChangeSets",
  );
  assert.ok(
    mergedChangeSets.every((row) => row.count === 1),
    "each canonical Task must contribute exactly one merged ChangeSet",
  );

  process.stdout.write(
    `AC-22 PASS: canonical Plan ${planId} completed Tasks ${taskIds.join(",")} through one FactoryRun ${factoryRunId}, one durable dispatch and one trusted merged ChangeSet per Task\n`,
  );
} finally {
  await sql.end({ timeout: 5 });
}
