/**
 * Public-facing routes — marketing-leads-pipeline.
 * Mounted at /api (no auth) by the integrator in apps/zync-api/src/index.ts.
 *
 * Routes:
 *   GET  /api/forms/:slug/public         form config for public form page (zync-www renders this)
 *   POST /api/forms/:slug                form submission (no auth, rate-limited)
 *   POST /api/webhooks/leads/:webhookId  inbound webhook (HMAC-verified)
 *   GET  /api/webhooks/leads/:webhookId  Facebook challenge-response verification
 *
 * IMPORTANT: No authMiddleware on this router. Tenant is identified by ?tenant= query
 * param (tenantSlug or tenantId). Rate limiter binding: RATE_LIMITER_LEAD_FORM.
 */
import { Hono, type Context } from 'hono'
import type { AppEnv } from '../../types'
import { createDb } from '@zync/db/queries'
import {
  getLeadFormBySlug,
  getLeadWebhookById,
  createFormSubmissionAndLead,
  createWebhookLead,
  addLeadActivity,
  touchWebhookLastReceived,
  publicFormSubmitSchema,
} from '@zync/db/queries'

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

async function decryptSecret(encrypted: string, keyHex: string): Promise<string> {
  const [ivHex, ciphertextHex] = encrypted.split(':')
  if (!ivHex || !ciphertextHex) throw new Error('Invalid encrypted format')

  const keyBytes = hexToBytes(keyHex)
  const cryptoKey = await crypto.subtle.importKey(
    'raw',
    keyBytes,
    { name: 'AES-GCM' },
    false,
    ['decrypt'],
  )
  const iv = hexToBytes(ivHex)
  const ciphertext = hexToBytes(ciphertextHex)
  const plaintext = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, cryptoKey, ciphertext)
  return new TextDecoder().decode(plaintext)
}

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

/** Constant-time comparison to prevent timing attacks */
function timingSafeEqual(a: string, b: string): boolean {
  if (a.length !== b.length) return false
  let diff = 0
  for (let i = 0; i < a.length; i++) {
    diff |= a.charCodeAt(i) ^ b.charCodeAt(i)
  }
  return diff === 0
}

function hexToBytes(hex: string): Uint8Array {
  const bytes = new Uint8Array(hex.length / 2)
  for (let i = 0; i < hex.length; i += 2) {
    bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16)
  }
  return bytes
}

// ── Per-source HMAC verification ──────────────────────────────────────────────

async function verifyWebhookSignature(
  source: string,
  secret: string,
  rawBody: string,
  headers: Record<string, string | undefined>,
  payload: Record<string, unknown>,
): Promise<boolean> {
  switch (source) {
    case 'facebook': {
      // X-Hub-Signature-256: sha256=<hex>
      const sig = headers['x-hub-signature-256']
      if (!sig || !sig.startsWith('sha256=')) return false
      const expected = await hmacSha256(secret, rawBody)
      return timingSafeEqual(sig.slice(7), expected)
    }
    case 'linkedin':
    case 'instagram':
    case 'zapier':
    case 'make':
    case 'generic': {
      if (!secret) return true
      // X-Zync-Signature: sha256=<hex>
      const sig = headers['x-zync-signature']
      if (!sig || !sig.startsWith('sha256=')) return false
      const expected = await hmacSha256(secret, rawBody)
      return timingSafeEqual(sig.slice(7), expected)
    }
    case 'google': {
      // Google posts shared secret in body field `google_key`
      const googleKey = payload['google_key']
      if (typeof googleKey !== 'string') return false
      return timingSafeEqual(googleKey, secret)
    }
    default:
      return false
  }
}

export const publicFormRoutes = new Hono<AppEnv>()

