import { readdirSync, readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { sql } from "drizzle-orm";
import { createPgliteClient } from "@platform-modules/db/postgres/pglite";
import { describe, expect, it } from "vitest";
import {
  MachineEnrollmentService,
  type ClusterNodeAdapter,
  type Clock,
  type IdGenerator,
  type MachineHostAdapter,
  type SecretStore,
} from "@awp/application";
import {
  authorityContext,
  unsafeOpaqueId,
  type AwpId,
  type ClusterId,
  type ConnectionId,
  type CorrelationId,
  type CredentialReference,
  type CredentialReferenceId,
  type MachinePreflightReport,
  type OperationId,
  type PrincipalId,
  type ProviderId,
} from "@awp/contracts";
import { PostgresUnitOfWork, schema } from "@awp/persistence";
import { classifyK3sPreflightFacts } from "../../packages/providers/machine-ssh/src/machine-ssh.js";

const migrationDirectory = fileURLToPath(
  new URL("../../packages/persistence/drizzle/", import.meta.url),
);

async function database() {
  const db = createPgliteClient({ schema });
  for (const name of readdirSync(migrationDirectory)
    .filter((name) => name.endsWith(".sql"))
    .sort()) {
    for (const statement of readFileSync(`${migrationDirectory}/${name}`, "utf8").split(
      "--> statement-breakpoint",
    )) {
      const sqlText = statement.trim();
      if (sqlText) await db.execute(sql.raw(sqlText));
    }
  }
  return db;
}

const clock: Clock = { now: () => new Date("2026-08-24T16:00:00.000Z") };
function ids(): IdGenerator {
  let next = 0;
  return {
    next<T extends AwpId>(): T {
      next += 1;
      return unsafeOpaqueId<T>(`i7a-${next}`);
    },
  };
}
function context(operation = "operation:i7a") {
  return {
    operationId: unsafeOpaqueId<OperationId>(operation),
    correlationId: unsafeOpaqueId<CorrelationId>(`correlation:${operation}`),
    idempotencyKey: operation,
    authority: authorityContext(
      { id: unsafeOpaqueId<PrincipalId>("principal:owner"), kind: "human", capabilities: [] },
      [],
    ),
  };
}

const clusterId = unsafeOpaqueId<ClusterId>("cluster:dogfood");
const sshConnectionId = unsafeOpaqueId<ConnectionId>("connection:machine-enrollment-ssh");
const sshCredentialId = unsafeOpaqueId<CredentialReferenceId>("credential:machine-enrollment-ssh");
const joinCredentialId = unsafeOpaqueId<CredentialReferenceId>("credential:k3s-node-token");

async function seed(uow: PostgresUnitOfWork) {
  await uow.transaction(async (tx) => {
    await tx.credentialReferences.upsert({ id: sshCredentialId, secretStoreKey: "ssh-key" });
    await tx.credentialReferences.upsert({ id: joinCredentialId, secretStoreKey: "join-token" });
    await tx.connections.upsert({
      id: sshConnectionId,
      providerId: unsafeOpaqueId<ProviderId>("provider:ssh-k3s"),
      credentialReferenceId: sshCredentialId,
      status: "connected",
      capabilities: [],
      resources: [clusterId],
    });
    await tx.clusters.insert({
      id: clusterId,
      name: "Dogfood",
      apiServerUrl: "https://100.101.104.41:6443",
      joinCredentialReferenceId: joinCredentialId,
      status: "active",
      revision: 1,
    });
  });
}

class FakeSecrets implements SecretStore {
  disposed = 0;
  readonly values = new Map<string, string>([
    ["ssh-key", "PRIVATE-KEY-SENTINEL"],
    ["join-token", "JOIN-TOKEN-SENTINEL"],
  ]);
  async resolve(reference: CredentialReference) {
    const value = this.values.get(reference.secretStoreKey);
    if (!value) throw new Error("missing test secret");
    return {
      value,
      dispose: () => {
        this.disposed += 1;
      },
    };
  }
  async rotate(): Promise<CredentialReference> {
    throw new Error("not used");
  }
}

function report(state: MachinePreflightReport["state"]): MachinePreflightReport {
  return {
    reachable: true,
    hostname: "debian4",
    osId: "debian",
    osVersion: "14",
    architecture: "x86_64",
    cpuCount: 8,
    memoryBytes: 16 * 1024 ** 3,
    diskFreeBytes: 100 * 1024 ** 3,
    hasK3s: state !== "clean",
    ...(state === "already-enrolled"
      ? {
          k3sRole: "agent" as const,
          k3sServerUrl: "https://100.101.104.41:6443",
          kubernetesNodeName: "debian4",
        }
      : {}),
    state,
    blockingReasons: ["foreign", "partial", "unsupported"].includes(state)
      ? [`blocked:${state}`]
      : [],
    observedAt: "2026-08-24T16:00:00.000Z",
    fingerprint: "f".repeat(64),
  };
}

class FakeHost implements MachineHostAdapter {
  ensures = 0;
  constructor(readonly result: MachinePreflightReport) {}
  async preflight() {
    return this.result;
  }
  async ensureK3sAgent(input: Parameters<MachineHostAdapter["ensureK3sAgent"]>[0]) {
    this.ensures += 1;
    expect(input.credential.value).toBe("PRIVATE-KEY-SENTINEL");
    expect(input.joinToken).toBe("JOIN-TOKEN-SENTINEL");
    return { nodeName: "debian4" };
  }
}
class FakeNode implements ClusterNodeAdapter {
  waits = 0;
  async waitForReady() {
    this.waits += 1;
    return {
      nodeName: "debian4",
      ready: true,
      observedAt: "2026-08-24T16:01:00.000Z",
      capabilities: [
        { key: "architecture", value: "amd64" },
        { key: "capacity/cpu", value: "8" },
      ],
    };
  }
}

async function requested(service: MachineEnrollmentService, operation = "operation:i7a") {
  return service.request({
    clusterId,
    name: "debian4",
    address: "100.64.0.44",
    sshUser: "user",
    sshConnectionId,
    sshCredentialReferenceId: sshCredentialId,
    labels: { ci: "true" },
    context: context(operation),
  });
}

describe("I7a machine enrollment", () => {
  it("is idempotent, persists only credential references, joins once, verifies Ready and discovers capabilities", async () => {
    const db = await database();
    const uow = new PostgresUnitOfWork(db);
    await seed(uow);
    const secrets = new FakeSecrets();
    const host = new FakeHost(report("clean"));
    const node = new FakeNode();
    const service = new MachineEnrollmentService(uow, ids(), clock, secrets, host, node);

    const first = await requested(service);
    const replay = await requested(service);
    expect(replay.id).toBe(first.id);
    expect(await uow.transaction((tx) => tx.machines.listByCluster(clusterId))).toHaveLength(1);

    const preflight = await service.preflight(first.id);
    expect(preflight.phase).toBe("awaiting-confirmation");
    expect(preflight.preflight?.state).toBe("clean");
    const complete = await service.apply(first.id);
    expect(complete.phase).toBe("completed");
    expect(host.ensures).toBe(1);
    expect(node.waits).toBe(1);
    expect((await service.capabilities(first.machineId)).map((item) => item.key)).toEqual([
      "architecture",
      "capacity/cpu",
    ]);
    await service.apply(first.id);
    expect(host.ensures).toBe(1);
    expect(node.waits).toBe(1);
    expect(secrets.disposed).toBe(3);

    const raw = JSON.stringify({
      machines: await db.select().from(schema.machines),
      enrollments: await db.select().from(schema.machineEnrollments),
      references: await db.select().from(schema.credentialReferences),
    });
    expect(raw).not.toContain("PRIVATE-KEY-SENTINEL");
    expect(raw).not.toContain("JOIN-TOKEN-SENTINEL");
    expect(raw).toContain("credential:machine-enrollment-ssh");
    expect(raw).toContain("credential:k3s-node-token");
    await db.$client.close();
  }, 20_000);

  it.each(["foreign", "partial", "unsupported"] as const)(
    "blocks %s preflight without mutation",
    async (state) => {
      const db = await database();
      const uow = new PostgresUnitOfWork(db);
      await seed(uow);
      const host = new FakeHost(report(state));
      const service = new MachineEnrollmentService(
        uow,
        ids(),
        clock,
        new FakeSecrets(),
        host,
        new FakeNode(),
      );
      const enrollment = await requested(service, `operation:${state}`);
      const checked = await service.preflight(enrollment.id);
      expect(checked.phase).toBe("failed");
      expect(checked.preflight?.state).toBe(state);
      await expect(service.apply(enrollment.id)).rejects.toThrow();
      expect(host.ensures).toBe(0);
      await db.$client.close();
    },
    20_000,
  );

  it("resumes the same durable enrollment after a preflight interruption and a new operation id", async () => {
    const db = await database();
    const uow = new PostgresUnitOfWork(db);
    await seed(uow);
    const secrets = new FakeSecrets();
    const service = new MachineEnrollmentService(
      uow,
      ids(),
      clock,
      secrets,
      new FakeHost(report("clean")),
      new FakeNode(),
    );

    const first = await requested(service, "operation:first-browser-submit");
    await uow.transaction(async (tx) => {
      const current = await tx.machineEnrollments.getById(first.id);
      expect(current).toBeDefined();
      await tx.machineEnrollments.update({
        ...current!,
        phase: "preflight",
        updatedAt: "2026-08-24T16:00:01.000Z",
        revision: current!.revision + 1,
      });
    });

    const resumed = await requested(service, "operation:browser-retry-after-restart");
    expect(resumed.id).toBe(first.id);
    expect(resumed.operationId).toBe(first.operationId);
    expect(await uow.transaction((tx) => tx.machines.listByCluster(clusterId))).toHaveLength(1);

    secrets.values.delete("ssh-key");
    const failed = await service.preflight(first.id);
    expect(failed.phase).toBe("failed");
    expect(failed.failureReason).toBe("missing test secret");

    secrets.values.set("ssh-key", "PRIVATE-KEY-SENTINEL");
    const recovered = await service.preflight(first.id);
    expect(recovered.phase).toBe("awaiting-confirmation");
    expect(recovered.preflight?.state).toBe("clean");
    await db.$client.close();
  }, 20_000);

  it("classifies clean, same-cluster, foreign and partial K3s states deterministically", () => {
    const base = {
      sudo_ok: "1",
      curl_ok: "1",
      systemd_ok: "1",
      tailscale_ok: "1",
      cluster_api_ok: "1",
      os_id: "debian",
      arch: "x86_64",
      cpu: "8",
      mem: String(16 * 1024 ** 3),
      disk: String(100 * 1024 ** 3),
      has_k3s: "0",
      active: "",
      role: "",
      server_url: "",
    };
    expect(classifyK3sPreflightFacts(base, "https://100.101.104.41:6443").state).toBe("clean");
    expect(
      classifyK3sPreflightFacts(
        {
          ...base,
          has_k3s: "1",
          active: "1",
          role: "agent",
          server_url: "https://100.101.104.41:6443",
        },
        "https://100.101.104.41:6443",
      ).state,
    ).toBe("already-enrolled");
    expect(
      classifyK3sPreflightFacts(
        { ...base, has_k3s: "1", active: "1", role: "agent", server_url: "https://other:6443" },
        "https://100.101.104.41:6443",
      ).state,
    ).toBe("foreign");
    expect(
      classifyK3sPreflightFacts({ ...base, has_k3s: "1" }, "https://100.101.104.41:6443").state,
    ).toBe("partial");
  });
});
