import { pageArgs, prismaClient, prismaJson } from './prisma.mjs'
import { checkStoredFileExists } from './file-storage.mjs'

const severityRank = { critical: 1, error: 2, warning: 3, info: 4 }

function linkedHrefForEvent(event) {
  const resourceType = String(event?.resourceType || '')
  const resourceId = String(event?.resourceId || '')
  if (!resourceId) return ''
  if (resourceType === 'payment_order' || resourceId.startsWith('PAY')) return `/admin/payments?selected=${encodeURIComponent(resourceId)}`
  if (resourceType === 'order' || resourceId.startsWith('ORD') || resourceId.startsWith('CKO')) return `/admin/orders/${encodeURIComponent(resourceId)}`
  if (resourceType === 'invoice_application' || resourceId.startsWith('INV')) return `/admin/invoices/${encodeURIComponent(resourceId)}`
  if (resourceType === 'consulting_request' || resourceId.startsWith('CON')) return `/admin/consulting-requests?selected=${encodeURIComponent(resourceId)}`
  if (resourceType === 'data_service_request' || resourceId.startsWith('DSR')) return `/admin/data-service/requests?selected=${encodeURIComponent(resourceId)}`
  if (resourceType === 'expert_booking' || resourceId.startsWith('EXB')) return `/admin/expert-bookings?selected=${encodeURIComponent(resourceId)}`
  if (resourceType === 'event_registration' || resourceId.startsWith('REG')) return `/admin/events/checkin?selected=${encodeURIComponent(resourceId)}`
  if (resourceType === 'settlement_record' || resourceId.startsWith('STL')) return `/admin/settlements?selected=${encodeURIComponent(resourceId)}`
  if (resourceType === 'expert_withdrawal' || resourceId.startsWith('WDR')) return `/admin/expert-withdrawals?selected=${encodeURIComponent(resourceId)}`
  if (resourceType === 'expert_application' || resourceId.startsWith('EXA')) return `/admin/expert-applications?selected=${encodeURIComponent(resourceId)}`
  if (resourceType === 'cooperation_request' || resourceId.startsWith('COO')) return `/admin/site/cooperation/${encodeURIComponent(resourceId)}`
  return ''
}

function eventJson(event, handler = null) {
  if (!event) return null
  return {
    id: event.id,
    severity: event.severity,
    eventType: event.eventType,
    resourceType: event.resourceType || '',
    resourceId: event.resourceId || '',
    message: event.message || '',
    payload: prismaJson(event.payload, {}),
    status: event.status || (event.resolvedAt ? 'resolved' : 'open'),
    handlerNote: event.handlerNote || '',
    handledAt: event.handledAt,
    resolvedAt: event.resolvedAt,
    handlerName: handler?.displayName || '',
    linkedHref: linkedHrefForEvent(event),
    createdAt: event.createdAt,
    updatedAt: event.updatedAt || event.createdAt,
  }
}

function normalizeStatus(event) {
  return event.status || (event.resolvedAt ? 'resolved' : 'open')
}

function sortOperationalEvents(a, b) {
  const severityDelta = (severityRank[a.severity] || 5) - (severityRank[b.severity] || 5)
  if (severityDelta) return severityDelta
  return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
}

async function loadHandlers(events) {
  const prisma = prismaClient()
  const ids = [...new Set(events.map((event) => event.handledByAccountId).filter(Boolean))]
  if (!ids.length) return new Map()
  const handlers = await prisma.staffAccount.findMany({
    where: { id: { in: ids } },
    select: { id: true, displayName: true },
  })
  return new Map(handlers.map((handler) => [handler.id, handler]))
}

async function createOperationalEventIfMissing(payload = {}) {
  const prisma = prismaClient()
  const eventType = String(payload.eventType || payload.event_type || 'manual_event').trim().slice(0, 120) || 'manual_event'
  const resourceId = String(payload.resourceId || payload.resource_id || '').trim().slice(0, 160)
  const existing = await prisma.operationalEvent.findFirst({
    where: {
      eventType,
      resourceId,
      status: { in: ['open', 'acknowledged'] },
    },
    select: { id: true },
  })
  if (existing) return null
  return createOperationalEvent({ ...payload, eventType, resourceId })
}

