import crypto from 'crypto'

const ALGORITHM = 'aes-256-gcm'

// keyId stamped on all newly encrypted ciphertexts.
export const CURRENT_KEY_ID = 'platform-v1'

// Implicit keyId for pre-Vault ciphertext rows that lack a keyId field. All
// known rows were restamped with CURRENT_KEY_ID during the re-encryption
// migration; this constant remains only so any unexpected legacy blob produces
// a clear "KEK not loaded" error rather than a silent default-key decrypt.
export const LEGACY_KEY_ID = 'platform-v0-legacy'

interface EncryptedData {
  iv: string
  authTag: string
  data: string
  keyId?: string
}

const kekMap = new Map<string, Buffer>()

export function setKek(keyId: string, key: Buffer): void {
  if (key.length !== 32) {
    throw new Error(`KEK must be 32 bytes (256 bits); got ${key.length}`)
  }
  kekMap.set(keyId, key)
}

export function clearKeks(): void {
  kekMap.clear()
}

function resolveKek(keyId: string): Buffer | undefined {
  return kekMap.get(keyId)
}

export function encryptCredential(plaintext: string): string {
  const key = resolveKek(CURRENT_KEY_ID)
  if (!key) {
    throw new Error(`Encryption KEK '${CURRENT_KEY_ID}' not loaded; call setKek() at boot`)
  }

  const iv = crypto.randomBytes(16)
  const cipher = crypto.createCipheriv(ALGORITHM, key, iv)
  const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()])
  const authTag = cipher.getAuthTag()

  return JSON.stringify({
    iv: iv.toString('base64'),
    authTag: authTag.toString('base64'),
    data: encrypted.toString('base64'),
    keyId: CURRENT_KEY_ID
  })
}

export function decryptCredential(encrypted: string): string {
  const { iv, authTag, data, keyId } = JSON.parse(encrypted) as EncryptedData
  const resolvedKeyId = keyId ?? LEGACY_KEY_ID
  const key = resolveKek(resolvedKeyId)
  if (!key) {
    throw new Error(`Decryption KEK '${resolvedKeyId}' not loaded`)
  }

  const decipher = crypto.createDecipheriv(ALGORITHM, key, Buffer.from(iv, 'base64'))
  decipher.setAuthTag(Buffer.from(authTag, 'base64'))

  return Buffer.concat([decipher.update(Buffer.from(data, 'base64')), decipher.final()]).toString('utf8')
}
