#!/usr/bin/env node
import { lstat, readFile } from "node:fs/promises";
import { execFile } from "node:child_process";
import { promisify } from "node:util";
import { PrismaClient } from "@prisma/client";
import { bootstrapAlphaSilo, type AlphaSiloBootstrapInput } from "./bootstrap-silo.js";
import { loadLocalSecretKeyringFile, LocalSecretEnvelope } from "../security/secretEnvelope.js";
import { readSiloOrganizationId } from "./silo.js";

const execFileAsync = promisify(execFile);

async function main(): Promise<void> {
  if (process.getuid?.() !== 0) throw new Error("Alpha Silo bootstrap must run as root");
  await requireStoppedSilo();
  const { configFile, keyringFile } = parseArgs(process.argv.slice(2));
  await requireRootPrivateFile(configFile, "bootstrap config");
  await requireRootPrivateFile(keyringFile, "secret keyring");
  const databaseUrl = process.env["DATABASE_URL"]?.trim();
  if (databaseUrl === undefined || databaseUrl === "") throw new Error("DATABASE_URL is required");
  const input = parseConfig(await readFile(configFile, "utf8"));
  const configuredOrganizationId = readSiloOrganizationId();
  if (input.organization.id !== configuredOrganizationId) {
    throw new Error(
      `bootstrap Organization ${input.organization.id} does not match HUB_SILO_ORGANIZATION_ID ${configuredOrganizationId}`,
    );
  }
  const secrets = new LocalSecretEnvelope(await loadLocalSecretKeyringFile(keyringFile));
  const prisma = new PrismaClient({ datasources: { db: { url: databaseUrl } }, log: [] });
  try {
    const result = await bootstrapAlphaSilo(prisma, secrets, input);
    console.log(JSON.stringify(result));
  } finally {
    await prisma.$disconnect();
  }
}

async function requireStoppedSilo(): Promise<void> {
  const unit = process.env["HUB_SYSTEMD_UNIT"]?.trim();
  if (unit === undefined || !/^cph-hub-[a-z0-9][a-z0-9-]*\.service$/.test(unit)) {
    throw new Error("HUB_SYSTEMD_UNIT must name the Silo being bootstrapped");
  }
  let state: string;
  try {
    const result = await execFileAsync("systemctl", ["show", "--property=ActiveState", "--value", unit], {
      timeout: 10_000,
    });
    state = result.stdout.trim();
  } catch (error) {
    throw new Error(`cannot verify that ${unit} is stopped`, { cause: error });
  }
  if (state !== "inactive" && state !== "failed") {
    throw new Error(`${unit} must be stopped before bootstrap; state=${state || "unknown"}`);
  }
}

function parseArgs(argv: readonly string[]): { readonly configFile: string; readonly keyringFile: string } {
  if (argv.length !== 4 || argv[0] !== "--config-file" || argv[2] !== "--keyring-file" ||
      argv[1] === undefined || argv[1] === "" || argv[3] === undefined || argv[3] === "") {
    throw new Error("usage: bootstrap-silo --config-file /root/bootstrap.json --keyring-file /root/keyring.json");
  }
  return { configFile: argv[1], keyringFile: argv[3] };
}

async function requireRootPrivateFile(path: string, label: string): Promise<void> {
  const metadata = await lstat(path);
  if (metadata.isSymbolicLink() || !metadata.isFile()) throw new Error(`${label} is not a regular file: ${path}`);
  if (metadata.uid !== 0 || (metadata.mode & 0o077) !== 0) {
    throw new Error(`${label} must be root-owned and inaccessible by group/others: ${path}`);
  }
}

function parseConfig(source: string): AlphaSiloBootstrapInput {
  let value: unknown;
  try {
    value = JSON.parse(source);
  } catch (error) {
    throw new Error("bootstrap config is not valid JSON", { cause: error });
  }
  if (!isRecord(value) || !isRecord(value["organization"]) || !isRecord(value["owner"]) ||
      !isRecord(value["feishu"]) || !isRecord(value["provider"])) {
    throw new Error("bootstrap config must contain organization, owner, feishu, and provider objects");
  }
  const organization = value["organization"];
  const owner = value["owner"];
  const feishu = value["feishu"];
  const provider = value["provider"];
  const teams = value["teams"];
  if (teams !== undefined && !Array.isArray(teams)) throw new Error("bootstrap teams must be an array");
  return {
    organization: {
      id: stringField(organization, "id"),
      slug: stringField(organization, "slug"),
      name: stringField(organization, "name"),
    },
    owner: {
      openId: stringField(owner, "openId"),
      displayName: stringField(owner, "displayName"),
      ...(optionalStringField(owner, "unionId") !== undefined
        ? { unionId: optionalStringField(owner, "unionId")! }
        : {}),
    },
    feishu: {
      appId: stringField(feishu, "appId"),
      appSecret: stringField(feishu, "appSecret"),
      botOpenId: stringField(feishu, "botOpenId"),
      ...(optionalStringField(feishu, "verificationToken") !== undefined
        ? { verificationToken: optionalStringField(feishu, "verificationToken")! }
        : {}),
      ...(optionalStringField(feishu, "encryptKey") !== undefined
        ? { encryptKey: optionalStringField(feishu, "encryptKey")! }
        : {}),
    },
    provider: {
      providerId: stringField(provider, "providerId"),
      baseUrl: stringField(provider, "baseUrl"),
      authToken: stringField(provider, "authToken"),
      ...(optionalStringField(provider, "anthropicApiKey") !== undefined
        ? { anthropicApiKey: optionalStringField(provider, "anthropicApiKey")! }
        : {}),
    },
    ...(Array.isArray(teams)
      ? {
          teams: teams.map((team, index) => {
            if (!isRecord(team)) throw new Error(`bootstrap teams[${index}] must be an object`);
            return {
              slug: stringField(team, "slug"),
              name: stringField(team, "name"),
              ...(optionalStringField(team, "description") !== undefined
                ? { description: optionalStringField(team, "description")! }
                : {}),
            };
          }),
        }
      : {}),
  };
}

function stringField(value: Record<string, unknown>, name: string): string {
  const field = value[name];
  if (typeof field !== "string" || field.trim() === "") throw new Error(`bootstrap ${name} is required`);
  return field;
}

function optionalStringField(value: Record<string, unknown>, name: string): string | undefined {
  const field = value[name];
  if (field === undefined) return undefined;
  if (typeof field !== "string") throw new Error(`bootstrap ${name} must be a string`);
  return field;
}

function isRecord(value: unknown): value is Record<string, unknown> {
  return typeof value === "object" && value !== null && !Array.isArray(value);
}

main().catch((error: unknown) => {
  console.error(`[bootstrap-silo] failed: ${error instanceof Error ? error.message : String(error)}`);
  process.exitCode = 1;
});
