import 'dotenv/config'
import path from 'node:path'
import cors from '@fastify/cors'
import jwt from '@fastify/jwt'
import multipart from '@fastify/multipart'
import fastifyStatic from '@fastify/static'
import Fastify from 'fastify'
import { NOTIFICATION_EVENTS } from '../../shared/constants/notification-events.js'
import { ensureAdminUser } from './lib/bootstrap-admin.js'
import { getOrCreateUnspecifiedCategory } from './lib/default-category.js'
import { createEventBus } from './lib/notifications/event-bus.js'
import {
  resolveUserNotificationTargets,
  resolveAdminNotificationTargets,
  resolveAuthCodeTargets,
} from './lib/notifications/preferences.js'
import { createNotificationQueue } from './lib/notifications/queue.js'
import { prisma } from './lib/prisma.js'
import { getMaxUploadBodyBytes, getProductImageMaxFileBytes } from './lib/upload-limits.js'
import { registerAuth } from './plugins/auth.js'
import { registerIpGate } from './plugins/ip-gate.js'
import { registerSecurityHeaders } from './plugins/security-headers.js'
import { registerApiRoutes } from './routes/api.js'
import { registerOAuthSocialRoutes } from './routes/oauth-social.js'
import { registerSseRoutes } from './routes/sse.js'
import { registerUploadsResized } from './routes/uploads-resized.js'
import { registerUserNotificationRoutes } from './routes/user/notifications.js'
import { registerUserAddressRoutes } from './routes/user-addresses.js'
import { registerUserCartRoutes } from './routes/user-cart.js'
import { registerUserMessageRoutes } from './routes/user-messages.js'
import { registerUserOrderRoutes } from './routes/user-orders.js'
import { registerUserPaymentRoutes } from './routes/user-payments.js'
import { registerYookassaWebhookRoute } from './routes/webhook-yookassa.js'

const port = Number(process.env.PORT) || 3333
const origin = (process.env.CORS_ORIGIN ?? '')
  .split(',')
  .map((s) => s.trim())
  .filter(Boolean)

const fastify = Fastify({
  logger: true,
  bodyLimit: getMaxUploadBodyBytes(),
  trustProxy: true,
})

await fastify.register(cors, {
  origin: origin.length ? origin : true,
  credentials: true,
})

await registerSecurityHeaders(fastify)

fastify.get('/health', async (request) => {
  try {
    await prisma.$queryRaw`SELECT 1`
    return { status: 'ok', database: 'connected', uptime: process.uptime() }
  } catch (err) {
    request.log.error({ err }, 'Health check database query failed')
    return { status: 'degraded', database: 'disconnected', uptime: process.uptime() }
  }
})

fastify.setErrorHandler(function errorHandler(error, request, reply) {
  const isProd = process.env.NODE_ENV === 'production'

  if (error.validation) {
    return reply.code(400).send({
      error: 'Ошибка валидации',
      details: isProd ? undefined : error.validation,
    })
  }

  if (error.code === 'FST_ERR_VALIDATION') {
    return reply.code(400).send({ error: 'Неверный формат запроса' })
  }

  if (error.statusCode) {
    return reply.code(error.statusCode).send({
      error: error.message || 'Произошла ошибка',
    })
  }

  request.log.error(error)

  return reply.code(500).send({
    error: isProd ? 'Внутренняя ошибка сервера' : error.message,
  })
})

await fastify.register(jwt, {
  secret: process.env.JWT_SECRET || 'dev-jwt-secret-change-me',
})

await fastify.register(multipart, {
  limits: {
    files: 10,
    fileSize: getProductImageMaxFileBytes(),
  },
})

registerUploadsResized(fastify)

const uploadsDir = path.join(process.cwd(), 'uploads')
await fastify.register(fastifyStatic, {
  root: uploadsDir,
  prefix: '/uploads/',
  setHeaders(res, filePath) {
    if (filePath.includes('/.cache/')) {
      res.setHeader('Cache-Control', 'public, max-age=31536000, immutable')
    } else {
      res.setHeader('Cache-Control', 'public, max-age=86400')
    }
  },
})

fastify.decorate('authenticate', async function authenticate(request, reply) {
  try {
    if (!request.headers.authorization && request.query?.token) {
      request.headers.authorization = `Bearer ${request.query.token}`
    }
    await request.jwtVerify()
  } catch (err) {
    request.log.error({ err }, 'JWT verification failed')
    return reply.code(401).send({ error: 'Не авторизован' })
  }
})

const eventBus = createEventBus()
const notificationQueue = createNotificationQueue()
fastify.decorate('eventBus', eventBus)
fastify.decorate('notificationQueue', notificationQueue)

