import express from 'express'
import cors from 'cors'
import helmet from 'helmet'
import morgan from 'morgan'
import http from 'http'
import { Server as SocketIOServer } from 'socket.io'
import cron from 'node-cron'
import jwt from 'jsonwebtoken'
import { z } from 'zod'
import { redis } from './lib/redis'
import { prisma } from './lib/prisma'
import { authLimiter, apiLimiter, publicLimiter, adminLimiter } from './middleware/rateLimiter'

// ─── Routes ───────────────────────────────────────────────────
import webhookRouter     from './routes/webhooks'
import companyAuthRouter  from './routes/auth.company'
import employeeAuthRouter from './routes/auth.employee'
import renterAuthRouter   from './routes/auth.renter'
import vehiclesRouter    from './routes/vehicles'
import reservationsRouter from './routes/reservations'
import teamRouter        from './routes/team'
import customersRouter   from './routes/customers'
import offersRouter      from './routes/offers'
import analyticsRouter   from './routes/analytics'
import notificationsRouter from './routes/notifications'
import marketplaceRouter from './routes/marketplace'
import adminRouter       from './routes/admin'
import companiesRouter   from './routes/companies'
import subscriptionsRouter from './routes/subscriptions'
import siteRouter        from './routes/site'
import paymentsRouter   from './routes/payments'

const app  = express()
const server = http.createServer(app)

// Trust the first hop from a reverse proxy so req.ip and rate-limiting
// use the real client IP (from X-Forwarded-For) rather than the proxy's IP.
if (process.env.NODE_ENV === 'production') {
  app.set('trust proxy', 1)
}
const v1 = '/api/v1'
const defaultCorsOrigins = [
  'http://localhost:3000',
  'http://localhost:3001',
  'http://localhost:3002',
  'http://localhost:3003',
  'http://127.0.0.1:3000',
  'http://127.0.0.1:3001',
  'http://127.0.0.1:3002',
  'http://127.0.0.1:3003',
]
const corsOrigins = process.env.CORS_ORIGINS
  ? process.env.CORS_ORIGINS.split(',').map((origin) => origin.trim()).filter(Boolean)
  : defaultCorsOrigins

const routeDocs = [
  { method: 'GET', path: '/health', description: 'Health check' },
  { method: 'GET', path: `${v1}/docs`, description: 'Machine-readable API index' },
  { method: 'GET', path: `${v1}/auth/renter/me`, description: 'Current renter profile' },
  { method: 'GET', path: `${v1}/vehicles`, description: 'List company vehicles' },
  { method: 'POST', path: `${v1}/vehicles`, description: 'Create vehicle' },
  { method: 'GET', path: `${v1}/reservations`, description: 'List reservations' },
  { method: 'POST', path: `${v1}/reservations`, description: 'Create reservation' },
  { method: 'GET', path: `${v1}/customers`, description: 'List customers' },
  { method: 'POST', path: `${v1}/customers`, description: 'Create customer' },
  { method: 'GET', path: `${v1}/offers`, description: 'List offers' },
  { method: 'GET', path: `${v1}/analytics/dashboard`, description: 'Dashboard analytics' },
  { method: 'GET', path: `${v1}/analytics/report`, description: 'Analytics report' },
  { method: 'GET', path: `${v1}/notifications/company`, description: 'Company notifications' },
  { method: 'GET', path: `${v1}/marketplace/search`, description: 'Marketplace search' },
  { method: 'GET', path: `${v1}/site/:slug/brand`, description: 'Public site brand config' },
  { method: 'GET', path: `${v1}/companies/me`, description: 'Current company profile' },
  { method: 'GET', path: `${v1}/subscriptions/plans`, description: 'Subscription plans' },
  { method: 'POST', path: `${v1}/admin/auth/login`, description: 'Admin login' },
]

// ─── Socket.io ────────────────────────────────────────────────
const io = new SocketIOServer(server, {
  cors: { origin: corsOrigins, credentials: true, methods: ['GET', 'POST'] },
})

const redisMessageSchema = z.object({
  type: z.string(),
  payload: z.unknown(),
})

