/**
 * /v1/webhooks routes — zapier-make-integration (wave-13).
 * REST-hook subscribe/unsubscribe proxy for Zapier and Make.
 *
 * POST   /v1/webhooks       — subscribe (Zapier REST-hook subscribe)
 * DELETE /v1/webhooks/:id   — unsubscribe
 * GET    /v1/webhooks       — list tenant endpoints
 *
 * Auth: scope `events:read`.
 * Tier: Business+ required.
 *
 * The `event` string MUST be a member of the white-label-api event catalog.
 * Non-catalog events are rejected 422 validation_error.
 */
import { z } from 'zod'
import { webhookEndpoints } from '@zync/db'
import { eq, and } from 'drizzle-orm'
import { validateSafeOutboundUrl } from '@zync/utils'
import { hasScope, errForbiddenScope } from '../index'
import type { PublicApiContext } from '../app'

/**
 * Full event catalog — union of white-label-api catalog plus
 * zapier-make-integration delta (lead.converted, time_entry.approved).
 * Task 13 adds these two events; they are enumerated here for validation.
 */
export const WEBHOOK_EVENT_CATALOG = new Set([
  // invoices
  'invoice.proforma_approved',
  'invoice.issued',
  'invoice.paid',
  'invoice.overdue',
  'invoice.voided',
  'invoice.created',
  'invoice.sent',
  // leads (marketing-leads-pipeline)
  'lead.created',
  'lead.stage_updated',
  'lead.converted',   // catalog delta — Task 13
  // tickets
  'ticket.created',
  'ticket.replied',
  'ticket.resolved',
  // time entries (time-management)
  'time_entry.approved', // catalog delta — Task 13
  // expenses
  'expense.uploaded',
  'expense.processed',
  'expense.failed',
  // customers
  'customer.created',
  'customer.updated',
  // projects
  'project.created',
  'project.status_changed',
  // payments
  'payment.received',
])

const createWebhookSchema = z.object({
  event: z.string().min(1),
  target_url: z.string().url(),
  name: z.string().min(1),
})

/** Generate a random AES-256-GCM secret stub (plaintext; production would encrypt with INTEGRATION_ENCRYPTION_KEY). */
function generateSigningSecret(): string {
  const bytes = new Uint8Array(32)
  crypto.getRandomValues(bytes)
  return Array.from(bytes).map((b) => b.toString(16).padStart(2, '0')).join('')
}

/** POST /v1/webhooks */
export async function subscribeWebhook(ctx: PublicApiContext): Promise<Response> {
  if (!hasScope(ctx.scopes, 'events:read')) return errForbiddenScope('events:read')
  const { db, tenantId } = ctx

  let body: unknown
  try {
    body = await ctx.request.json()
  } catch {
    return new Response(JSON.stringify({ error: 'validation_error', message: 'Invalid JSON body' }), {
      status: 422, headers: { 'Content-Type': 'application/json' },
    })
  }

  const parsed = createWebhookSchema.safeParse(body)
  if (!parsed.success) {
    return new Response(
      JSON.stringify({
        error: 'validation_error',
        message: parsed.error.issues[0]?.message ?? 'Invalid input',
        field: parsed.error.issues[0]?.path.join('.'),
      }),
      { status: 422, headers: { 'Content-Type': 'application/json' } },
    )
  }

  const { event, target_url, name } = parsed.data

  // Validate event is in catalog
  if (!WEBHOOK_EVENT_CATALOG.has(event)) {
    return new Response(
      JSON.stringify({
        error: 'validation_error',
        message: `Event '${event}' is not in the event catalog`,
        field: 'event',
      }),
      { status: 422, headers: { 'Content-Type': 'application/json' } },
    )
  }

  const urlCheck = validateSafeOutboundUrl(target_url)
  if (!urlCheck.ok) {
    return new Response(
      JSON.stringify({
        error: 'validation_error',
        message: urlCheck.reason,
        field: 'target_url',
      }),
      { status: 422, headers: { 'Content-Type': 'application/json' } },
    )
  }

  const secretEncrypted = generateSigningSecret()

  const [inserted] = await db
    .insert(webhookEndpoints)
    .values({
      tenantId,
      url: target_url,
      events: [event],
      secretEncrypted,
      isActive: true,
      description: name,
    })
    .returning({ id: webhookEndpoints.id })

  if (!inserted) {
    return new Response(JSON.stringify({ error: 'internal_error', message: 'Failed to create webhook endpoint' }), {
      status: 500, headers: { 'Content-Type': 'application/json' },
    })
  }

  return new Response(JSON.stringify({ id: inserted.id }), {
    status: 201, headers: { 'Content-Type': 'application/json' },
  })
}

/** DELETE /v1/webhooks/:id */
export async function unsubscribeWebhook(ctx: PublicApiContext, id: string): Promise<Response> {
  if (!hasScope(ctx.scopes, 'events:read')) return errForbiddenScope('events:read')
  const { db, tenantId } = ctx

  const [existing] = await db
    .select({ id: webhookEndpoints.id })
    .from(webhookEndpoints)
    .where(and(eq(webhookEndpoints.id, id), eq(webhookEndpoints.tenantId, tenantId)))
    .limit(1)

  if (!existing) {
    return new Response(JSON.stringify({ error: 'not_found', message: 'Webhook endpoint not found' }), {
      status: 404, headers: { 'Content-Type': 'application/json' },
    })
  }

  await db
    .delete(webhookEndpoints)
    .where(and(eq(webhookEndpoints.id, id), eq(webhookEndpoints.tenantId, tenantId)))

  return new Response(null, { status: 204 })
}

/** GET /v1/webhooks */
export async function listWebhooks(ctx: PublicApiContext): Promise<Response> {
  if (!hasScope(ctx.scopes, 'events:read')) return errForbiddenScope('events:read')
  const { db, tenantId } = ctx

  const rows = await db
    .select({
      id: webhookEndpoints.id,
      events: webhookEndpoints.events,
      url: webhookEndpoints.url,
      isActive: webhookEndpoints.isActive,
      description: webhookEndpoints.description,
      createdAt: webhookEndpoints.createdAt,
    })
    .from(webhookEndpoints)
    .where(eq(webhookEndpoints.tenantId, tenantId))
    .limit(100)

  const data = rows.map((r) => ({
    id: r.id,
    event: Array.isArray(r.events) ? (r.events as string[])[0] ?? null : null,
    url: r.url,
    is_active: r.isActive,
    name: r.description ?? null,
    created_at: r.createdAt.toISOString(),
  }))

  return new Response(JSON.stringify({ data, meta: { total: data.length } }), {
    status: 200, headers: { 'Content-Type': 'application/json' },
  })
}