async function handlePublicFormSubmit(c: Context<AppEnv>) {
  const { slug } = c.req.param()
  if (!slug) return c.json({ error: 'Form not found' }, 404)
  const tenantParam = c.req.query('tenant')
  if (!tenantParam) return c.json({ error: 'Missing tenant parameter' }, 400)

  const db = createDb(c.env)
  const body = await c.req.json().catch(() => null)
  const inputParsed = publicFormSubmitSchema.safeParse(body)
  if (!inputParsed.success) return c.json({ error: 'Validation failed', issues: inputParsed.error.issues }, 400)
  const input = inputParsed.data

  const form = await getLeadFormBySlug(db, tenantParam, slug)
  if (!form) return c.json({ error: 'Form not found' }, 404)

  const rateLimiter = (c.env as { RATE_LIMITER_LEAD_FORM?: { limit: (opts: { key: string }) => Promise<{ success: boolean }> } }).RATE_LIMITER_LEAD_FORM
  if (rateLimiter) {
    const ip = c.req.header('CF-Connecting-IP') ?? c.req.header('X-Forwarded-For') ?? 'unknown'
    const result = await rateLimiter.limit({ key: `form:${form.id}:${ip}` })
    if (!result.success) {
      return c.json({ error: 'Too many requests' }, 429)
    }
  }

  const utmInput = {
    ...input,
    utm_source: c.req.query('utm_source') ?? input.utm_source,
    utm_medium: c.req.query('utm_medium') ?? input.utm_medium,
    utm_campaign: c.req.query('utm_campaign') ?? input.utm_campaign,
    utm_content: c.req.query('utm_content') ?? input.utm_content,
    utm_term: c.req.query('utm_term') ?? input.utm_term,
  }

  const fields = form.fields as Array<{ id: string; required: boolean; label: string }>
  for (const field of fields) {
    if (field.required) {
      const val = utmInput.payload[field.id]
      if (val === undefined || val === null || val === '') {
        return c.json({ error: `Field '${field.label}' is required`, field: field.id }, 422)
      }
    }
  }

  const ip = c.req.header('CF-Connecting-IP') ?? c.req.header('X-Forwarded-For') ?? null
  const userAgent = c.req.header('User-Agent') ?? null
  const referrer = c.req.header('Referer') ?? null

  const { lead, submissionId } = await createFormSubmissionAndLead(
    db,
    form.tenantId,
    form,
    utmInput,
    { ip: ip ?? undefined, userAgent: userAgent ?? undefined, referrer: referrer ?? undefined },
  )

  const ae = (c.env as { ANALYTICS_ENGINE?: { writeDataPoint: (dp: Record<string, unknown>) => void } }).ANALYTICS_ENGINE
  if (ae) {
    ae.writeDataPoint({
      blobs: [
        lead.tenantId,
        form.id,
        lead.id,
        'form',
        utmInput.utm_source ?? '',
        utmInput.utm_medium ?? '',
        utmInput.utm_campaign ?? '',
      ],
      indexes: ['lead_captured'],
    })
  }

  return c.json({
    ok: true,
    leadId: lead.id,
    submissionId,
    redirectUrl: form.redirectUrl ?? null,
  })
}

// ── Public form config (for zync-www renderer) ────────────────────────────────

// GET /api/forms/:slug/public?tenant=<tenantSlug>
publicFormRoutes.get('/forms/:slug/public', async (c) => {
  const { slug } = c.req.param()
  const tenantParam = c.req.query('tenant')
  if (!tenantParam) return c.json({ error: 'Missing tenant parameter' }, 400)

  const db = createDb(c.env)
  // tenantParam may be tenantSlug or tenantId — resolve by trying UUID first
  const uuidRe = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
  const tenantId = uuidRe.test(tenantParam) ? tenantParam : tenantParam

  const form = await getLeadFormBySlug(db, tenantId, slug)
  if (!form) return c.json({ error: 'Form not found' }, 404)

  // Return only safe, public fields — no secrets, no internal IDs beyond form metadata
  return c.json({
    form: {
      id: form.id,
      name: form.name,
      slug: form.slug,
      fields: form.fields,
      redirectUrl: form.redirectUrl,
      style: form.style,
    },
  })
})