// Authenticate socket connections via JWT before joining user rooms
io.use((socket, next) => {
  const token = socket.handshake.auth?.token as string | undefined
  if (!token) return next() // unauthenticated connections allowed; they just don't join rooms
  try {
    const payload = jwt.verify(token, process.env.JWT_SECRET!) as { sub: string; type: string }
    ;(socket as any).authenticatedUserId = payload.sub
    next()
  } catch {
    next(new Error('invalid_token'))
  }
})

io.on('connection', (socket) => {
  const userId = (socket as any).authenticatedUserId as string | undefined
  if (userId) {
    socket.join(`user:${userId}`)
  }
})

// Redis pub/sub → broadcast to connected clients
const subscriber = redis.duplicate()
subscriber.psubscribe('notifications:*', (err) => {
  if (err) console.error('[Redis] Subscribe error:', err)
})
subscriber.on('pmessage', (_pattern, channel, message) => {
  try {
    const parsed = JSON.parse(message)
    const validated = redisMessageSchema.parse(parsed)
    const userId = channel.replace('notifications:', '')
    io.to(`user:${userId}`).emit('notification', validated)
  } catch (err) {
    console.error('[Redis] Invalid notification message:', err)
  }
})

// ─── Middleware ────────────────────────────────────────────────
// Serve local file storage
app.use('/storage', express.static(require('path').resolve(__dirname, 'lib/storage')))

// Webhook must use raw body BEFORE express.json()
app.use('/api/v1/webhooks', express.raw({ type: 'application/json' }), webhookRouter)

app.use(helmet())
app.use(cors({ origin: corsOrigins, credentials: true }))
app.use(morgan('combined'))
app.use(express.json({ limit: '10mb' }))

// ─── API Routes ───────────────────────────────────────────────
// Auth routes: strict brute-force protection
app.use(`${v1}/auth/renter`,    authLimiter, renterAuthRouter)
app.use(`${v1}/auth/company`,   authLimiter, companyAuthRouter)
app.use(`${v1}/auth/employee`,  authLimiter, employeeAuthRouter)

// Admin routes: dedicated limit
app.use(`${v1}/admin`,         adminLimiter, adminRouter)

// Public unauthenticated routes
app.use(`${v1}/marketplace`,   publicLimiter, marketplaceRouter)
app.use(`${v1}/site`,          publicLimiter, siteRouter)

// Authenticated company/renter routes
app.use(`${v1}/vehicles`,      apiLimiter, vehiclesRouter)
app.use(`${v1}/reservations`,  apiLimiter, reservationsRouter)
app.use(`${v1}/team`,          apiLimiter, teamRouter)
app.use(`${v1}/customers`,     apiLimiter, customersRouter)
app.use(`${v1}/offers`,        apiLimiter, offersRouter)
app.use(`${v1}/analytics`,     apiLimiter, analyticsRouter)
app.use(`${v1}/notifications`, apiLimiter, notificationsRouter)
app.use(`${v1}/companies`,     apiLimiter, companiesRouter)
app.use(`${v1}/subscriptions`, apiLimiter, subscriptionsRouter)
app.use(`${v1}/payments`,      apiLimiter, paymentsRouter)

// ─── Health check ─────────────────────────────────────────────
app.get('/health', (_req, res) => {
  res.json({ status: 'ok', version: '1.0.0', timestamp: new Date().toISOString() })
})

app.get(`${v1}/docs`, (_req, res) => {
  res.json({
    name: 'rentaldrivego-api',
    version: '1.0.0',
    baseUrl: v1,
    docsUrl: '/docs',
    routes: routeDocs,
  })
})

