import assert from "node:assert/strict";
import { execFileSync } from "node:child_process";
import { randomUUID } from "node:crypto";
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 kubeconfig = requiredEnvironment(process.env, "AWP_KUBECONFIG");
const workspaceImage = requiredEnvironment(process.env, "AWP_WORKSPACE_IMAGE");
const cleanWorkerNode = requiredEnvironment(process.env, "AWP_CLEAN_WORKER_NODE");
const namespace = process.env.AWP_WORKSPACE_NAMESPACE ?? "awp-workspaces";
const { factoryRunId, firstTaskId } = approvedJourneyEnvironment();
assert.match(workspaceImage, /@sha256:[0-9a-f]{64}$/iu, "runner image must be digest pinned");
const workspaceDigest = workspaceImage.slice(workspaceImage.indexOf("@sha256:") + 1);

function kubectl(args: string[], input?: string): string {
  return execFileSync("kubectl", ["--kubeconfig", kubeconfig, ...args], {
    encoding: "utf8",
    ...(input === undefined ? {} : { input }),
  }).trim();
}

function cleanWorkerImages(): readonly string[] {
  const output = execFileSync(
    "ssh",
    [
      "-o",
      "BatchMode=yes",
      "-o",
      "ConnectTimeout=5",
      cleanWorkerNode,
      "sudo",
      "-n",
      "k3s",
      "ctr",
      "images",
      "list",
      "-q",
    ],
    { encoding: "utf8" },
  );
  return output
    .split(/\r?\n/u)
    .map((value) => value.trim())
    .filter(Boolean);
}

async function proveCleanWorkerPull(): Promise<void> {
  const nodes = JSON.parse(kubectl(["get", "nodes", "-o", "json"])) as {
    items: Array<{
      metadata: { name: string; labels?: Record<string, string> };
      spec?: { unschedulable?: boolean; taints?: Array<{ effect?: string }> };
      status?: { conditions?: Array<{ type?: string; status?: string }> };
    }>;
  };
  const node = nodes.items.find((candidate) => candidate.metadata.name === cleanWorkerNode);
  assert.ok(node, `clean worker ${cleanWorkerNode} must be a K3s node`);
  assert.equal(node.spec?.unschedulable ?? false, false, "clean worker must be schedulable");
  assert.ok(
    node.status?.conditions?.some(
      (condition) => condition.type === "Ready" && condition.status === "True",
    ),
    "clean worker must be Ready",
  );
  assert.equal(
    (node.spec?.taints ?? []).some(
      (taint) => taint.effect === "NoSchedule" || taint.effect === "NoExecute",
    ),
    false,
    "clean worker must not carry a scheduling-blocking taint",
  );
  assert.equal(
    Object.keys(node.metadata.labels ?? {}).some((label) =>
      label.toLowerCase().includes("agent-runner"),
    ),
    false,
    "runner scheduling must not depend on hand-maintained image-readiness labels",
  );
  assert.equal(
    cleanWorkerImages().some(
      (image) => image === workspaceImage || image.includes(workspaceDigest),
    ),
    false,
    `clean worker ${cleanWorkerNode} must not preload ${workspaceImage}`,
  );

  const probeName = `awp-image-pull-${randomUUID().slice(0, 8)}`;
  const probe = {
    apiVersion: "v1",
    kind: "Pod",
    metadata: {
      name: probeName,
      namespace,
      labels: { "awp.dev/purpose": "golive-clean-worker-pull-proof" },
    },
    spec: {
      nodeSelector: { "kubernetes.io/hostname": cleanWorkerNode },
      restartPolicy: "Never",
      automountServiceAccountToken: false,
      imagePullSecrets: [{ name: "awp-ghcr-pull" }],
      containers: [
        {
          name: "probe",
          image: workspaceImage,
          imagePullPolicy: "IfNotPresent",
          command: ["node", "-e", "setInterval(() => {}, 1000)"],
        },
      ],
    },
  };

  try {
    kubectl(["apply", "-f", "-"], `${JSON.stringify(probe)}\n`);
    const deadline = Date.now() + 120_000;
    while (Date.now() < deadline) {
      const current = JSON.parse(
        kubectl(["-n", namespace, "get", "pod", probeName, "-o", "json"]),
      ) as {
        spec?: { nodeName?: string; containers?: Array<{ image?: string }> };
        status?: {
          phase?: string;
          containerStatuses?: Array<{ imageID?: string; ready?: boolean }>;
        };
      };
      if (current.status?.phase === "Running" && current.status.containerStatuses?.[0]?.ready) {
        assert.equal(current.spec?.nodeName, cleanWorkerNode);
        assert.equal(current.spec?.containers?.[0]?.image, workspaceImage);
        assert.ok(
          current.status.containerStatuses[0]?.imageID?.includes(workspaceDigest),
          `clean-worker pull imageID must resolve to ${workspaceDigest}`,
        );
        return;
      }
      await delay(500);
    }
    throw new Error(`clean-worker image pull probe ${probeName} did not reach Running`);
  } finally {
    try {
      kubectl([
        "-n",
        namespace,
        "delete",
        "pod",
        probeName,
        "--ignore-not-found=true",
        "--wait=true",
      ]);
    } catch {
      // Preserve the primary proof failure; Kubernetes cleanup can be reconciled independently.
    }
  }
}

