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 timeoutMs = Number(process.env.AWP_GOLIVE_TIMEOUT_MS ?? "120000");
const { projectId, planRevisionId, factoryRunId, taskIds } = approvedJourneyEnvironment();
const sql = postgres(databaseUrl, { max: 1 });

type TimingRow = {
  prerequisite_id: string;
  dependent_id: string;
  completed_at: Date | null;
  dispatched_at: Date | null;
};

try {
  const edges = await sql<{ task_id: string; prerequisite_task_id: string }[]>`
    select d.task_id, d.prerequisite_task_id
    from task_dependencies d
    join tasks dependent on dependent.id = d.task_id
    join tasks prerequisite on prerequisite.id = d.prerequisite_task_id
    where dependent.project_id = ${projectId}
      and prerequisite.project_id = ${projectId}
      and dependent.plan_revision_id = ${planRevisionId}
      and prerequisite.plan_revision_id = ${planRevisionId}
    order by dependent.position
  `;
  assert.deepEqual(
    edges,
    [
      { task_id: taskIds[1], prerequisite_task_id: taskIds[0] },
      { task_id: taskIds[2], prerequisite_task_id: taskIds[1] },
    ],
    "canonical journey must retain exactly task2->task1 and task3->task2",
  );

  const deadline = Date.now() + timeoutMs;
  let timings: TimingRow[] = [];
  while (Date.now() < deadline) {
    timings = await sql<TimingRow[]>`
      with exact_edges(prerequisite_id, dependent_id) as (
        values (${taskIds[0]}::text, ${taskIds[1]}::text),
               (${taskIds[1]}::text, ${taskIds[2]}::text)
      )
      select
        edge.prerequisite_id,
        edge.dependent_id,
        (
          select min(event.occurred_at)
          from change_sets cs
          join business_events event
            on event.type = 'MergeCompleted'
           and event.aggregate_type = 'ChangeSet'
           and event.aggregate_id = cs.id
          join attempts attempt on attempt.id = cs.producer_attempt_id
          join agent_runs run on run.id = attempt.agent_run_id
          where cs.task_id = edge.prerequisite_id
            and cs.project_id = ${projectId}
            and run.factory_run_id = ${factoryRunId}
        ) as completed_at,
        (
          select min(event.occurred_at)
          from business_events event
          join agent_runs run
            on run.id = event.aggregate_id
           and event.aggregate_type = 'AgentRun'
          where event.type = 'AgentRunQueued'
            and event.project_id = ${projectId}
            and run.factory_run_id = ${factoryRunId}
            and run.task_id = edge.dependent_id
            and event.payload->>'taskId' = edge.dependent_id
        ) as dispatched_at
      from exact_edges edge
      order by edge.prerequisite_id = ${taskIds[1]}
    `;
    if (
      timings.length === 2 &&
      timings.every((row) => row.completed_at !== null && row.dispatched_at !== null)
    ) {
      break;
    }
    await delay(100);
  }

  assert.equal(
    timings.length,
    2,
    "both canonical dependency edges must have durable timing evidence",
  );
  for (const row of timings) {
    assert.ok(row.completed_at, `missing completion event for ${row.prerequisite_id}`);
    assert.ok(row.dispatched_at, `missing dispatch event for ${row.dependent_id}`);
    assert.ok(
      row.completed_at.getTime() <= row.dispatched_at.getTime(),
      `${row.dependent_id} dispatched before ${row.prerequisite_id} completed`,
    );
  }
  process.stdout.write(
    `AC-13 PASS: exact FactoryRun ${factoryRunId} retained durable completion-before-dispatch evidence for both dependency edges\n`,
  );
} finally {
  await sql.end({ timeout: 5 });
}
