import { execFile } from "node:child_process";
import { chmod, mkdir, mkdtemp, readFile, rm, symlink, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { promisify } from "node:util";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import {
  DEFAULT_ORG_ID,
  prisma,
  resetDb,
  testSecretEnvelope,
  TEST_SECRET_KEY,
  TEST_SECRET_KEY_ID,
} from "./helpers.js";
import { ProviderConnectionService } from "../../src/connections/providerConnections.js";
import { FeishuApplicationConnectionService } from "../../src/connections/feishuApplicationConnections.js";

const execFileAsync = promisify(execFile);
const TEST_DATABASE_URL = "postgresql://paradigm:paradigm@127.0.0.1:5432/cph_hub_test";

describe("deployment preflight CLI", { timeout: 20_000 }, () => {
  let root: string;
  let baseDir: string;
  let hubDir: string;
  let persistentDir: string;
  let marker: string;

  beforeEach(async () => {
    await resetDb();
    root = await mkdtemp(join(tmpdir(), "cph-deployment-preflight-"));
    baseDir = join(root, "deploy");
    hubDir = join(baseDir, "current", "hub");
    persistentDir = join(root, "persistent");
    marker = join(root, "external-check-ran");
    await Promise.all([
      mkdir(hubDir, { recursive: true }),
      mkdir(join(persistentDir, "home"), { recursive: true }),
      mkdir(join(persistentDir, "state"), { recursive: true }),
      mkdir(join(persistentDir, "cache"), { recursive: true }),
      mkdir(join(persistentDir, "workspaces"), { recursive: true }),
    ]);
    await Promise.all([
      chmod(join(persistentDir, "home"), 0o750),
      chmod(join(persistentDir, "state"), 0o750),
      chmod(join(persistentDir, "cache"), 0o750),
      chmod(join(persistentDir, "workspaces"), 0o750),
    ]);
  });

  afterEach(async () => {
    await rm(root, { recursive: true, force: true });
  });

  it("exits before prerequisite checks when a persistent path overlaps the deployment tree", async () => {
    const workspaceRoot = join(hubDir, "data", "workspaces");
    await mkdir(workspaceRoot, { recursive: true });
    const fixture = await createFixture({ workspaceRoot, marker });

    const result = await runCli(fixture.args);

    expect(result.exitCode).toBe(1);
    expect(result.stderr).toContain("workspace root must not overlap deployment root");
    await expect(readFile(marker, "utf8")).rejects.toMatchObject({ code: "ENOENT" });
  });

  it("returns success only after configuration and external prerequisites pass", async () => {
    const workspaceRoot = join(persistentDir, "workspaces");
    const fixture = await createFixture({ workspaceRoot, marker });

    const result = await runCli(fixture.args);

    expect(result).toMatchObject({ exitCode: 0, stderr: "" });
    expect(result.stdout).toContain("[preflight] configuration and prerequisites valid");
    expect(await readFile(marker, "utf8")).toContain("cph\npg_isready\n");
  });

  it("preserves the failed prerequisite exit code and stderr", async () => {
    const workspaceRoot = join(persistentDir, "workspaces");
    const fixture = await createFixture({
      workspaceRoot,
      marker,
      pgIsReadyScript: "#!/bin/sh\nprintf 'socket permission denied\\n' >&2\nexit 23\n",
    });

    const result = await runCli(fixture.args);

    expect(result.exitCode).toBe(1);
    expect(result.stderr).toContain("PostgreSQL is not accepting connections for DATABASE_URL");
    expect(result.stderr).toContain("exit=23");
    expect(result.stderr).toContain("socket permission denied");
  });

  it("rejects a database URL that accepts TCP connections but cannot execute a query", async () => {
    const workspaceRoot = join(persistentDir, "workspaces");
    const fixture = await createFixture({
      workspaceRoot,
      marker,
      databaseUrl: "postgresql://paradigm:paradigm@127.0.0.1:5432/cph_hub_preflight_missing",
    });

    const result = await runCli(fixture.args);

    expect(result.exitCode).toBe(1);
    expect(result.stderr).toContain("PostgreSQL authenticated query failed for DATABASE_URL");
  });

  it("rejects a structurally valid keyring that cannot authenticate stored envelopes", async () => {
    await prismaActorForEnvelope();
    const workspaceRoot = join(persistentDir, "workspaces");
    const fixture = await createFixture({
      workspaceRoot,
      marker,
      keyringKey: Buffer.alloc(32, "wrong"),
    });

    const result = await runCli(fixture.args);

    expect(result.exitCode).toBe(1);
    expect(result.stderr).toContain("stored connection envelope verification failed");
    expect(result.stderr).toContain("provider: secret envelope authentication failed");
    expect(result.stderr).toContain("Feishu: secret envelope authentication failed");
    expect(result.stderr).toContain("secret envelope authentication failed");
    expect(result.stderr).not.toContain("stored-envelope-secret");
    expect(result.stderr).not.toContain("stored-feishu-envelope-secret");
  });

  it("loads the actual Hub dependency graph and queries PostgreSQL in the runtime probe", async () => {
    const result = await execFileAsync(
      resolve("node_modules/.bin/tsx"),
      [resolve("src/deployment/service-runtime-probe.ts")],
      {
        env: { ...process.env, DATABASE_URL: TEST_DATABASE_URL, NODE_ENV: "production" },
      },
    );

    expect(result.stderr).toBe("");
    expect(result.stdout).toContain("Hub dependencies and PostgreSQL query succeeded");
  });

  it("probes the built runtime and bubblewrap as the service identity", async () => {
    const configuredPersistentParent = join(root, "persistent-link");
    await symlink(persistentDir, configuredPersistentParent);
    const workspaceRoot = join(configuredPersistentParent, "workspaces");
    const fixture = await createFixture({ workspaceRoot, marker });
    const uid = process.getuid?.();
    const gid = process.getgid?.();
    if (uid === undefined || gid === undefined) throw new Error("service identity probe requires POSIX uid/gid");

    const result = await runCli([
      ...fixture.args,
      "--service-uid",
      String(uid),
      "--service-gid",
      String(gid),
    ]);

    expect(result).toMatchObject({ exitCode: 0, stderr: "" });
    const probeLog = await readFile(marker, "utf8");
    expect(probeLog).toContain(workspaceRoot);
    expect(probeLog).toContain("service-setpriv\n");
    expect(probeLog).toContain("service-bwrap\n");
    expect(probeLog).toContain("service-socat\n");
    expect(probeLog).toContain("service-prisma\n");
    expect(probeLog).toContain("service-database\n");
  });

  async function createFixture(options: {
    workspaceRoot: string;
    marker: string;
    pgIsReadyScript?: string;
    databaseUrl?: string;
    keyringKey?: Buffer;
  }): Promise<{ args: string[] }> {
    const binDir = join(root, "bin");
    const cphBin = join(binDir, "cph");
    const bwrapBin = join(binDir, "bwrap");
    const socatBin = join(binDir, "socat");
    const pgIsReadyBin = join(binDir, "pg_isready");
    const runuserBin = join(binDir, "runuser");
    const setprivBin = join(binDir, "setpriv");
    const envFile = join(root, "platform.env");
    const keyringFile = join(root, "secret-keyring.json");
    await Promise.all([
      mkdir(binDir, { recursive: true }),
      mkdir(join(hubDir, "dist"), { recursive: true }),
      mkdir(join(hubDir, "dist", "deployment"), { recursive: true }),
      mkdir(join(hubDir, "node_modules", "prisma", "build"), { recursive: true }),
      mkdir(join(hubDir, "prisma"), { recursive: true }),
    ]);
    await Promise.all([
      executable(cphBin, `#!/bin/sh\nprintf 'cph\\n' >> '${options.marker}'\nprintf 'cph 0.0.2\\n'\n`),
      executable(bwrapBin, `#!/bin/sh\nprintf 'service-bwrap\\n' >> '${options.marker}'\nexit 0\n`),
      executable(socatBin, `#!/bin/sh\nprintf 'service-socat\\n' >> '${options.marker}'\nexit 0\n`),
      executable(
        runuserBin,
        `#!/bin/sh\nprintf '%s\\n' "$*" >> '${options.marker}'\n[ "$1" = "-u" ] || exit 91\nshift 2\n[ "$1" = "--" ] || exit 92\nshift\nexec "$@"\n`,
      ),
      executable(
        setprivBin,
        `#!/bin/sh\n[ "$1" = "--no-new-privs" ] || exit 93\nshift\nprintf 'service-setpriv\\n' >> '${options.marker}'\nexec "$@"\n`,
      ),
      executable(
        pgIsReadyBin,
        options.pgIsReadyScript ?? `#!/bin/sh\nprintf 'pg_isready\\n' >> '${options.marker}'\nexit 0\n`,
      ),
      writeFile(join(hubDir, "dist", "server.js"), "export {};\n"),
      writeFile(
        join(hubDir, "dist", "deployment", "service-runtime-probe.js"),
        `const { appendFileSync } = require("node:fs");\nappendFileSync(${JSON.stringify(options.marker)}, "service-database\\n");\n`,
      ),
      writeFile(
        join(hubDir, "node_modules", "prisma", "build", "index.js"),
        `const { appendFileSync } = require("node:fs");\nappendFileSync(${JSON.stringify(options.marker)}, "service-prisma\\n");\n`,
      ),
      writeFile(join(hubDir, "prisma", "schema.prisma"), "// fixture\n"),
      writeFile(keyringFile, JSON.stringify({
        version: 1,
        activeKeyId: TEST_SECRET_KEY_ID,
        keys: { [TEST_SECRET_KEY_ID]: (options.keyringKey ?? TEST_SECRET_KEY).toString("base64") },
      }), { mode: 0o600 }),
    ]);
    await writeFile(
      envFile,
      [
        "NODE_ENV=production",
        `DATABASE_URL=${options.databaseUrl ?? TEST_DATABASE_URL}`,
        "HUB_SILO_ORGANIZATION_ID=org_test",
        "HUB_SYSTEMD_UNIT=cph-hub-test.service",
        `CPH_BIN=${cphBin}`,
        "HOST=127.0.0.1",
        "PORT=8788",
        `HUB_PROJECT_WORKSPACE_ROOT=${options.workspaceRoot}`,
        "HUB_PUBLIC_BASE_URL=https://hub.example.com",
        "HUB_SESSION_SECRET=a-production-session-secret-with-32-bytes",
        "HUB_AGENT_MAX_CONCURRENT_RUNS=1",
        "HUB_AGENT_MAX_RUN_SECONDS=900",
        "HUB_HTTP_BODY_LIMIT_BYTES=1048576",
        "HUB_MAX_FILES_PER_MESSAGE=8",
        "HUB_MAX_FILE_BYTES=26214400",
        "HUB_HTTP_REQUESTS_PER_MINUTE=120",
        "HUB_FEISHU_EVENTS_PER_MINUTE=120",
        "",
      ].join("\n"),
      { mode: 0o600 },
    );

    return {
      args: [
        "--env-file",
        envFile,
        "--keyring-file",
        keyringFile,
        "--node-bin",
        process.execPath,
        "--runuser-bin",
        runuserBin,
        "--setpriv-bin",
        setprivBin,
        "--service-user",
        process.env["USER"] ?? "test-user",
        "--base-dir",
        baseDir,
        "--hub-dir",
        hubDir,
        "--service-home",
        join(persistentDir, "home"),
        "--state-dir",
        join(persistentDir, "state"),
        "--cache-dir",
        join(persistentDir, "cache"),
        "--workspace-root",
        options.workspaceRoot,
        "--service-unit",
        "cph-hub-test.service",
        "--bwrap-bin",
        bwrapBin,
        "--socat-bin",
        socatBin,
        "--pg-isready-bin",
        pgIsReadyBin,
      ],
    };
  }

  async function prismaActorForEnvelope(): Promise<void> {
    await prisma.user.create({ data: { id: "preflight-admin", feishuOpenId: "ou_preflight", displayName: "Preflight" } });
    await prisma.organizationMembership.create({
      data: { organizationId: DEFAULT_ORG_ID, userId: "preflight-admin", role: "ADMIN" },
    });
    await new ProviderConnectionService(prisma, testSecretEnvelope, async () => {}).rotateByok({
      organizationId: DEFAULT_ORG_ID,
      providerId: "openrouter",
      actorUserId: "preflight-admin",
      baseUrl: "https://provider.example/api",
      authToken: "stored-envelope-secret",
    });
    await new FeishuApplicationConnectionService(prisma, testSecretEnvelope, async () => {}).rotateCustomerApplication({
      organizationId: DEFAULT_ORG_ID,
      actorUserId: "preflight-admin",
      appId: "cli_preflight",
      appSecret: "stored-feishu-envelope-secret",
      botOpenId: "ou_preflight_bot",
    });
  }
});

async function executable(path: string, contents: string): Promise<void> {
  await writeFile(path, contents);
  await chmod(path, 0o755);
}

async function runCli(args: string[]): Promise<{ exitCode: number; stdout: string; stderr: string }> {
  try {
    const result = await execFileAsync(resolve("node_modules/.bin/tsx"), [resolve("src/deployment/preflight-cli.ts"), ...args]);
    return { exitCode: 0, stdout: result.stdout, stderr: result.stderr };
  } catch (error) {
    const failure = error as { code?: number; stdout?: string; stderr?: string };
    return { exitCode: failure.code ?? 1, stdout: failure.stdout ?? "", stderr: failure.stderr ?? "" };
  }
}
