import crypto from 'crypto'
import { Prisma } from '@prisma/client'
import { prisma } from '@/lib/prisma'

/**
 * MOPC Webhook Signature Verification
 * ====================================
 *
 * Every outbound webhook delivery is signed with HMAC-SHA256 using the
 * webhook's shared secret. The signature is sent in the `X-Webhook-Signature`
 * header with a `sha256=` prefix.
 *
 * Additional headers included with each delivery:
 *   - X-Webhook-Event:     the event type (e.g. "evaluation.submitted")
 *   - X-Webhook-Delivery:  unique delivery ID (UUID)
 *
 * To verify a delivery on the consumer side:
 *
 *   // Node.js example
 *   const crypto = require('crypto');
 *
 *   function verifySignature(secret, body, signatureHeader) {
 *     const expected = 'sha256=' + crypto
 *       .createHmac('sha256', secret)
 *       .update(body, 'utf8')    // raw request body string
 *       .digest('hex');
 *     return crypto.timingSafeEqual(
 *       Buffer.from(expected),
 *       Buffer.from(signatureHeader),
 *     );
 *   }
 *
 *   // In your handler:
 *   const sig = req.headers['x-webhook-signature'];
 *   if (!verifySignature(WEBHOOK_SECRET, rawBody, sig)) {
 *     return res.status(401).send('Invalid signature');
 *   }
 *
 * IMPORTANT:
 *   - Always verify against the raw request body (before JSON parsing).
 *   - Use timing-safe comparison to prevent timing attacks.
 *   - The secret can be regenerated via the admin UI (Settings → Webhooks).
 */

/**
 * Dispatch a webhook event to all active webhooks subscribed to this event.
 */
export async function dispatchWebhookEvent(
  event: string,
  payload: Record<string, unknown>
): Promise<number> {
  const webhooks = await prisma.webhook.findMany({
    where: {
      isActive: true,
      events: { has: event },
    },
  })

  if (webhooks.length === 0) return 0

  let deliveryCount = 0

  for (const webhook of webhooks) {
    try {
      const delivery = await prisma.webhookDelivery.create({
        data: {
          webhookId: webhook.id,
          event,
          payload: payload as Prisma.InputJsonValue,
          status: 'PENDING',
          attempts: 0,
        },
      })

      // Attempt delivery asynchronously (don't block the caller)
      deliverWebhook(delivery.id).catch((err) => {
        console.error(`[Webhook] Background delivery failed for ${delivery.id}:`, err)
      })

      deliveryCount++
    } catch (error) {
      console.error(`[Webhook] Failed to create delivery for webhook ${webhook.id}:`, error)
    }
  }

  return deliveryCount
}

/**
 * Attempt to deliver a single webhook.
 */
export async function deliverWebhook(deliveryId: string): Promise<void> {
  const delivery = await prisma.webhookDelivery.findUnique({
    where: { id: deliveryId },
    include: { webhook: true },
  })

  if (!delivery || !delivery.webhook) {
    console.error(`[Webhook] Delivery ${deliveryId} not found`)
    return
  }

  const { webhook } = delivery
  const payloadStr = JSON.stringify(delivery.payload)

  // Sign payload with HMAC-SHA256
  const signature = crypto
    .createHmac('sha256', webhook.secret)
    .update(payloadStr)
    .digest('hex')

  // Build headers
  const headers: Record<string, string> = {
    'Content-Type': 'application/json',
    'X-Webhook-Signature': `sha256=${signature}`,
    'X-Webhook-Event': delivery.event,
    'X-Webhook-Delivery': delivery.id,
  }

  // Merge custom headers from webhook config
  if (webhook.headers && typeof webhook.headers === 'object') {
    const customHeaders = webhook.headers as Record<string, string>
    for (const [key, value] of Object.entries(customHeaders)) {
      if (typeof value === 'string') {
        headers[key] = value
      }
    }
  }

  try {
    const controller = new AbortController()
    const timeout = setTimeout(() => controller.abort(), 30000) // 30s timeout

    const response = await fetch(webhook.url, {
      method: 'POST',
      headers,
      body: payloadStr,
      signal: controller.signal,
    })

    clearTimeout(timeout)

    const responseBody = await response.text().catch(() => '')

    await prisma.webhookDelivery.update({
      where: { id: deliveryId },
      data: {
        status: response.ok ? 'DELIVERED' : 'FAILED',
        responseStatus: response.status,
        responseBody: responseBody.slice(0, 4000), // Truncate long responses
        attempts: delivery.attempts + 1,
        lastAttemptAt: new Date(),
      },
    })
  } catch (error) {
    const errorMessage = error instanceof Error ? error.message : 'Unknown error'

    await prisma.webhookDelivery.update({
      where: { id: deliveryId },
      data: {
        status: 'FAILED',
        responseBody: errorMessage.slice(0, 4000),
        attempts: delivery.attempts + 1,
        lastAttemptAt: new Date(),
      },
    })
  }
}

/**
 * Retry all failed webhook deliveries that haven't exceeded max retries.
 * Called by cron.
 */
export async function retryFailedDeliveries(): Promise<{
  retried: number
  errors: number
}> {
  let retried = 0
  let errors = 0

  const failedDeliveries = await prisma.webhookDelivery.findMany({
    where: {
      status: 'FAILED',
    },
    include: {
      webhook: {
        select: { maxRetries: true, isActive: true },
      },
    },
  })

  for (const delivery of failedDeliveries) {
    // Skip if webhook is inactive or max retries exceeded
    if (!delivery.webhook.isActive) continue
    if (delivery.attempts >= delivery.webhook.maxRetries) continue

    try {
      await deliverWebhook(delivery.id)
      retried++
    } catch (error) {
      console.error(`[Webhook] Retry failed for delivery ${delivery.id}:`, error)
      errors++
    }
  }

  return { retried, errors }
}

/**
 * Generate a random HMAC secret for webhook signing.
 */
export function generateWebhookSecret(): string {
  return crypto.randomBytes(32).toString('hex')
}
