/**
 * AES-256-GCM adapter-credential encryption helpers.
 *
 * Used by @zync/db to encrypt/decrypt per-tenant integration credentials
 * (Telegram, Slack, Gmail, invoice adapter API keys, etc.) stored in
 * adapter_credentials. Extracted here to avoid pulling @cloudflare/workers-types
 * into packages that don't run in the CF edge runtime.
 *
 * Uses only standard Web Crypto API (SubtleCrypto) — no CF-specific extensions.
 */

const CRED_GCM_IV_BYTES = 12
const CRED_GCM_TAG_BYTES = 16 // GCM tag is always 128 bits

function b64ToBytes(b64: string): Uint8Array {
  const binary = atob(b64)
  const out = new Uint8Array(binary.length)
  for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i)
  return out
}

function bytesToB64(bytes: Uint8Array): string {
  let binary = ''
  for (const b of bytes) binary += String.fromCharCode(b)
  return btoa(binary)
}

async function importAesCredKey(keyB64: string): Promise<CryptoKey> {
  const raw = b64ToBytes(keyB64)
  if (raw.byteLength !== 32) {
    throw new Error('INTEGRATION_ENCRYPTION_KEY must decode to exactly 32 bytes (AES-256)')
  }
  return crypto.subtle.importKey('raw', raw.buffer as ArrayBuffer, { name: 'AES-GCM' }, false, ['encrypt', 'decrypt'])
}

/**
 * Encrypt `plaintext` with AES-256-GCM under `keyB64` (base64 32-byte key).
 * Returns three separate base64 strings: ciphertext (without IV/tag), iv, authTag.
 * A fresh random 12-byte IV is generated per call.
 */
export async function encryptCredential(
  plaintext: string,
  keyB64: string,
): Promise<{ ciphertext: string; iv: string; authTag: string }> {
  const key = await importAesCredKey(keyB64)
  const ivBytes = crypto.getRandomValues(new Uint8Array(CRED_GCM_IV_BYTES))
  const ctWithTag = await crypto.subtle.encrypt(
    { name: 'AES-GCM', iv: ivBytes, tagLength: CRED_GCM_TAG_BYTES * 8 },
    key,
    new TextEncoder().encode(plaintext),
  )
  const ctWithTagBytes = new Uint8Array(ctWithTag)
  // GCM ciphertext = ctWithTag[0 .. len-16]; auth tag = ctWithTag[len-16 ..]
  const ctLen = ctWithTagBytes.byteLength - CRED_GCM_TAG_BYTES
  const cipherBytes = ctWithTagBytes.subarray(0, ctLen)
  const tagBytes = ctWithTagBytes.subarray(ctLen)

  return {
    ciphertext: bytesToB64(cipherBytes),
    iv: bytesToB64(ivBytes),
    authTag: bytesToB64(tagBytes),
  }
}

/**
 * Decrypt a credential blob produced by `encryptCredential`.
 * Throws on tamper / wrong key (GCM tag mismatch) or malformed input.
 */
export async function decryptCredential(
  blob: { ciphertext: string; iv: string; authTag: string },
  keyB64: string,
): Promise<string> {
  const key = await importAesCredKey(keyB64)
  const cipherBytes = b64ToBytes(blob.ciphertext)
  const ivBytes = b64ToBytes(blob.iv)
  const tagBytes = b64ToBytes(blob.authTag)
  // Re-assemble ciphertext+tag for WebCrypto
  const ctWithTag = new Uint8Array(cipherBytes.byteLength + tagBytes.byteLength)
  ctWithTag.set(cipherBytes, 0)
  ctWithTag.set(tagBytes, cipherBytes.byteLength)
  const pt = await crypto.subtle.decrypt(
    { name: 'AES-GCM', iv: ivBytes.buffer as ArrayBuffer, tagLength: CRED_GCM_TAG_BYTES * 8 },
    key,
    ctWithTag.buffer as ArrayBuffer,
  )
  return new TextDecoder().decode(pt)
}
