/**
 * Scheduling inbound webhook routes — calendar-module.
 * Handles bookings from Calendly, Acuity, and moCal.
 *
 * POST /webhooks/scheduling/calendly
 * POST /webhooks/scheduling/acuity
 * POST /webhooks/scheduling/mocal
 *
 * HMAC-verified. Creates a calendar_events row, links to customer_contacts email match,
 * optionally enqueues task/ticket creation, emits calendar.booking_created notification.
 */
import { Hono } from 'hono'
import type { Context } from 'hono'
import type { AppEnv } from '../../types'
import {
  getSchedulingConnectionByTenant,
  createCalendarEvent,
  findCustomerByContactEmail,
  getTenantFirstMemberId,
  createNotification,
} from '@zync/db/queries'
import { verifyHmacSignature, decryptToken } from '@zync/calendar/server'

export const schedulingWebhooksRoute = new Hono<AppEnv>()

// ── Shared inbound booking handler ────────────────────────────────────────────

async function handleBookingWebhook(
  c: Context<AppEnv>,
  providerName: 'calendly' | 'acuity' | 'mocal',
): Promise<Response> {
  const rateLimiter = c.env.RATE_LIMITER_WEBHOOK
  if (rateLimiter) {
    const result = await rateLimiter.limit({ key: `calendar-scheduling-${providerName}` })
    if (!result.success) {
      return c.text('Rate limit exceeded', 429)
    }
  }

  const rawBody = await c.req.text()
  const encryptionKey = c.env.INTEGRATION_ENCRYPTION_KEY
  if (!encryptionKey) {
    return c.text('Server misconfiguration', 500)
  }

  // Derive tenantId from provider-specific header
  let tenantId: string | null = null
  if (providerName === 'calendly') {
    // Calendly sends X-Calendly-Webhook-Subscription-Uuid; we store it as a KV key
    const subId = c.req.header('X-Calendly-Webhook-Subscription-Uuid')
    if (subId && c.env.KV) {
      tenantId = await c.env.KV.get(`calendly_tenant:${subId}`)
    }
  } else if (providerName === 'acuity') {
    tenantId = c.req.header('X-Acuity-Tenant-Id') ?? null
  } else if (providerName === 'mocal') {
    tenantId = c.req.header('X-Mocal-Tenant-Id') ?? null
  }

  if (!tenantId) {
    return c.text('Cannot identify tenant', 400)
  }

  const db = c.get('db')
  const conn = await getSchedulingConnectionByTenant(db, tenantId, providerName)
  if (!conn) {
    return c.text('No scheduling connection found', 401)
  }

  // Decrypt API key and verify HMAC
  const apiKey = await decryptToken(conn.apiKey, encryptionKey)

  // Determine HMAC signature header by provider
  const signatureHeader =
    providerName === 'calendly'
      ? (c.req.header('Calendly-Webhook-Signature') ?? '')
      : providerName === 'acuity'
      ? (c.req.header('X-Acuity-Signature') ?? '')
      : (c.req.header('X-Mocal-Signature') ?? '')

  const valid = await verifyHmacSignature(apiKey, rawBody, signatureHeader)
  if (!valid) {
    return c.text('Invalid signature', 401)
  }

  // Parse the booking payload
  let booking: {
    title?: string
    start?: string
    end?: string
    inviteeEmail?: string
    allDay?: boolean
  }

  try {
    const parsed = JSON.parse(rawBody) as Record<string, unknown>
    if (providerName === 'calendly') {
      const payload = parsed.payload as Record<string, unknown>
      const eventType = payload?.event_type as Record<string, unknown>
      const startTime = (payload?.start_time ?? (payload?.scheduled_event as Record<string, unknown>)?.start_time) as string
      const endTime = (payload?.end_time ?? (payload?.scheduled_event as Record<string, unknown>)?.end_time) as string
      booking = {
        title: (eventType?.name as string) ?? 'Calendly Booking',
        start: startTime,
        end: endTime,
        inviteeEmail: (payload?.invitee_email as string) ?? undefined,
      }
    } else if (providerName === 'acuity') {
      booking = {
        title: (parsed.type as string) ?? 'Acuity Appointment',
        start: parsed.datetime as string,
        end: parsed.datetimeEnd as string,
        inviteeEmail: parsed.email as string,
      }
    } else {
      // mocal
      booking = {
        title: (parsed.title as string) ?? 'moCal Booking',
        start: parsed.start as string,
        end: parsed.end as string,
        inviteeEmail: parsed.inviteeEmail as string,
      }
    }
  } catch {
    return c.text('Invalid payload', 400)
  }

  if (!booking.start || !booking.end) {
    return c.text('Missing start/end time', 400)
  }

  // Match invitee email to a customer contact (best-effort)
  let customerId: string | null = null
  if (booking.inviteeEmail) {
    customerId = await findCustomerByContactEmail(db, tenantId, booking.inviteeEmail).catch(() => null)
  }

  // Resolve a createdBy user from the tenant's first member — bail if none found (FK constraint)
  const createdBy = await getTenantFirstMemberId(db, tenantId).catch(() => null)
  if (!createdBy) {
    return c.json({ received: true }, 200) // tenant has no members; skip insert
  }

  const calendarEvent = await createCalendarEvent(db, {
    tenantId,
    createdBy,
    title: booking.title ?? 'Booking',
    description: `Booked via ${providerName}${booking.inviteeEmail ? ` by ${booking.inviteeEmail}` : ''}`,
    startAt: new Date(booking.start),
    endAt: new Date(booking.end),
    allDay: booking.allDay ?? false,
    source: providerName,
    customerId,
    syncStatus: 'local',
  })

  // Retrieve post-booking action from settings
  const settings = conn.settings as Record<string, unknown> | null
  const action = (settings?.action as string | undefined) ?? 'event'

  // Enqueue task/ticket creation if configured
  if ((action === 'event_task' || action === 'event_ticket') && c.env.QUEUE) {
    void c.env.QUEUE.send({
      type: action === 'event_task' ? 'calendar.booking.create_task' : 'calendar.booking.create_ticket',
      tenantId,
      eventId: calendarEvent.id,
      customerId,
      title: calendarEvent.title,
      startAt: calendarEvent.startAt.toISOString(),
    }).catch(() => {/* non-fatal */})
  }

  // Emit calendar.booking_created notification (best-effort)
  await createNotification(db, {
    tenantId,
    userId: createdBy,
    type: 'calendar.booking_created',
    titleKey: 'calendar.booking_created.title',
    bodyKey: 'calendar.booking_created.body',
    params: {
      provider: providerName,
      title: calendarEvent.title,
      startAt: calendarEvent.startAt.toISOString(),
    },
    entityType: 'calendar_event',
    entityId: calendarEvent.id,
  }).catch(() => {/* non-fatal */})

  return c.json({ received: true, eventId: calendarEvent.id }, 200)
}

// ── Route registrations ───────────────────────────────────────────────────────

schedulingWebhooksRoute.post('/webhooks/scheduling/calendly', (c) =>
  handleBookingWebhook(c, 'calendly'),
)

schedulingWebhooksRoute.post('/webhooks/scheduling/acuity', (c) =>
  handleBookingWebhook(c, 'acuity'),
)

schedulingWebhooksRoute.post('/webhooks/scheduling/mocal', (c) =>
  handleBookingWebhook(c, 'mocal'),
)
