/**
 * Calendar token crypto helpers — calendar-module.
 *
 * Thin wrappers over upstream `encryptCredential`/`decryptCredential` (from @zync/auth),
 * adapting the JSON-object envelope to/from the BYTEA Uint8Array the DB columns expect.
 * `verifyHmacSignature` uses Web Crypto HMAC-SHA256 and timing-safe comparison.
 *
 * NEVER re-implement AES here — delegate to the upstream helper.
 */
import { encryptCredential, decryptCredential, timingSafeEqual } from '@zync/auth'

// ── Internal envelope ─────────────────────────────────────────────────────────

interface TokenEnvelope {
  ciphertext: string
  iv: string
  authTag: string
}

// ── encryptToken ──────────────────────────────────────────────────────────────

/**
 * Encrypt a plaintext string (e.g. an OAuth access or refresh token) and return
 * the serialized envelope as a Uint8Array suitable for BYTEA storage.
 */
export async function encryptToken(plaintext: string, key: string): Promise<Uint8Array> {
  const encrypted = await encryptCredential(plaintext, key)
  const envelope: TokenEnvelope = {
    ciphertext: encrypted.ciphertext,
    iv: encrypted.iv,
    authTag: encrypted.authTag,
  }
  const json = JSON.stringify(envelope)
  return new TextEncoder().encode(json)
}

// ── decryptToken ──────────────────────────────────────────────────────────────

/**
 * Decrypt a BYTEA Uint8Array previously produced by `encryptToken` and return
 * the original plaintext string.
 */
export async function decryptToken(ciphertext: Uint8Array, key: string): Promise<string> {
  const json = new TextDecoder().decode(ciphertext)
  const envelope = JSON.parse(json) as TokenEnvelope
  return decryptCredential(
    {
      ciphertext: envelope.ciphertext,
      iv: envelope.iv,
      authTag: envelope.authTag,
    },
    key,
  )
}

// ── verifyHmacSignature ───────────────────────────────────────────────────────

/**
 * Verify an HMAC-SHA256 signature over a raw request body.
 *
 * `signatureHeader` may be a bare hex digest or `sha256=<hex>` (Calendly/Acuity style).
 * Comparison uses `timingSafeEqual` (never string === on secrets).
 *
 * Returns false on any crypto failure (bad key, bad signature format, mismatch).
 */
export async function verifyHmacSignature(
  secret: string,
  rawBody: string,
  signatureHeader: string,
): Promise<boolean> {
  try {
    // Strip optional "sha256=" prefix
    const rawHex = signatureHeader.startsWith('sha256=')
      ? signatureHeader.slice(7)
      : signatureHeader

    const key = await crypto.subtle.importKey(
      'raw',
      new TextEncoder().encode(secret),
      { name: 'HMAC', hash: 'SHA-256' },
      false,
      ['sign'],
    )

    const sigBuffer = await crypto.subtle.sign(
      'HMAC',
      key,
      new TextEncoder().encode(rawBody),
    )

    // Convert computed signature to hex
    const computed = Array.from(new Uint8Array(sigBuffer))
      .map((b) => b.toString(16).padStart(2, '0'))
      .join('')

    return timingSafeEqual(computed, rawHex)
  } catch {
    return false
  }
}