async function refreshPaymentExceptionEvents(prisma) {
  const timeoutPayments = await prisma.$queryRaw`
    select payment_no, channel, status, expires_at
    from payment_orders
    where status in ('created', 'paying')
      and expires_at <= now()
    order by expires_at asc
    limit 100
  `
  for (const payment of timeoutPayments) {
    await createOperationalEventIfMissing({
      severity: 'warning',
      eventType: 'payment.order.timeout',
      resourceType: 'payment_order',
      resourceId: payment.payment_no,
      message: '支付单超过有效期仍未确认',
      payload: payment,
    })
  }

  const mismatchedPayments = await prisma.$queryRaw`
    select po.payment_no, po.status as payment_status, o.order_no, o.status as order_status
    from payment_orders po
    join orders o on o.id = po.order_id
    where po.status = 'succeeded'
      and o.status <> 'paid'
    order by po.updated_at desc
    limit 100
  `
  for (const payment of mismatchedPayments) {
    await createOperationalEventIfMissing({
      severity: 'critical',
      eventType: 'payment.order_state_mismatch',
      resourceType: 'payment_order',
      resourceId: payment.payment_no,
      message: '支付单已成功但订单状态未同步为已支付',
      payload: payment,
    })
  }
}

async function refreshEntitlementExceptionEvents(prisma) {
  const rows = await prisma.$queryRaw`
    select o.id, o.order_no, o.status, o.updated_at
    from orders o
    where o.status = 'paid'
      and o.updated_at < now() - interval '2 minutes'
      and exists (select 1 from order_items oi where oi.order_id = o.id)
      and not exists (select 1 from entitlements e where e.order_id = o.id)
    order by o.updated_at desc
    limit 100
  `
  for (const order of rows) {
    await createOperationalEventIfMissing({
      severity: 'critical',
      eventType: 'order.entitlement_missing',
      resourceType: 'order',
      resourceId: order.order_no,
      message: '订单已支付但权益尚未发放',
      payload: order,
    })
  }
}

async function refreshInvoiceExceptionEvents(prisma) {
  const staleInvoices = await prisma.$queryRaw`
    select invoice_no, status, created_at, amount
    from invoice_applications
    where status = 'pending'
      and created_at < now() - interval '3 days'
    order by created_at asc
    limit 100
  `
  for (const invoice of staleInvoices) {
    await createOperationalEventIfMissing({
      severity: 'warning',
      eventType: 'invoice.pending_stale',
      resourceType: 'invoice_application',
      resourceId: invoice.invoice_no,
      message: '发票申请待处理超过 3 天',
      payload: invoice,
    })
  }

  const failedInvoices = await prisma.$queryRaw`
    select invoice_no, status, failure_reason, updated_at
    from invoice_applications
    where status in ('failed', 'rejected')
    order by updated_at desc
    limit 100
  `
  for (const invoice of failedInvoices) {
    await createOperationalEventIfMissing({
      severity: invoice.status === 'failed' ? 'error' : 'warning',
      eventType: 'invoice.failed',
      resourceType: 'invoice_application',
      resourceId: invoice.invoice_no,
      message: invoice.status === 'failed' ? '发票开票失败' : '发票申请已驳回',
      payload: invoice,
    })
  }
}

async function refreshFileExceptionEvents(prisma) {
  const files = await prisma.uploadedFile.findMany({
    where: {
      storageDriver: { in: ['local', 's3'] },
      OR: [
        { storagePath: { not: '' } },
        { objectKey: { not: '' } },
      ],
    },
    orderBy: { createdAt: 'desc' },
    take: 50,
  })

  for (const file of files) {
    try {
      await checkStoredFileExists({
        id: file.id,
        storageDriver: file.storageDriver,
        storagePath: file.storagePath,
        objectKey: file.objectKey,
        bucket: file.bucket,
        contentBase64: file.contentBase64,
      })
    } catch (error) {
      await createOperationalEventIfMissing({
        severity: 'error',
        eventType: 'file.object.missing',
        resourceType: 'uploaded_file',
        resourceId: file.id,
        message: '文件记录存在，但对象存储或本地文件无法读取',
        payload: {
          code: error?.code || 'file_object_missing',
          storageDriver: file.storageDriver,
          bucket: file.bucket || '',
          objectKey: file.objectKey || '',
          storagePath: file.storagePath || '',
          ownerType: file.ownerType || '',
          ownerId: file.ownerId || '',
        },
      })
    }
  }
}

