#!/usr/bin/env bun
import { createHash } from "node:crypto";
import { readFileSync } from "node:fs";

function option(name) {
  const index = process.argv.indexOf(name);
  if (index === -1) return null;
  const value = process.argv[index + 1];
  if (value === undefined || value.length === 0 || value.startsWith("--")) throw new Error(`${name} requires a value`);
  return value;
}

function record(value, path) {
  if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${path} must be an object`);
  return value;
}

function required(value, path) {
  if (typeof value !== "string" || value.length === 0) throw new Error(`${path} must be a non-empty string`);
  return value;
}

function positiveInteger(value, path) {
  if (!Number.isInteger(value) || value < 1) throw new Error(`${path} must be a positive integer`);
  return value;
}

function booleanValue(value, path) {
  if (typeof value !== "boolean") throw new Error(`${path} must be a boolean`);
  return value;
}

function stringValue(value, path) {
  if (typeof value !== "string") throw new Error(`${path} must be a string`);
  return value;
}

function valueAt(root, reference) {
  return reference.split(".").reduce((value, key) => record(value, reference)[key], root);
}

function sha(value) {
  return createHash("sha256").update(value).digest("hex").slice(0, 16);
}

function secretEnv(name, secretName, secretKey) {
  return { name, valueFrom: { secretKeyRef: { name: secretName, key: secretKey } } };
}

function main() {
const configPath = option("--config") ?? "config/unidesk-host-k8s.yaml";
const serviceRef = required(option("--service-ref"), "--service-ref");
const image = required(option("--image"), "--image");
const sourceCommit = required(option("--source-commit"), "--source-commit");
if (!/^services\.[A-Za-z][A-Za-z0-9]*$/u.test(serviceRef)) throw new Error("--service-ref must identify one services.<key> entry");
if (!/@sha256:[0-9a-f]{64}$/u.test(image)) throw new Error("--image must be digest pinned");
if (!/^[0-9a-f]{40}$/u.test(sourceCommit)) throw new Error("--source-commit must be a full Git commit SHA");

const config = record(Bun.YAML.parse(readFileSync(configPath, "utf8")), configPath);
const target = record(config.target, "target");
const runtime = record(config.runtime, "runtime");
const images = record(config.images, "images");
const service = record(valueAt(config, serviceRef), serviceRef);
const database = record(service.database, `${serviceRef}.database`);
const storage = record(service.storage, `${serviceRef}.storage`);
const repository = record(service.repository, `${serviceRef}.repository`);
const reminder = service.reminder === undefined ? null : record(service.reminder, `${serviceRef}.reminder`);
const reminderTransport = reminder === null ? null : record(reminder.transport, `${serviceRef}.reminder.transport`);
const resources = record(service.resources, `${serviceRef}.resources`);
const github = storage.primary === "github-repo" ? record(storage.github, `${serviceRef}.storage.github`) : null;
const githubSsh = github === null ? null : record(github.ssh, `${serviceRef}.storage.github.ssh`);
const privateKey = githubSsh === null ? null : record(githubSsh.privateKey, `${serviceRef}.storage.github.ssh.privateKey`);
const knownHosts = githubSsh === null ? null : record(githubSsh.knownHosts, `${serviceRef}.storage.github.ssh.knownHosts`);
const namespace = required(target.namespace, "target.namespace");
const serviceId = required(service.serviceId, `${serviceRef}.serviceId`);
const serviceName = required(service.serviceName, `${serviceRef}.serviceName`);
const deploymentName = required(service.deploymentName, `${serviceRef}.deploymentName`);
const containerName = required(service.containerName, `${serviceRef}.containerName`);
const containerPort = positiveInteger(service.containerPort, `${serviceRef}.containerPort`);
const databaseSecretName = required(database.secretName, `${serviceRef}.database.secretName`);
const databaseSecretKey = required(database.secretKey, `${serviceRef}.database.secretKey`);
const storageEnvPrefix = required(service.storageEnvPrefix, `${serviceRef}.storageEnvPrefix`);
const configuredImage = required(valueAt(config, required(service.imageRef, `${serviceRef}.imageRef`)), service.imageRef);
const configFingerprint = sha(JSON.stringify({ target, runtime, images, serviceRef, service, configuredImage }));
const commonLabels = {
  "app.kubernetes.io/name": serviceId,
  "app.kubernetes.io/part-of": "unidesk",
  "app.kubernetes.io/managed-by": "argocd",
  "unidesk.ai/deployment": required(record(config.metadata, "metadata").name, "metadata.name"),
  "unidesk.ai/target": required(target.id, "target.id"),
};

const env = [
  { name: "HOST", value: "0.0.0.0" },
  { name: "PORT", value: String(containerPort) },
  secretEnv("DATABASE_URL", databaseSecretName, databaseSecretKey),
  { name: "DATABASE_POOL_MAX", value: required(record(config.runtimeConfig, "runtimeConfig").databasePoolMax, "runtimeConfig.databasePoolMax") },
  { name: "UNIDESK_DEPLOY_SERVICE_ID", value: serviceId },
  { name: "UNIDESK_DEPLOY_REPO", value: required(repository.url, `${serviceRef}.repository.url`) },
  { name: "UNIDESK_DEPLOY_COMMIT", value: sourceCommit },
  { name: "UNIDESK_DEPLOY_REQUESTED_COMMIT", value: sourceCommit },
  { name: "LOG_FILE", value: required(service.logFile, `${serviceRef}.logFile`) },
  { name: `${storageEnvPrefix}_STORAGE_PRIMARY`, value: required(storage.primary, `${serviceRef}.storage.primary`) },
];

if (storage.healthRefreshSeconds !== undefined) {
  env.push({
    name: `${storageEnvPrefix}_STORAGE_HEALTH_REFRESH_MS`,
    value: String(positiveInteger(storage.healthRefreshSeconds, `${serviceRef}.storage.healthRefreshSeconds`) * 1000),
  });
}

if (reminder !== null && reminderTransport !== null) {
  const reminderTransportType = required(reminderTransport.type, `${serviceRef}.reminder.transport.type`);
  if (reminderTransportType !== "claudeqq") throw new Error(`${serviceRef}.reminder.transport.type must be claudeqq`);
  const reminderTargetType = required(reminderTransport.targetType, `${serviceRef}.reminder.transport.targetType`);
  if (reminderTargetType !== "private" && reminderTargetType !== "group") throw new Error(`${serviceRef}.reminder.transport.targetType must be private or group`);
  const reminderUserId = stringValue(reminderTransport.userId, `${serviceRef}.reminder.transport.userId`);
  const reminderGroupId = stringValue(reminderTransport.groupId, `${serviceRef}.reminder.transport.groupId`);
  if (booleanValue(reminder.enabled, `${serviceRef}.reminder.enabled`) && reminderTargetType === "private" && reminderUserId.length === 0) throw new Error(`${serviceRef}.reminder.transport.userId is required for private reminders`);
  if (reminder.enabled && reminderTargetType === "group" && reminderGroupId.length === 0) throw new Error(`${serviceRef}.reminder.transport.groupId is required for group reminders`);
  env.push(
    { name: "TODO_NOTE_REMINDER_CLAUDEQQ_ENABLED", value: String(reminder.enabled) },
    { name: "TODO_NOTE_REMINDER_CLAUDEQQ_BASE_URL", value: required(reminderTransport.baseUrl, `${serviceRef}.reminder.transport.baseUrl`) },
    { name: "TODO_NOTE_REMINDER_CLAUDEQQ_TARGET_TYPE", value: reminderTargetType },
    { name: "TODO_NOTE_REMINDER_CLAUDEQQ_USER_ID", value: reminderUserId },
    { name: "TODO_NOTE_REMINDER_CLAUDEQQ_GROUP_ID", value: reminderGroupId },
    { name: "TODO_NOTE_REMINDER_LEAD_MINUTES", value: String(positiveInteger(reminder.leadMinutes, `${serviceRef}.reminder.leadMinutes`)) },
    { name: "TODO_NOTE_REMINDER_SCAN_INTERVAL_MS", value: String(positiveInteger(reminder.scanIntervalMs, `${serviceRef}.reminder.scanIntervalMs`)) },
    { name: "TODO_NOTE_REMINDER_CLAUDEQQ_TIMEOUT_MS", value: String(positiveInteger(reminderTransport.timeoutMs, `${serviceRef}.reminder.transport.timeoutMs`)) },
    { name: "TODO_NOTE_REMINDER_CLAUDEQQ_SEND_ATTEMPTS", value: String(positiveInteger(reminderTransport.sendAttempts, `${serviceRef}.reminder.transport.sendAttempts`)) },
  );
}

const volumeMounts = [{ name: "logs", mountPath: "/var/log/unidesk" }];
const volumes = [{ name: "logs", emptyDir: {} }];
if (github !== null && githubSsh !== null && privateKey !== null && knownHosts !== null) {
  const mountPath = required(githubSsh.mountPath, `${serviceRef}.storage.github.ssh.mountPath`);
  const privateKeyName = required(privateKey.secretKey, `${serviceRef}.storage.github.ssh.privateKey.secretKey`);
  const knownHostsName = required(knownHosts.secretKey, `${serviceRef}.storage.github.ssh.knownHosts.secretKey`);
  env.push(
    { name: `${storageEnvPrefix}_GIT_REPO`, value: required(github.repo, `${serviceRef}.storage.github.repo`) },
    { name: `${storageEnvPrefix}_GIT_REPO_SSH_URL`, value: required(github.sshUrl, `${serviceRef}.storage.github.sshUrl`) },
    { name: `${storageEnvPrefix}_GIT_BRANCH`, value: required(github.branch, `${serviceRef}.storage.github.branch`) },
    { name: `${storageEnvPrefix}_GIT_BASE_PATH`, value: required(github.basePath, `${serviceRef}.storage.github.basePath`) },
    { name: `${storageEnvPrefix}_GIT_WORKTREE`, value: required(github.worktreePath, `${serviceRef}.storage.github.worktreePath`) },
    { name: `${storageEnvPrefix}_GIT_AUTHOR_NAME`, value: required(record(github.author, `${serviceRef}.storage.github.author`).name, `${serviceRef}.storage.github.author.name`) },
    { name: `${storageEnvPrefix}_GIT_AUTHOR_EMAIL`, value: required(record(github.author, `${serviceRef}.storage.github.author`).email, `${serviceRef}.storage.github.author.email`) },
    { name: `${storageEnvPrefix}_GIT_COMMIT_PREFIX`, value: required(github.commitMessagePrefix, `${serviceRef}.storage.github.commitMessagePrefix`) },
    { name: "GIT_SSH_COMMAND", value: `ssh -i ${mountPath}/${privateKeyName} -o UserKnownHostsFile=${mountPath}/${knownHostsName} -o IdentitiesOnly=yes -o StrictHostKeyChecking=yes` },
  );
  volumeMounts.push({ name: "github-ssh", mountPath, readOnly: true });
  volumes.push({ name: "github-ssh", secret: { secretName: required(githubSsh.secretName, `${serviceRef}.storage.github.ssh.secretName`), defaultMode: 256 } });
}

const objects = [
  {
    apiVersion: "v1",
    kind: "Service",
    metadata: { name: serviceName, namespace, labels: commonLabels },
    spec: {
      type: "ClusterIP",
      selector: { "app.kubernetes.io/name": serviceId },
      ports: [{ name: "http", port: containerPort, targetPort: "http" }],
    },
  },
  {
    apiVersion: "apps/v1",
    kind: "Deployment",
    metadata: { name: deploymentName, namespace, labels: commonLabels },
    spec: {
      replicas: positiveInteger(service.replicas, `${serviceRef}.replicas`),
      selector: { matchLabels: { "app.kubernetes.io/name": serviceId } },
      template: {
        metadata: {
          labels: commonLabels,
          annotations: {
            "unidesk.ai/source-commit": sourceCommit,
            "unidesk.ai/config-fingerprint": `sha256:${configFingerprint}`,
          },
        },
        spec: {
          initContainers: [{
            name: "wait-database",
            image: required(images.postgres, "images.postgres"),
            imagePullPolicy: "IfNotPresent",
            env: [secretEnv("DATABASE_URL", databaseSecretName, databaseSecretKey)],
            command: ["sh", "-ec", "db_url=\"$(printf '%s' \"$DATABASE_URL\" | sed 's/[?&]uselibpqcompat=true//g')\"; until psql \"$db_url\" -Atqc 'SELECT 1' >/dev/null 2>&1; do sleep 2; done"],
          }],
          containers: [{
            name: containerName,
            image,
            imagePullPolicy: "IfNotPresent",
            ports: [{ name: "http", containerPort }],
            env,
            volumeMounts,
            readinessProbe: { httpGet: { path: required(service.healthPath, `${serviceRef}.healthPath`), port: "http" }, periodSeconds: 5, timeoutSeconds: 3, failureThreshold: 18 },
            livenessProbe: { httpGet: { path: required(service.livePath, `${serviceRef}.livePath`), port: "http" }, periodSeconds: 10, timeoutSeconds: 3, failureThreshold: 6 },
            startupProbe: { httpGet: { path: required(service.livePath, `${serviceRef}.livePath`), port: "http" }, periodSeconds: 5, timeoutSeconds: 3, failureThreshold: 30 },
            resources,
          }],
          volumes,
        },
      },
    },
  },
];

process.stdout.write(`${objects.map((object) => Bun.YAML.stringify(object).trim()).join("\n---\n")}\n`);
}

if (!process.execArgv.includes("--check")) main();
