import crypto from "node:crypto";
import { isEncryptionDebugEnabled } from "./toggles.js";
import { createLogger } from "../logger/index.js";
const logger = createLogger("shared").child({ component: "encryption" });
var TenantDataEncryptionErrorCode = /* @__PURE__ */ ((TenantDataEncryptionErrorCode2) => {
  TenantDataEncryptionErrorCode2["AUTH_FAILED"] = "AUTH_FAILED";
  TenantDataEncryptionErrorCode2["MALFORMED_PAYLOAD"] = "MALFORMED_PAYLOAD";
  TenantDataEncryptionErrorCode2["KMS_UNAVAILABLE"] = "KMS_UNAVAILABLE";
  TenantDataEncryptionErrorCode2["WRONG_KEY"] = "WRONG_KEY";
  TenantDataEncryptionErrorCode2["DECRYPT_INTERNAL"] = "DECRYPT_INTERNAL";
  return TenantDataEncryptionErrorCode2;
})(TenantDataEncryptionErrorCode || {});
class TenantDataEncryptionError extends Error {
  constructor(code, message) {
    super(message);
    this.name = "TenantDataEncryptionError";
    this.code = code;
  }
}
function generateDek() {
  return crypto.randomBytes(32).toString("base64");
}
function logDebug(event, payload) {
  if (!isEncryptionDebugEnabled()) return;
  try {
    logger.debug(event, payload);
  } catch {
  }
}
function encryptWithAesGcm(value, dekBase64) {
  const dek = Buffer.from(dekBase64, "base64");
  const iv = crypto.randomBytes(12);
  const cipher = crypto.createCipheriv("aes-256-gcm", dek, iv);
  const ciphertext = Buffer.concat([cipher.update(value, "utf8"), cipher.final()]);
  const tag = cipher.getAuthTag();
  const payload = [
    iv.toString("base64"),
    ciphertext.toString("base64"),
    tag.toString("base64"),
    "v1"
  ].join(":");
  logDebug("encrypt", { length: ciphertext.length });
  return { value: payload, raw: payload, version: "v1" };
}
function runAesGcmDecrypt(dek, iv, ciphertext, tag) {
  const decipher = crypto.createDecipheriv("aes-256-gcm", dek, iv);
  decipher.setAuthTag(tag);
  return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8");
}
function decryptWithAesGcm(payload, dekBase64) {
  if (!payload) return null;
  const parts = payload.split(":");
  if (parts.length !== 4) return null;
  const [ivB64, ciphertextB64, tagB64, version] = parts;
  if (version !== "v1") return null;
  const dek = Buffer.from(dekBase64, "base64");
  const iv = Buffer.from(ivB64, "base64");
  const ciphertext = Buffer.from(ciphertextB64, "base64");
  const tag = Buffer.from(tagB64, "base64");
  try {
    const result = runAesGcmDecrypt(dek, iv, ciphertext, tag);
    logDebug("decrypt", { iv: ivB64, tag: tagB64 });
    return result;
  } catch (err) {
    logDebug("decrypt_error", { message: err?.message || String(err) });
    return null;
  }
}
const LOOKUP_HASH_V2_PREFIX = "v2:";
function normalizeLookupValue(value) {
  return value.toLowerCase().trim();
}
function legacyHashForLookup(value) {
  return crypto.createHash("sha256").update(normalizeLookupValue(value)).digest("hex");
}
function resolveLookupPepper() {
  const candidates = [
    process.env.LOOKUP_HASH_PEPPER,
    process.env.TENANT_DATA_ENCRYPTION_FALLBACK_KEY,
    process.env.TENANT_DATA_ENCRYPTION_KEY
  ];
  for (const candidate of candidates) {
    if (typeof candidate !== "string") continue;
    const normalized = candidate.trim().replace(/(?:^['"]|['"]$)/g, "");
    if (normalized) return normalized;
  }
  return null;
}
function hashForLookup(value, context) {
  const pepper = resolveLookupPepper();
  const normalized = normalizeLookupValue(value);
  if (!pepper) {
    return legacyHashForLookup(value);
  }
  const message = context ? `${context}:${normalized}` : normalized;
  const digest = crypto.createHmac("sha256", pepper).update(message).digest("hex");
  return `${LOOKUP_HASH_V2_PREFIX}${digest}`;
}
function lookupHashCandidates(value, context) {
  const primary = hashForLookup(value, context);
  const legacy = legacyHashForLookup(value);
  return primary === legacy ? [primary] : [primary, legacy];
}
function decryptWithAesGcmStrict(payload, dekBase64) {
  const parts = payload.split(":");
  if (parts.length !== 4 || parts[3] !== "v1") {
    throw new TenantDataEncryptionError(
      "AUTH_FAILED" /* AUTH_FAILED */,
      "Value is not an encrypted payload (format mismatch)"
    );
  }
  const [ivB64, ciphertextB64, tagB64] = parts;
  let dek, iv, ciphertext, tag;
  try {
    dek = Buffer.from(dekBase64, "base64");
    iv = Buffer.from(ivB64, "base64");
    ciphertext = Buffer.from(ciphertextB64, "base64");
    tag = Buffer.from(tagB64, "base64");
  } catch {
    throw new TenantDataEncryptionError(
      "MALFORMED_PAYLOAD" /* MALFORMED_PAYLOAD */,
      "Failed to decode base64 components"
    );
  }
  if (iv.length !== 12 || tag.length !== 16 || ciphertext.length === 0) {
    throw new TenantDataEncryptionError(
      "MALFORMED_PAYLOAD" /* MALFORMED_PAYLOAD */,
      "Invalid AES-GCM payload: unexpected IV, tag, or ciphertext size"
    );
  }
  try {
    return runAesGcmDecrypt(dek, iv, ciphertext, tag);
  } catch {
    throw new TenantDataEncryptionError(
      "AUTH_FAILED" /* AUTH_FAILED */,
      "AES-GCM authentication tag verification failed"
    );
  }
}
export {
  TenantDataEncryptionError,
  TenantDataEncryptionErrorCode,
  decryptWithAesGcm,
  decryptWithAesGcmStrict,
  encryptWithAesGcm,
  generateDek,
  hashForLookup,
  legacyHashForLookup,
  lookupHashCandidates
};
//# sourceMappingURL=aes.js.map
