import crypto from 'crypto'

type WebhookStatus = 'PROCESSING' | 'PROCESSED' | 'FAILED'

type WebhookProcessInput<T> = {
  provider: string
  providerEventId: string
  eventType: string
  rawBody: string | Buffer
  handle: () => Promise<T>
}

function payloadHash(rawBody: string | Buffer) {
  return crypto.createHash('sha256').update(rawBody).digest('hex')
}

function getWebhookEventModel() {
  try {
    // Lazy load keeps isolated unit tests from requiring a generated Prisma client
    // before `prisma generate` has run in the local sandbox.
    // eslint-disable-next-line @typescript-eslint/no-var-requires
    const { prisma } = require('../lib/prisma')
    return (prisma as any).webhookEvent as any | undefined
  } catch {
    return undefined
  }
}

async function findExisting(model: any, provider: string, providerEventId: string) {
  return model.findUnique({
    where: { provider_providerEventId: { provider, providerEventId } },
  })
}

async function mark(model: any, id: string, status: WebhookStatus, errorMessage?: string) {
  await model.update({
    where: { id },
    data: {
      status,
      processedAt: status === 'PROCESSED' ? new Date() : undefined,
      errorMessage: errorMessage?.slice(0, 1000),
    },
  })
}

export async function processWebhookOnce<T>({
  provider,
  providerEventId,
  eventType,
  rawBody,
  handle,
}: WebhookProcessInput<T>): Promise<{ duplicate: boolean; result?: T }> {
  const model = getWebhookEventModel()
  if (!model) {
    const result = await handle()
    return { duplicate: false, result }
  }

  const hash = payloadHash(rawBody)
  let eventRecord: any

  try {
    eventRecord = await model.create({
      data: {
        provider,
        providerEventId,
        eventType,
        status: 'PROCESSING',
        payloadHash: hash,
      },
    })
  } catch (err: any) {
    const existing = await findExisting(model, provider, providerEventId)
    if (existing?.status === 'PROCESSED') return { duplicate: true }
    if (existing?.payloadHash && existing.payloadHash !== hash) {
      throw new Error('Webhook event ID replayed with a different payload hash')
    }
    if (existing?.status === 'PROCESSING') return { duplicate: true }
    eventRecord = existing
  }

  try {
    const result = await handle()
    if (eventRecord?.id) await mark(model, eventRecord.id, 'PROCESSED')
    return { duplicate: false, result }
  } catch (err: any) {
    if (eventRecord?.id) await mark(model, eventRecord.id, 'FAILED', err?.message)
    throw err
  }
}

export function getWebhookEventId(provider: 'amanpay' | 'paypal', payload: any) {
  if (provider === 'paypal') {
    return String(payload.id ?? payload.event_id ?? `${payload.event_type ?? 'unknown'}:${payload.resource?.id ?? 'missing'}`)
  }
  return String(payload.event_id ?? payload.id ?? payload.transaction_id ?? 'missing')
}
