import { readFileSync } from "node:fs";
import {
  loadManagedProvisionerPublicKeyFile,
  validateManagedProvisionerAudience,
  validateManagedProvisionerIdentity,
} from "./managed-provisioner-auth.mjs";

export function readConfig(env = process.env) {
  const provisionerApiEnabled = boolean(env.EXTERNAL_DATA_PLANE_PROVISIONING_ENABLED, false);
  const managedProvisionerApiEnabled = boolean(env.EXTERNAL_DATA_PLANE_MANAGED_PROVISIONING_ENABLED, false);
  const foundryProvisionerApiEnabled = boolean(env.EXTERNAL_DATA_PLANE_FOUNDRY_PROVISIONING_ENABLED, false);
  const managedProvisionerMaxSkewSeconds = integer(
    env.EXTERNAL_DATA_PLANE_MANAGED_PROVISIONER_MAX_SKEW_SECONDS,
    60,
    5,
    300,
  );
  const config = {
    port: integer(env.PORT, 18106, 1, 65535),
    databaseUrl: required(env.EXTERNAL_DATA_PLANE_DATABASE_URL, "EXTERNAL_DATA_PLANE_DATABASE_URL"),
    databasePoolSize: integer(env.EXTERNAL_DATA_PLANE_DATABASE_POOL_SIZE, 10, 1, 50),
    internalAccessToken: optional(env.NODEDC_INTERNAL_ACCESS_TOKEN),
    provisionerApiEnabled,
    managedProvisionerApiEnabled,
    foundryProvisionerApiEnabled,
    provisionerAccessToken: provisionerApiEnabled ? secretFile(env.EXTERNAL_DATA_PLANE_PROVISIONER_TOKEN_FILE) : "",
    managedProvisionerPublicKey: managedProvisionerApiEnabled
      ? loadManagedProvisionerPublicKeyFile(env.EXTERNAL_DATA_PLANE_MANAGED_PROVISIONER_PUBLIC_KEY_FILE)
      : null,
    managedProvisionerServiceId: managedProvisionerApiEnabled
      ? validateManagedProvisionerIdentity(
        required(env.EXTERNAL_DATA_PLANE_MANAGED_PROVISIONER_SERVICE_ID, "EXTERNAL_DATA_PLANE_MANAGED_PROVISIONER_SERVICE_ID"),
        "service_id",
      )
      : "",
    managedProvisionerKeyId: managedProvisionerApiEnabled
      ? validateManagedProvisionerIdentity(
        required(env.EXTERNAL_DATA_PLANE_MANAGED_PROVISIONER_KEY_ID, "EXTERNAL_DATA_PLANE_MANAGED_PROVISIONER_KEY_ID"),
        "key_id",
      )
      : "",
    managedProvisionerAudience: managedProvisionerApiEnabled
      ? validateManagedProvisionerAudience(
        required(env.EXTERNAL_DATA_PLANE_MANAGED_PROVISIONER_AUDIENCE, "EXTERNAL_DATA_PLANE_MANAGED_PROVISIONER_AUDIENCE"),
      )
      : "",
    managedProvisionerMaxSkewMs: managedProvisionerMaxSkewSeconds * 1000,
    managedProvisionerReplayCacheMaxEntries: integer(
      env.EXTERNAL_DATA_PLANE_MANAGED_PROVISIONER_REPLAY_CACHE_MAX_ENTRIES,
      10_000,
      100,
      100_000,
    ),
    foundryProvisionerPublicKey: foundryProvisionerApiEnabled
      ? loadManagedProvisionerPublicKeyFile(env.EXTERNAL_DATA_PLANE_FOUNDRY_PROVISIONER_PUBLIC_KEY_FILE)
      : null,
    foundryProvisionerServiceId: foundryProvisionerApiEnabled
      ? validateManagedProvisionerIdentity(
        required(env.EXTERNAL_DATA_PLANE_FOUNDRY_PROVISIONER_SERVICE_ID, "EXTERNAL_DATA_PLANE_FOUNDRY_PROVISIONER_SERVICE_ID"),
        "service_id",
      )
      : "",
    foundryProvisionerKeyId: foundryProvisionerApiEnabled
      ? validateManagedProvisionerIdentity(
        required(env.EXTERNAL_DATA_PLANE_FOUNDRY_PROVISIONER_KEY_ID, "EXTERNAL_DATA_PLANE_FOUNDRY_PROVISIONER_KEY_ID"),
        "key_id",
      )
      : "",
    foundryProvisionerAudience: foundryProvisionerApiEnabled
      ? validateManagedProvisionerAudience(
        required(env.EXTERNAL_DATA_PLANE_FOUNDRY_PROVISIONER_AUDIENCE, "EXTERNAL_DATA_PLANE_FOUNDRY_PROVISIONER_AUDIENCE"),
      )
      : "",
    foundryProvisionerMaxSkewMs: integer(
      env.EXTERNAL_DATA_PLANE_FOUNDRY_PROVISIONER_MAX_SKEW_SECONDS,
      60,
      5,
      300,
    ) * 1000,
    foundryProvisionerReplayCacheMaxEntries: integer(
      env.EXTERNAL_DATA_PLANE_FOUNDRY_PROVISIONER_REPLAY_CACHE_MAX_ENTRIES,
      10_000,
      100,
      100_000,
    ),
    rawRetentionDays: integer(env.EXTERNAL_DATA_PLANE_RAW_RETENTION_DAYS, 14, 1, 3650),
    maxBatchBytes: integer(env.EXTERNAL_DATA_PLANE_MAX_BATCH_BYTES, 5 * 1024 * 1024, 1024, 50 * 1024 * 1024),
    maxFactsPerPublish: integer(env.EXTERNAL_DATA_PLANE_MAX_FACTS_PER_PUBLISH, 5000, 1, 100_000),
    maxAttributesBytesPerFact: integer(env.EXTERNAL_DATA_PLANE_MAX_ATTRIBUTES_BYTES_PER_FACT, 64 * 1024, 256, 1024 * 1024),
    maxPatchOperations: integer(env.EXTERNAL_DATA_PLANE_MAX_PATCH_OPERATIONS, 500, 1, 5000),
    maxPatchBytes: integer(env.EXTERNAL_DATA_PLANE_MAX_PATCH_BYTES, 256 * 1024, 16 * 1024, 5 * 1024 * 1024),
    patchRetentionMs: integer(env.EXTERNAL_DATA_PLANE_PATCH_RETENTION_MS, 60 * 60 * 1000, 60 * 1000, 7 * 24 * 60 * 60 * 1000),
    receiptRetentionMs: integer(env.EXTERNAL_DATA_PLANE_RECEIPT_RETENTION_MS, 7 * 24 * 60 * 60 * 1000, 24 * 60 * 60 * 1000, 90 * 24 * 60 * 60 * 1000),
    retentionDeleteLimit: integer(env.EXTERNAL_DATA_PLANE_RETENTION_DELETE_LIMIT, 10_000, 100, 100_000),
    streamHeartbeatMs: integer(env.EXTERNAL_DATA_PLANE_STREAM_HEARTBEAT_MS, 20_000, 5_000, 60_000),
    streamPollMs: integer(env.EXTERNAL_DATA_PLANE_STREAM_POLL_MS, 1_000, 250, 10_000),
    maxReaderStreams: integer(env.EXTERNAL_DATA_PLANE_MAX_READER_STREAMS, 10, 1, 1000),
    writerBindingMaxTtlDays: integer(env.EXTERNAL_DATA_PLANE_WRITER_BINDING_MAX_TTL_DAYS, 90, 1, 365),
    maxFutureSkewSeconds: integer(env.EXTERNAL_DATA_PLANE_MAX_FUTURE_SKEW_SECONDS, 300, 0, 86400),
    retentionSweepMs: integer(env.EXTERNAL_DATA_PLANE_RETENTION_SWEEP_MS, 60 * 60 * 1000, 60 * 1000, 24 * 60 * 60 * 1000),
    legacyIntakeEnabled: boolean(env.EXTERNAL_DATA_PLANE_LEGACY_INTAKE_ENABLED, false),
  };
  if (config.internalAccessToken && config.provisionerAccessToken && config.internalAccessToken === config.provisionerAccessToken) {
    throw new Error("provisioner_token_must_differ_from_internal_token");
  }
  return config;
}

function required(value, name) {
  const normalized = optional(value);
  if (!normalized) throw new Error(`${name}_required`);
  return normalized;
}

function optional(value) {
  const normalized = String(value ?? "").trim();
  return normalized || "";
}

function integer(value, fallback, min, max) {
  const candidate = optional(value);
  if (!candidate) return fallback;
  const number = Number.parseInt(candidate, 10);
  if (!Number.isInteger(number) || number < min || number > max) throw new Error(`invalid_integer:${candidate}`);
  return number;
}

function boolean(value, fallback) {
  const normalized = optional(value).toLowerCase();
  if (!normalized) return fallback;
  if (normalized === "true") return true;
  if (normalized === "false") return false;
  throw new Error(`invalid_boolean:${normalized}`);
}

function secretFile(value) {
  const path = optional(value);
  if (!path) return "";
  let secret = "";
  try {
    secret = readFileSync(path, "utf8").trim();
  } catch {
    throw new Error("external_data_plane_provisioner_token_file_unreadable");
  }
  if (!/^[A-Za-z0-9_-]{48,256}$/.test(secret)) {
    throw new Error("external_data_plane_provisioner_token_file_invalid");
  }
  return secret;
}