app.get('/docs', (_req, res) => {
  const rows = routeDocs
    .map(
      (route) => `
        <tr>
          <td>${route.method}</td>
          <td><code>${route.path}</code></td>
          <td>${route.description}</td>
        </tr>`,
    )
    .join('')

  res.type('html').send(`<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>RentalDriveGo API Docs</title>
    <style>
      body { font-family: Arial, sans-serif; margin: 40px; color: #0f172a; background: #f8fafc; }
      h1 { margin-bottom: 8px; }
      p { color: #475569; }
      .meta { margin-bottom: 24px; }
      table { width: 100%; border-collapse: collapse; background: #fff; border-radius: 12px; overflow: hidden; }
      th, td { text-align: left; padding: 12px 16px; border-bottom: 1px solid #e2e8f0; }
      th { background: #e2e8f0; font-size: 12px; text-transform: uppercase; letter-spacing: 0.04em; }
      code { background: #f1f5f9; padding: 2px 6px; border-radius: 6px; }
    </style>
  </head>
  <body>
    <h1>RentalDriveGo API</h1>
    <p class="meta">Base URL: <code>${v1}</code> · JSON index: <code>${v1}/docs</code></p>
    <table>
      <thead>
        <tr>
          <th>Method</th>
          <th>Path</th>
          <th>Description</th>
        </tr>
      </thead>
      <tbody>${rows}</tbody>
    </table>
  </body>
</html>`)
})

// ─── Error handler ────────────────────────────────────────────
app.use((err: any, _req: express.Request, res: express.Response, _next: express.NextFunction) => {
  const statusCode = err.statusCode ?? 500
  const message    = err.message ?? 'Internal server error'
  const code       = err.code ?? 'internal_error'

  if (statusCode >= 500) {
    console.error('[API Error]', err)
  }

  // Zod validation error
  if (err.name === 'ZodError') {
    return res.status(400).json({ error: 'validation_error', message: 'Invalid request body', issues: err.issues, statusCode: 400 })
  }

  // Prisma not found
  if (err.code === 'P2025') {
    return res.status(404).json({ error: 'not_found', message: 'Resource not found', statusCode: 404 })
  }

  // Prisma unique constraint
  if (err.code === 'P2002') {
    return res.status(409).json({ error: 'conflict', message: 'A resource with this value already exists', statusCode: 409 })
  }

  res.status(statusCode).json({ error: code, message, statusCode })
})

// ─── Scheduled jobs ───────────────────────────────────────────

// Daily: flag expiring/expired licenses
cron.schedule('0 8 * * *', async () => {
  const customers = await prisma.customer.findMany({ where: { licenseExpiry: { not: null } } })
  for (const c of customers) {
    if (!c.licenseExpiry) continue
    const daysLeft = Math.ceil((c.licenseExpiry.getTime() - Date.now()) / (1000 * 60 * 60 * 24))
    const expired = c.licenseExpiry <= new Date()
    const expiring = !expired && daysLeft < 90
    if (expired !== c.licenseExpired || expiring !== c.licenseExpiringSoon) {
      await prisma.customer.update({ where: { id: c.id }, data: { licenseExpired: expired, licenseExpiringSoon: expiring, licenseValidationStatus: expired ? 'EXPIRED' : expiring ? 'EXPIRING' : 'VALID' } })
    }
  }
})

// Daily: send trial-ending reminders (3 days before trial end)
cron.schedule('0 9 * * *', async () => {
  const soon = new Date(Date.now() + 3 * 24 * 60 * 60 * 1000)
  const subscriptions = await prisma.subscription.findMany({
    where: { status: 'TRIALING', trialEndAt: { lte: soon, gte: new Date() } },
    include: { company: { include: { employees: { where: { role: 'OWNER' } } } } },
  })
  for (const sub of subscriptions) {
    const owner = sub.company.employees[0]
    if (owner) {
      await prisma.notification.create({
        data: { type: 'SUBSCRIPTION_TRIAL_ENDING', title: 'Your trial ends soon', body: 'Your 14-day free trial ends in 3 days. Add a payment method to keep access.', companyId: sub.companyId, employeeId: owner.id, channel: 'IN_APP', status: 'DELIVERED' },
      })
    }
  }
})

// ─── Start ────────────────────────────────────────────────────
const PORT = process.env.API_PORT ?? 4000
server.listen(PORT, () => {
  console.log(`[API] Server running on port ${PORT}`)
})

export { app, io }