await registerIpGate(fastify)
registerAuth(fastify)
await registerUserAddressRoutes(fastify)
await registerUserCartRoutes(fastify)
await registerUserMessageRoutes(fastify)
await registerSseRoutes(fastify)
await registerUserOrderRoutes(fastify)
await registerUserPaymentRoutes(fastify)
await registerUserNotificationRoutes(fastify)
await registerOAuthSocialRoutes(fastify)
await registerYookassaWebhookRoute(fastify)
await registerApiRoutes(fastify)

try {
  await ensureAdminUser()
} catch (err) {
  fastify.log.error({ err }, 'ensureAdminUser failed — continuing startup')
}

try {
  await getOrCreateUnspecifiedCategory()
} catch (err) {
  fastify.log.error({ err }, 'getOrCreateUnspecifiedCategory failed — continuing startup')
}

try {
  await notificationQueue.flushPendingOnStartup()
} catch (err) {
  fastify.log.error({ err }, 'notificationQueue.flushPendingOnStartup failed')
}
notificationQueue.start()

const {
  ORDER_CREATED,
  ORDER_STATUS_CHANGED,
  ORDER_MESSAGE_SENT,
  ORDER_MESSAGE_ADMIN_REPLY,
  PAYMENT_STATUS_CHANGED,
  AUTH_CODE_REQUESTED,
  DELIVERY_FEE_ADJUSTED,
} = NOTIFICATION_EVENTS

async function dispatchNotification(eventType, payload) {
  try {
    if (eventType === AUTH_CODE_REQUESTED) {
      const targets = await resolveAuthCodeTargets(eventType, payload)
      for (const target of targets.filter((t) => t.channel === 'telegram')) {
        const log = await prisma.notificationLog.create({
          data: {
            eventType,
            channel: target.channel,
            status: 'pending',
            payload: JSON.stringify(payload),
          },
        })
        notificationQueue.enqueue({ ...target, eventType, payload, logId: log.id })
      }
      return
    }

    const userTargets = await resolveUserNotificationTargets(eventType, payload)
    for (const target of userTargets) {
      const log = await prisma.notificationLog.create({
        data: {
          userId: payload.userId,
          eventType,
          channel: target.channel,
          status: 'pending',
          payload: JSON.stringify(payload),
        },
      })
      notificationQueue.enqueue({ ...target, eventType, payload, logId: log.id })
    }

    const adminEventType = eventType === 'order:created:admin' ? ORDER_CREATED : eventType
    const adminTargets = await resolveAdminNotificationTargets(adminEventType, payload)
    for (const target of adminTargets) {
      const log = await prisma.notificationLog.create({
        data: {
          eventType,
          channel: target.channel,
          status: 'pending',
          payload: JSON.stringify(payload),
        },
      })
      notificationQueue.enqueue({ ...target, eventType, payload, logId: log.id })
    }
  } catch (err) {
    console.error(`[notification] Error dispatching ${eventType}:`, err.message)
  }
}

eventBus.on(ORDER_CREATED, (payload) => dispatchNotification(ORDER_CREATED, payload))
eventBus.on(ORDER_STATUS_CHANGED, (payload) => dispatchNotification(ORDER_STATUS_CHANGED, payload))
eventBus.on(ORDER_MESSAGE_SENT, (payload) => dispatchNotification(ORDER_MESSAGE_SENT, payload))
eventBus.on(ORDER_MESSAGE_ADMIN_REPLY, (payload) => dispatchNotification(ORDER_MESSAGE_ADMIN_REPLY, payload))
eventBus.on(PAYMENT_STATUS_CHANGED, (payload) => dispatchNotification(PAYMENT_STATUS_CHANGED, payload))
eventBus.on(AUTH_CODE_REQUESTED, (payload) => dispatchNotification(AUTH_CODE_REQUESTED, payload))
eventBus.on('order:created:admin', (payload) => dispatchNotification('order:created:admin', payload))
eventBus.on('review:created', (payload) => dispatchNotification('review:created', payload))
eventBus.on(DELIVERY_FEE_ADJUSTED, (payload) => dispatchNotification(DELIVERY_FEE_ADJUSTED, payload))

async function shutdown() {
  notificationQueue.stop()
  await fastify.close()
  process.exit(0)
}
process.on('SIGINT', shutdown)
process.on('SIGTERM', shutdown)

process.on('unhandledRejection', (reason) => {
  console.error('[process] Unhandled rejection:', reason?.message || reason)
})

try {
  await fastify.listen({ port, host: '0.0.0.0' })
} catch (err) {
  fastify.log.error(err)
  process.exit(1)
}