// ── Form submission handler ───────────────────────────────────────────────────

// POST /api/forms/:slug?tenant=<tenantSlug>
publicFormRoutes.post('/forms/:slug', handlePublicFormSubmit)
publicFormRoutes.post('/forms/:slug/submit', handlePublicFormSubmit)

// ── Facebook webhook verification (GET) ───────────────────────────────────────

// GET /api/webhooks/leads/:webhookId
publicFormRoutes.get('/webhooks/leads/:webhookId', async (c) => {
  const { webhookId } = c.req.param()
  const mode = c.req.query('hub.mode')
  const token = c.req.query('hub.verify_token')
  const challenge = c.req.query('hub.challenge')

  if (!mode || !token || !challenge) {
    return c.json({ error: 'Missing verification parameters' }, 400)
  }
  if (mode !== 'subscribe') {
    return c.json({ error: 'Invalid hub.mode' }, 400)
  }

  const db = createDb(c.env)
  const webhook = await getLeadWebhookById(db, webhookId)
  if (!webhook || !webhook.isActive) return c.json({ error: 'Not found' }, 404)

  const encKey = (c.env as { INTEGRATION_ENCRYPTION_KEY?: string }).INTEGRATION_ENCRYPTION_KEY
  if (!encKey) return c.json({ error: 'Encryption key not configured' }, 500)

  const secret = await decryptSecret(webhook.secret, encKey)

  if (!timingSafeEqual(token, secret)) {
    return c.json({ error: 'Token mismatch' }, 403)
  }

  // Facebook expects plain text echo of the challenge
  return c.text(challenge)
})

// ── Inbound webhook receiver (POST) ───────────────────────────────────────────

// POST /api/webhooks/leads/:webhookId
publicFormRoutes.post('/webhooks/leads/:webhookId', async (c) => {
  const { webhookId } = c.req.param()

  const db = createDb(c.env)
  const webhook = await getLeadWebhookById(db, webhookId)
  if (!webhook || !webhook.isActive) return c.json({ error: 'Not found' }, 404)

  const encKey = (c.env as { INTEGRATION_ENCRYPTION_KEY?: string }).INTEGRATION_ENCRYPTION_KEY
  if (!encKey) return c.json({ error: 'Encryption key not configured' }, 500)

  const secret = await decryptSecret(webhook.secret, encKey)

  // Read raw body for HMAC verification
  const rawBody = await c.req.text()
  let payload: Record<string, unknown>
  try {
    payload = JSON.parse(rawBody)
  } catch {
    return c.json({ error: 'Invalid JSON body' }, 400)
  }

  const headers: Record<string, string | undefined> = {
    'x-hub-signature-256': c.req.header('x-hub-signature-256'),
    'x-zync-signature': c.req.header('x-zync-signature'),
  }

  const valid = await verifyWebhookSignature(webhook.source, secret, rawBody, headers, payload)
  if (!valid) {
    return c.json({ error: 'Signature verification failed' }, 403)
  }

  // Touch last_received_at
  await touchWebhookLastReceived(db, webhook.tenantId, webhookId)

  // Create lead from webhook payload
  const lead = await createWebhookLead(db, webhook.tenantId, webhook, payload)

  // Log activity
  await addLeadActivity(db, webhook.tenantId, lead.id, null, {
    type: 'webhook_received',
    metadata: { webhookId, source: webhook.source, payloadKeys: Object.keys(payload) },
  })

  // Emit AE event (fire-and-forget)
  const ae = (c.env as { ANALYTICS_ENGINE?: { writeDataPoint: (dp: Record<string, unknown>) => void } }).ANALYTICS_ENGINE
  if (ae) {
    ae.writeDataPoint({
      blobs: [
        lead.tenantId,
        webhookId,
        lead.id,
        webhook.source,
        lead.utmSource ?? '',
        lead.utmMedium ?? '',
        lead.utmCampaign ?? '',
      ],
      indexes: ['lead_captured'],
    })
  }

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