import crypto from 'node:crypto'

export type PaddleWebhookVerificationResult =
  | { ok: true; timestamp: string }
  | {
      ok: false
      error: 'missing_secret' | 'missing_signature' | 'invalid_signature_header' | 'expired_signature' | 'invalid_signature'
    }

interface VerifyPaddleWebhookSignatureInput {
  rawBody: string
  signatureHeader: string | string[] | undefined
  secretKey: string
  toleranceMs?: number
  now?: () => number
}

function normalizeSignatureHeaderValue(signatureHeader: string | string[] | undefined): string {
  if (Array.isArray(signatureHeader)) {
    return signatureHeader[0] || ''
  }
  return String(signatureHeader || '')
}

export function verifyPaddleWebhookSignature({
  rawBody,
  signatureHeader,
  secretKey,
  toleranceMs = 5_000,
  now = () => Date.now(),
}: VerifyPaddleWebhookSignatureInput): PaddleWebhookVerificationResult {
  if (!secretKey.trim()) {
    return { ok: false, error: 'missing_secret' }
  }

  const normalizedHeader = normalizeSignatureHeaderValue(signatureHeader).trim()
  if (!normalizedHeader) {
    return { ok: false, error: 'missing_signature' }
  }

  const headerParts = new Map<string, string>()
  for (const part of normalizedHeader.split(';')) {
    const [key, value] = part.split('=', 2).map((entry) => entry.trim())
    if (key && value) {
      headerParts.set(key, value)
    }
  }

  const timestamp = headerParts.get('ts')
  const providedSignature = headerParts.get('h1')
  if (!timestamp || !providedSignature) {
    return { ok: false, error: 'invalid_signature_header' }
  }

  const timestampMs = Number(timestamp) * 1_000
  if (!Number.isFinite(timestampMs) || Math.abs(now() - timestampMs) > toleranceMs) {
    return { ok: false, error: 'expired_signature' }
  }

  if (!/^[a-f0-9]+$/i.test(providedSignature) || providedSignature.length % 2 !== 0) {
    return { ok: false, error: 'invalid_signature' }
  }

  const expectedSignature = crypto
    .createHmac('sha256', secretKey)
    .update(`${timestamp}:${rawBody}`)
    .digest('hex')

  const expectedBuffer = Buffer.from(expectedSignature, 'hex')
  const providedBuffer = Buffer.from(providedSignature, 'hex')
  if (expectedBuffer.length !== providedBuffer.length) {
    return { ok: false, error: 'invalid_signature' }
  }

  if (!crypto.timingSafeEqual(expectedBuffer, providedBuffer)) {
    return { ok: false, error: 'invalid_signature' }
  }

  return { ok: true, timestamp }
}