const sql = postgres(databaseUrl, { max: 1 });
const runs = await sql<{ id: string }[]>`
  select ar.id
  from factory_runs fr
  join agent_runs ar
    on ar.factory_run_id = fr.id
   and ar.task_id = fr.task_id
  where fr.id = ${factoryRunId}
    and fr.task_id = ${firstTaskId}
  order by ar.created_at, ar.id
`;
assert.equal(
  runs.length,
  1,
  "the approved journey must have exactly one first-Task AgentRun at AC-12",
);
const agentRunId = runs[0]!.id;
const [attemptCount] = await sql<{ count: number }[]>`
  select count(*)::int as count
  from attempts
  where agent_run_id = ${agentRunId}
    and provider_id = 'provider:acp'
    and account_id is not null
    and model is not null
`;
assert.ok(
  (attemptCount?.count ?? 0) >= 1,
  "the canonical AgentRun must have native ACP provenance",
);
await sql.end({ timeout: 5 });

assert.ok(
  kubectl(["-n", namespace, "get", "secret", "awp-ghcr-pull", "-o", "name"]),
  "workspace namespace must contain the system-scoped registry pull secret",
);
await proveCleanWorkerPull();

const deadline = Date.now() + 120_000;
let podJson = "";
while (Date.now() < deadline) {
  try {
    podJson = kubectl([
      "-n",
      namespace,
      "get",
      "pods",
      "-l",
      `awp.agent-run-id=${agentRunId}`,
      "-o",
      "json",
    ]);
    const parsed = JSON.parse(podJson) as {
      items: Array<{
        metadata: { name: string };
        spec: {
          nodeName?: string;
          automountServiceAccountToken?: boolean;
          imagePullSecrets?: Array<{ name?: string }>;
          containers?: Array<{ image?: string }>;
        };
        status: { phase?: string };
      }>;
    };
    if (parsed.items.length === 1 && parsed.items[0]?.status.phase === "Running") {
      const pod = parsed.items[0];
      assert.ok(pod);
      assert.ok(pod.spec.nodeName, "AgentRun pod must be assigned to a cluster node");
      const clusterNodes = JSON.parse(kubectl(["get", "nodes", "-o", "json"])) as {
        items: Array<{ metadata: { name: string } }>;
      };
      assert.ok(
        clusterNodes.items.some((node) => node.metadata.name === pod.spec.nodeName),
        "AgentRun pod must run on a registered K3s node",
      );
      assert.equal(pod.spec.automountServiceAccountToken, false);
      assert.equal(pod.spec.containers?.[0]?.image, workspaceImage);
      assert.deepEqual(pod.spec.imagePullSecrets, [{ name: "awp-ghcr-pull" }]);
      assert.match(pod.metadata.name, /^awp-/u);
      process.stdout.write(
        `AC-12 PASS: clean worker ${cleanWorkerNode} pulled ${workspaceImage} normally; exact FactoryRun ${factoryRunId} first Task ${firstTaskId} runs as AgentRun ${agentRunId} in digest-pinned pod ${pod.metadata.name} on cluster node ${pod.spec.nodeName}\n`,
      );
      process.exit(0);
    }
  } catch {
    // Kubernetes may not have observed the newly committed AgentRun yet.
  }
  await delay(500);
}

throw new Error(
  `AC-12 timed out waiting for exactly one Running pod labelled awp.agent-run-id=${agentRunId}: ${podJson}`,
);