export async function refreshDerivedOperationalEvents() {
  const prisma = prismaClient()
  try {
    await refreshPaymentExceptionEvents(prisma)
    await refreshEntitlementExceptionEvents(prisma)
    await refreshInvoiceExceptionEvents(prisma)
    await refreshFileExceptionEvents(prisma)
  } catch (error) {
    if (!['P2010', 'P2021', 'P2022'].includes(error?.code)) throw error
  }
}

export async function createOperationalEvent(payload = {}) {
  const prisma = prismaClient()
  const severity = ['info', 'warning', 'error', 'critical'].includes(payload.severity) ? payload.severity : 'warning'
  const eventType = String(payload.eventType || payload.event_type || 'manual_event').trim().slice(0, 120) || 'manual_event'
  const resourceType = String(payload.resourceType || payload.resource_type || '').trim().slice(0, 120)
  const resourceId = String(payload.resourceId || payload.resource_id || '').trim().slice(0, 160)
  const message = String(payload.message || '').trim().slice(0, 500)
  const event = await prisma.operationalEvent.create({
    data: {
      severity,
      eventType,
      resourceType,
      resourceId,
      message,
      payload: payload.payload || {},
      status: 'open',
    },
  })
  return eventJson(event)
}

export async function listOperationalEvents(filters = {}) {
  await refreshDerivedOperationalEvents()
  const prisma = prismaClient()
  const status = String(filters.status || '')
  const severity = String(filters.severity || '')
  const type = String(filters.type || filters.eventType || '').trim()
  const q = String(filters.q || filters.search || '').trim().toLowerCase()
  const { page, pageSize } = pageArgs(filters)

  const where = {
    ...(status ? { status } : {}),
    ...(severity ? { severity } : {}),
    ...(type ? { eventType: type } : {}),
  }

  const [allMatching, summaryRows] = await Promise.all([
    prisma.operationalEvent.findMany({ where }),
    prisma.operationalEvent.findMany({
      select: { status: true, severity: true, resolvedAt: true },
    }),
  ])

  const filtered = q
    ? allMatching.filter((event) => [
        event.eventType,
        event.resourceType,
        event.resourceId,
        event.message,
      ].some((value) => String(value || '').toLowerCase().includes(q)))
    : allMatching
  const sorted = filtered.sort(sortOperationalEvents)
  const pageItems = sorted.slice((page - 1) * pageSize, page * pageSize)
  const handlers = await loadHandlers(pageItems)
  const summary = {
    open: 0,
    acknowledged: 0,
    resolved: 0,
    critical: 0,
    error: 0,
  }
  for (const event of summaryRows) {
    const normalized = normalizeStatus(event)
    if (summary[normalized] !== undefined) summary[normalized] += 1
    if (['open', 'acknowledged'].includes(normalized) && event.severity === 'critical') summary.critical += 1
    if (['open', 'acknowledged'].includes(normalized) && event.severity === 'error') summary.error += 1
  }

  return {
    items: pageItems.map((event) => eventJson(event, handlers.get(event.handledByAccountId))),
    summary,
    page,
    pageSize,
    total: filtered.length,
  }
}

export async function findOperationalEventByKey(key) {
  const prisma = prismaClient()
  const value = String(key || '').trim()
  if (!value) return null
  const where = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value)
    ? { id: value }
    : { resourceId: value }
  const event = await prisma.operationalEvent.findFirst({
    where,
    orderBy: { createdAt: 'desc' },
  })
  if (!event) return null
  const handlers = await loadHandlers([event])
  return eventJson(event, handlers.get(event.handledByAccountId))
}

export async function updateOperationalEventById(id, payload = {}) {
  const prisma = prismaClient()
  const status = String(payload.status || '').trim()
  const now = new Date()
  const event = await prisma.operationalEvent.update({
    where: { id },
    data: {
      status,
      handlerNote: String(payload.handlerNote || payload.note || '').trim().slice(0, 1000),
      handledByAccountId: payload.handledByAccountId || undefined,
      handledAt: now,
      resolvedAt: ['resolved', 'ignored'].includes(status) ? now : null,
      updatedAt: now,
    },
  })
  const handlers = await loadHandlers([event])
  return eventJson(event, handlers.get(event.handledByAccountId))
}

export async function findOperationalEventByFingerprint(eventType, resourceId) {
  const prisma = prismaClient()
  const event = await prisma.operationalEvent.findFirst({
    where: { eventType, resourceId },
    orderBy: { createdAt: 'desc' },
  })
  return eventJson(event)
}
