/**
 * Resend webhook receiver — invoice-email-history (wave-13, spec 159).
 *
 * POST /api/webhooks/resend
 *
 * Handles delivery/engagement events from Resend for invoice emails.
 * Mounted via webhookRoutes (already behind RATE_LIMITER_WEBHOOK).
 * NO authMiddleware — Resend cannot authenticate as a user session.
 *
 * Security: Svix-style HMAC-SHA256 signature verified against RESEND_WEBHOOK_SECRET.
 * Timing-safe comparison protects against timing attacks.
 *
 * Tenant resolution: derived from `data.tags.invoice_id` → look up most recent
 * `sent` event for that invoice to obtain (tenant_id, sent_by). If no prior
 * `sent` exists, ignore quietly (200).
 */
import { Hono } from 'hono'
import { timingSafeEqual } from '@zync/auth'
import {
  createDb,
  insertEmailEvent,
  hasRecentOpenEvent,
  createNotification,
  getLastSentEventForInvoice,
  getInvoice,
} from '@zync/db/queries'
import type { AppEnv } from '../../types'
import type { InvoiceEmailEventType } from '@zync/db/queries'
import { claimInboundWebhookEvent } from '../../lib/webhook-idempotency'

// ── HMAC helpers ──────────────────────────────────────────────────────────────

async function hmacSha256(secret: string, data: string): Promise<string> {
  const keyBytes = new TextEncoder().encode(secret)
  const dataBytes = new TextEncoder().encode(data)
  const key = await crypto.subtle.importKey(
    'raw',
    keyBytes,
    { name: 'HMAC', hash: 'SHA-256' },
    false,
    ['sign'],
  )
  const sigBuf = await crypto.subtle.sign('HMAC', key, dataBytes)
  return Array.from(new Uint8Array(sigBuf))
    .map((b) => b.toString(16).padStart(2, '0'))
    .join('')
}

// ── Event type mapping ────────────────────────────────────────────────────────

const RESEND_EVENT_MAP: Record<string, InvoiceEmailEventType> = {
  'email.delivered': 'delivered',
  'email.opened': 'opened',
  'email.clicked': 'clicked',
  'email.bounced': 'bounced',
}

// ── Router ─────────────────────────────────────────────────────────────────────

export const resendWebhookRouter = new Hono<AppEnv>()

resendWebhookRouter.post('/', async (c) => {
  // Read raw body for signature verification
  const rawBody = await c.req.text()

  // Verify Resend Svix-style signature
  // Resend sends: svix-id, svix-timestamp, svix-signature headers
  const svixId = c.req.header('svix-id') ?? ''
  const svixTimestamp = c.req.header('svix-timestamp') ?? ''
  const svixSignature = c.req.header('svix-signature') ?? ''

  const webhookSecret = c.env.RESEND_WEBHOOK_SECRET
  if (!webhookSecret) {
    console.error('RESEND_WEBHOOK_SECRET not configured')
    return c.json({ error: 'Server misconfigured' }, 500)
  }

  // Replay prevention: svix-timestamp must be within ±5 minutes
  const tsMs = parseInt(svixTimestamp, 10) * 1000
  if (isNaN(tsMs) || Math.abs(Date.now() - tsMs) > 5 * 60 * 1000) {
    return c.json({ error: 'Request timestamp out of range' }, 401)
  }

  // Construct signed payload per Svix spec: "{id}.{timestamp}.{body}"
  const signedPayload = `${svixId}.${svixTimestamp}.${rawBody}`
  // Svix secrets are "whsec_base64" — strip prefix and decode
  const secretBase64 = webhookSecret.startsWith('whsec_')
    ? webhookSecret.slice(6)
    : webhookSecret
  const expectedSig = await hmacSha256(secretBase64, signedPayload)

  // svix-signature can contain multiple sigs (e.g. "v1,abc v1,def")
  const signatures = svixSignature.split(' ')
  const matched = await Promise.all(
    signatures.map(async (sigEntry) => {
      const [, sigHex] = sigEntry.split(',')
      if (!sigHex) return false
      return timingSafeEqual(sigHex, expectedSig)
    }),
  )

  if (!matched.some(Boolean)) {
    return c.json({ error: 'Invalid signature' }, 401)
  }

  const dedupeId = svixId || undefined
  if (dedupeId) {
    const claimed = await claimInboundWebhookEvent(c.env.KV, 'resend', dedupeId)
    if (!claimed) {
      return c.json({ ok: true }, 200)
    }
  }

  // Parse event body
  let event: { type: string; data: Record<string, unknown> }
  try {
    event = JSON.parse(rawBody) as { type: string; data: Record<string, unknown> }
  } catch {
    return c.json({ error: 'Invalid JSON' }, 400)
  }

  const eventType = RESEND_EVENT_MAP[event.type]
  if (!eventType) {
    // Not an event we handle — acknowledge quietly
    return c.json({ ok: true }, 200)
  }

  // Extract invoice_id from tags
  const tags = event.data?.tags as Record<string, string> | undefined
  const invoiceId = tags?.invoice_id
  if (!invoiceId) {
    // Not an invoice email — ignore
    return c.json({ ok: true }, 200)
  }

  const toAddress = (event.data?.to as string | undefined) ?? ''
  const resendId = (event.data?.email_id as string | undefined) ?? ''

  const db = createDb(c.env)

  // Resolve tenant + sent_by from the most recent 'sent' event for this invoice
  const lastSentEvent = await getLastSentEventForInvoice(db, invoiceId)

  if (!lastSentEvent) {
    // No prior sent event — ignore this webhook
    return c.json({ ok: true }, 200)
  }

  const { tenantId, sentBy } = lastSentEvent

  // Deduplicate opened events within 1-hour window
  if (eventType === 'opened') {
    const isDupe = await hasRecentOpenEvent(db, {
      tenantId,
      invoiceId,
      toAddress,
      withinMs: 3_600_000,
    })
    if (isDupe) {
      return c.json({ ok: true }, 200)
    }
  }

  // Build metadata from event payload
  const metadata: Record<string, unknown> = { resend_id: resendId }
  if (event.data?.user_agent) metadata.user_agent = event.data.user_agent
  if (event.data?.link) metadata.link = event.data.link
  const bounce = event.data?.bounce as Record<string, unknown> | undefined
  if (bounce?.code) metadata.bounce_code = bounce.code
  if (bounce?.message) metadata.bounce_reason = bounce.message

  // Insert the event
  const insertedEvent = await insertEmailEvent(db, {
    tenantId,
    invoiceId,
    sentBy,
    toAddress,
    eventType,
    metadata,
  })

  // For bounced events: create in-app notification for the invoice owner
  if (eventType === 'bounced') {
    try {
      // Look up the invoice to get createdBy (the owner to notify)
      const invoice = await getInvoice(db, tenantId, invoiceId)

      if (invoice?.createdBy) {
        const invoiceNumber = invoice.invoiceNumber ?? invoice.proformaNumber ?? invoiceId
        await createNotification(db, {
          tenantId,
          userId: invoice.createdBy,
          type: 'invoice_email_bounced',
          titleKey: 'notification.invoice_email_bounced.title',
          bodyKey: 'notification.invoice_email_bounced.body',
          params: { invoiceNumber, address: toAddress },
          entityType: 'invoice',
          entityId: invoiceId,
        })
      }
    } catch {
      // Non-fatal: bounce notification failure must not affect the webhook 200 response
    }
  }

  void insertedEvent // used to confirm insert succeeded

  return c.json({ ok: true }, 200)
})
