import express from 'express'
import path from 'path'
import { fileURLToPath } from 'url'
import { existsSync, mkdirSync } from 'fs'
import sqlite3 from 'better-sqlite3'
import z from 'zod'
import rateLimit from 'express-rate-limit'
import helmet from 'helmet'
import cors from 'cors'

// --- Setup ---
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const app = express()

// Trust first proxy (Docker/reverse proxy) for correct client IP in rate limiting
app.set('trust proxy', 1)
const dbPath = path.join(__dirname, '../db/queuenorth.db')
const dbDir = path.dirname(dbPath)

// Create db directory if it doesn't exist
if (!existsSync(dbDir)) {
  mkdirSync(dbDir, { recursive: true })
}

// --- Logger ---
const LOG_LEVELS = { error: 0, warn: 1, info: 2, debug: 3 }
const currentLevel = LOG_LEVELS[process.env.LOG_LEVEL?.toLowerCase()] ?? LOG_LEVELS.info

const log = {
  info: (...args) => { if (currentLevel >= LOG_LEVELS.info) console.log(`[${new Date().toISOString()}] INFO `, ...args) },
  warn: (...args) => { if (currentLevel >= LOG_LEVELS.warn) console.warn(`[${new Date().toISOString()}] WARN `, ...args) },
  error: (...args) => { if (currentLevel >= LOG_LEVELS.error) console.error(`[${new Date().toISOString()}] ERROR`, ...args) },
  debug: (...args) => { if (currentLevel >= LOG_LEVELS.debug) console.debug(`[${new Date().toISOString()}] DEBUG`, ...args) },
}

// --- Rate Limiting ---
const rateLimitWindowMs = 60 * 1000 // 1 minute
const rateLimitMax = (() => {
  const val = parseInt(process.env.RATE_LIMIT_PER_MINUTE || '5', 10)
  if (isNaN(val) || val < 1) {
    log.warn('[RateLimit] Invalid RATE_LIMIT_PER_MINUTE, defaulting to 5')
    return 5
  }
  return val
})()

const apiLimiter = rateLimit({
  windowMs: rateLimitWindowMs,
  max: rateLimitMax,
  standardHeaders: true,
  legacyHeaders: false,
  handler: (req, res) => {
    log.warn(`Rate limit exceeded for IP: ${req.ip}`)
    res.status(429).json({
      error: 'Too Many Requests',
      message: 'Please try again later.',
      retryAfter: Math.ceil(rateLimitWindowMs / 1000),
    })
  },
})

// --- Security Headers (Helmet) ---
const cspDirectives = {
  defaultSrc: ["'self'"],
  scriptSrc: ["'self'"],
  styleSrc: ["'self'", 'https://fonts.googleapis.com'],
  fontSrc: ["'self'", 'https://fonts.gstatic.com'],
  imgSrc: ["'self'", 'data:'],
  connectSrc: ["'self'"],
  objectSrc: ["'none'"],
  baseUri: ["'self'"],
  formAction: ["'self'"],
}

app.use(helmet({
  contentSecurityPolicy: {
    directives: cspDirectives,
  },
  crossOriginEmbedderPolicy: false, // Prevent CSP issues with embedded content
  crossOriginOpenerPolicy: false,
  crossOriginResourcePolicy: { policy: 'same-origin' },
  dnsPrefetchControl: { allow: false },
  frameguard: { action: 'deny' },
  hidePoweredBy: true,
  hsts: { maxAge: 31536000, includeSubDomains: true },
  ieNoOpen: true,
  noSniff: true,
  originAgentCluster: true,
  permittedCrossDomainPolicies: { permittedPolicies: 'none' },
  referrerPolicy: { policy: 'same-origin' },
  xssFilter: true,
}))

log.info('[Security] Helmet enabled with CSP configured')

// --- CORS Configuration ---
const corsOrigin = process.env.CORS_ORIGIN || '*' // Default to * for development
const corsConfig = cors({
  origin: corsOrigin === '*' ? corsOrigin : (corsOrigin === 'null' ? undefined : corsOrigin),
  methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  exposedHeaders: ['X-RateLimit-Remaining', 'X-RateLimit-Reset'],
  maxAge: 86400, // 24 hours
  credentials: true,
})

app.use(corsConfig)
log.info(`[CORS] Enabled with origin: ${corsOrigin}`)

// Middleware — JSON body parsing only on POST routes (issue #14)
app.use(express.urlencoded({ extended: true, limit: '1mb' }))

// Rate limiting for API routes only
app.use('/api', apiLimiter)

// Request logging middleware
app.use((req, res, next) => {
  const start = Date.now()
  res.on('finish', () => {
    const ms = Date.now() - start
    const level = res.statusCode >= 500 ? 'error' : res.statusCode >= 400 ? 'warn' : 'info'
    log[level](`${req.method} ${req.originalUrl} ${res.statusCode} ${ms}ms`)
  })
  next()
})

// --- Database ---
const db = sqlite3(dbPath)

// Initialize schema
const initSchema = () => {
  // Check if leads table exists and needs UNIQUE constraint migration
  const tableExists = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='leads'").get()

  if (tableExists) {
    // Check if UNIQUE constraint already exists on email
    const pragma = db.prepare("PRAGMA table_info(leads)").all()
    const emailCol = pragma.find(col => col.name === 'email')
    
    if (emailCol && !emailCol.pk) {
      // UNIQUE constraint doesn't exist, need to add it via migration
      log.info('[DB] Adding UNIQUE constraint on leads.email via migration')
      
      // Migrate leads table to add UNIQUE constraint
      db.exec(
        [
          'CREATE TABLE IF NOT EXISTS leads_new (',
          '  id INTEGER PRIMARY KEY AUTOINCREMENT,',
          '  company TEXT NOT NULL,',
          '  name TEXT NOT NULL,',
          '  email TEXT NOT NULL UNIQUE,',
          '  phone TEXT,',
          '  zip TEXT,',
          '  message TEXT,',
          '  service_interest TEXT,',
          '  created_at DATETIME DEFAULT CURRENT_TIMESTAMP',
          ')'
        ].join('\n')
      )
      
      // Copy existing data (deduplicate - keep first occurrence per email)
      db.exec(
        [
          'INSERT OR IGNORE INTO leads_new (id, company, name, email, phone, zip, message, service_interest, created_at)',
          'SELECT id, company, name, email, phone, zip, message, service_interest, created_at',
          'FROM leads'
        ].join('\n')
      )
      
      // Drop old table
      db.exec('DROP TABLE leads')
      
      // Rename new table
      db.exec('ALTER TABLE leads_new RENAME TO leads')
      
      log.info('[DB] UNIQUE constraint added on leads.email')
    }
  }

  // Leads table (now with UNIQUE constraint on email, either from migration or fresh)
  db.exec(`
    CREATE TABLE IF NOT EXISTS leads (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      company TEXT NOT NULL,
      name TEXT NOT NULL,
      email TEXT NOT NULL UNIQUE,
      phone TEXT,
      zip TEXT,
      message TEXT,
      service_interest TEXT,
      created_at DATETIME DEFAULT CURRENT_TIMESTAMP
    )
  `)

  // Support requests table
  db.exec(`
    CREATE TABLE IF NOT EXISTS support_requests (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      name TEXT NOT NULL,
      company TEXT NOT NULL,
      email TEXT NOT NULL,
      phone TEXT,
      issue TEXT NOT NULL,
      priority TEXT DEFAULT 'medium',
      created_at DATETIME DEFAULT CURRENT_TIMESTAMP
    )
  `)
}

initSchema()

// --- Sanitization Helper ---
const sanitizeString = (input, maxLength) => {
  if (typeof input !== 'string') return input
  // Trim whitespace
  let sanitized = input.trim()
  // Remove HTML/script tags to prevent XSS
  sanitized = sanitized.replace(/<script[^>]*>.*?<\/script>/gi, '')
  sanitized = sanitized.replace(/<[^>]*>/g, '')
  // Truncate to max length
  sanitized = sanitized.substring(0, maxLength)
  // Convert empty strings to undefined so they become NULL in DB
  return sanitized === '' ? undefined : sanitized
}

const sanitizePayload = (data, fields) => {
  const result = { ...data }
  for (const [field, maxLength] of Object.entries(fields)) {
    if (result[field] !== undefined) {
      result[field] = sanitizeString(result[field], maxLength)
    }
  }
  return result
}

// --- Validation Schemas ---
const leadSchema = z.object({
  company: z.string().min(1, 'Company name is required').trim().max(200, 'Company name must be 200 characters or less'),
  name: z.string().min(1, 'Name is required').trim().max(100, 'Name must be 100 characters or less'),
  email: z.string().email('Valid email is required').trim().max(254, 'Email must be 254 characters or less'),
  phone: z.string().trim().max(50, 'Phone must be 50 characters or less').optional().or(z.literal('').transform(() => undefined)),
  zip: z.string().trim().max(10, 'ZIP code must be 10 characters or less').optional().or(z.literal('').transform(() => undefined)),
  message: z.string().trim().max(5000, 'Message must be 5000 characters or less').optional().or(z.literal('').transform(() => undefined)),
  service_interest: z.string().trim().max(50, 'Service interest must be 50 characters or less').optional().or(z.literal('').transform(() => undefined)),
})

const supportSchema = z.object({
  name: z.string().min(1, 'Name is required').trim().max(100, 'Name must be 100 characters or less'),
  company: z.string().min(1, 'Company name is required').trim().max(200, 'Company name must be 200 characters or less'),
  email: z.string().email('Valid email is required').trim().max(254, 'Email must be 254 characters or less'),
  phone: z.string().trim().max(50, 'Phone must be 50 characters or less').optional().or(z.literal('').transform(() => undefined)),
  issue: z.string().min(10, 'Please provide at least 10 characters describing your issue').trim().max(5000, 'Issue description must be 5000 characters or less'),
  priority: z.enum(['low', 'medium', 'high'], {
    errorMap: () => ({ message: 'Priority must be low, medium, or high' }),
  }).transform((val) => val?.toLowerCase() ?? undefined).optional().or(z.literal('').transform(() => undefined)),
})

// --- Zoho CRM Forwarding (best-effort, fire-and-forget) ---
const ZOHO_ENABLED = process.env.ZOHO_ENABLED === 'true'
const ZOHO_CASES_ENABLED = process.env.ZOHO_CASES_ENABLED === 'true'
const ZOHO_API_DOMAIN = process.env.ZOHO_API_DOMAIN || 'https://www.zohoapis.com'
const ZOHO_ACCOUNTS_DOMAIN = process.env.ZOHO_ACCOUNTS_DOMAIN || 'https://accounts.zoho.com'
const ZOHO_CLIENT_ID = process.env.ZOHO_CLIENT_ID || null
const ZOHO_CLIENT_SECRET = process.env.ZOHO_CLIENT_SECRET || null
const ZOHO_REFRESH_TOKEN = process.env.ZOHO_REFRESH_TOKEN || null

// In-memory access token cache
let zohoAccessToken = null
let zohoTokenExpiry = 0

// 10 second timeout for all Zoho API calls
const ZOHO_TIMEOUT_MS = 10000

async function getZohoAccessToken() {
  // Return cached token if still valid (with 60s buffer)
  if (zohoAccessToken && Date.now() < zohoTokenExpiry - 60000) {
    return zohoAccessToken
  }

  try {
    // Token endpoint is on the ACCOUNTS domain, NOT the API domain
    // US: accounts.zoho.com | EU: accounts.zoho.eu | IN: accounts.zoho.in
    const url = `${ZOHO_ACCOUNTS_DOMAIN}/oauth/v2/token`
    const params = new URLSearchParams({
      grant_type: 'refresh_token',
      client_id: ZOHO_CLIENT_ID,
      client_secret: ZOHO_CLIENT_SECRET,
      refresh_token: ZOHO_REFRESH_TOKEN,
    })

    const controller = new AbortController()
    const timeoutId = setTimeout(() => controller.abort(), ZOHO_TIMEOUT_MS)

    let response
    try {
      response = await fetch(url, {
        method: 'POST',
        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
        body: params.toString(),
        signal: controller.signal,
      })
    } catch (err) {
      if (err.name === 'AbortError') {
        log.warn('[Zoho] Token fetch timed out after', ZOHO_TIMEOUT_MS, 'ms')
      } else {
        log.error('[Zoho] Token fetch error:', err.message)
      }
      clearTimeout(timeoutId)
      return null
    } finally {
      clearTimeout(timeoutId)
    }

    // Issue #3: Check response.ok before parsing JSON
    if (!response.ok) {
      const text = await response.text()
      log.error(`[Zoho] Token fetch failed (${response.status}):`, text)
      return null
    }

    const data = await response.json()

    if (data.access_token) {
      zohoAccessToken = data.access_token
      zohoTokenExpiry = Date.now() + (data.expires_in || 3600) * 1000
      log.info('[Zoho] Access token acquired, expires in', data.expires_in || 3600, 'seconds')
      return zohoAccessToken
    } else {
      log.error('[Zoho] Token exchange failed:', JSON.stringify(data))
      return null
    }
  } catch (err) {
    log.error('[Zoho] Token acquisition error:', err.message)
    return null
  }
}

async function forwardToZoho(leadData) {
  if (!ZOHO_ENABLED) return

  // Short-circuit if Zoho credentials are missing
  if (!ZOHO_CLIENT_ID || !ZOHO_CLIENT_SECRET || !ZOHO_REFRESH_TOKEN) {
    log.warn("[Zoho] Skipping forwarding - ZOHO_CLIENT_ID, ZOHO_CLIENT_SECRET, or ZOHO_REFRESH_TOKEN not configured")
    return
  }

  let accessToken = await getZohoAccessToken()
  if (!accessToken) {
    // Retry once — token refresh can fail transiently
    log.warn('[Zoho] First token refresh failed, retrying...')
    // Clear cached token to force a fresh attempt
    zohoAccessToken = null
    zohoTokenExpiry = 0
    accessToken = await getZohoAccessToken()
    if (!accessToken) {
      log.warn('[Zoho] No access token available after retry, skipping lead forwarding')
      return
    }
  }

  // Issue #8: Prevent double-slash in URL path
  // Use upsert to handle duplicates gracefully (insert new or update existing by email)
  const url = `${ZOHO_API_DOMAIN.replace(/\/$/, "")}/crm/v8/Leads/upsert`

  // Split full name into First_Name / Last_Name for Zoho
  // Zoho requires Last_Name (mandatory), First_Name is optional
  const nameParts = (leadData.name || '').trim().split(/\s+/)
  const lastName = nameParts.length > 1 ? nameParts.slice(1).join(' ') : (nameParts[0] || 'Unknown')
  const firstName = nameParts.length > 1 ? nameParts[0] : ''

  // Build Description with service interest appended for Zoho visibility
  const descriptionParts = []
  if (leadData.message) descriptionParts.push(leadData.message)
  if (leadData.service_interest) descriptionParts.push(`Service Interest: ${leadData.service_interest}`)
  const description = descriptionParts.join('\n\n')

  const payload = {
    data: [
      {
        First_Name: firstName || undefined,
        Last_Name: lastName,
        Company: leadData.company || '',
        Email: leadData.email || '',
        Phone: leadData.phone || '',
        Zip_Code: leadData.zip || '',
        Description: description || '',
        Lead_Source: 'Website',
      },
    ],
    duplicate_check_fields: ['Email'],
    trigger: ['workflow'],
  }

  const controller = new AbortController()
  const timeoutId = setTimeout(() => controller.abort(), ZOHO_TIMEOUT_MS)

  try {
    const response = await fetch(url, {
      method: "POST",
      headers: {
        "Authorization": `Zoho-oauthtoken ${accessToken}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify(payload),
      signal: controller.signal,
    })

    // Issue #3: Check response.ok before processing
    if (!response.ok) {
      const text = await response.text()
      log.error(`[Zoho] Lead forwarding failed (${response.status}):`, text)
      return
    }

    const result = await response.json()
    log.info("[Zoho] Lead forwarded successfully:", result.data?.[0]?.details?.id || "no id returned")
  } catch (fetchErr) {
    if (fetchErr.name === "AbortError") {
      log.warn("[Zoho] Lead forwarding timed out after", ZOHO_TIMEOUT_MS, "ms")
    } else {
      log.error("[Zoho] Forwarding error:", fetchErr.message)
    }
  } finally {
    clearTimeout(timeoutId)
  }
}

// --- Zoho Cases Forwarding (best-effort, fire-and-forget) ---
async function forwardSupportToZoho(supportData) {
  if (!ZOHO_CASES_ENABLED) return

  // Short-circuit if Zoho credentials are missing
  if (!ZOHO_CLIENT_ID || !ZOHO_CLIENT_SECRET || !ZOHO_REFRESH_TOKEN) {
    log.warn("[Zoho Cases] Skipping forwarding - ZOHO_CLIENT_ID, ZOHO_CLIENT_SECRET, or ZOHO_REFRESH_TOKEN not configured")
    return
  }

  let accessToken = await getZohoAccessToken()
  if (!accessToken) {
    // Retry once — token refresh can fail transiently
    log.warn('[Zoho Cases] First token refresh failed, retrying...')
    // Clear cached token to force a fresh attempt
    zohoAccessToken = null
    zohoTokenExpiry = 0
    accessToken = await getZohoAccessToken()
    if (!accessToken) {
      log.warn('[Zoho Cases] No access token available after retry, skipping support forwarding')
      return
    }
  }

  // Map priority to Zoho format
  const priorityMap = {
    low: 'Low',
    medium: 'Medium',
    high: 'High',
  }
  const priority = priorityMap[supportData.priority] || 'Medium'

  // Build description with name and company since Cases don't have Company field directly
  const descriptionParts = []
  descriptionParts.push(`Name: ${supportData.name}`)
  descriptionParts.push(`Company: ${supportData.company}`)
  if (supportData.phone) descriptionParts.push(`Phone: ${supportData.phone}`)
  descriptionParts.push(`\n${supportData.issue}`)
  const description = descriptionParts.join('\n')

  const payload = {
    data: [
      {
        Subject: supportData.issue,
        Priority: priority,
        Email: supportData.email || '',
        Description: description || '',
        Case_Origin: 'Website',
      },
    ],
    trigger: ['workflow'],
  }

  // Issue #8: Prevent double-slash in URL path
  const url = `${ZOHO_API_DOMAIN.replace(/\/$/, '')}/crm/v8/Cases`

  const controller = new AbortController()
  const timeoutId = setTimeout(() => controller.abort(), ZOHO_TIMEOUT_MS)

  try {
    const response = await fetch(url, {
      method: "POST",
      headers: {
        "Authorization": `Zoho-oauthtoken ${accessToken}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify(payload),
      signal: controller.signal,
    })

    // Issue #3: Check response.ok before processing
    if (!response.ok) {
      const text = await response.text()
      log.error(`[Zoho Cases] Support forwarding failed (${response.status}):`, text)
      return
    }

    const result = await response.json()
    log.info("[Zoho Cases] Support forwarded successfully:", result.data?.[0]?.details?.id || "no id returned")
  } catch (fetchErr) {
    if (fetchErr.name === "AbortError") {
      log.warn("[Zoho Cases] Support forwarding timed out after", ZOHO_TIMEOUT_MS, "ms")
    } else {
      log.error("[Zoho Cases] Forwarding error:", fetchErr.message)
    }
  } finally {
    clearTimeout(timeoutId)
  }
}

// --- API Routes ---

// Health check
app.get('/api/health', (req, res) => {
  try {
    // Verify DB connection by executing a simple query
    db.prepare('SELECT 1').get()
    res.json({ status: 'ok', db: 'ok', timestamp: new Date().toISOString() })
  } catch (err) {
    log.error('Health check DB verification failed:', err.message)
    res.status(503).json({ error: 'Service unavailable', db: 'error', timestamp: new Date().toISOString() })
  }
})

// Submit lead
app.post('/api/leads', express.json({ limit: '1mb' }), (req, res) => {
  let sanitized
  try {
    const parsed = leadSchema.safeParse(req.body)

    if (!parsed.success) {
      const fieldErrors = {}
      for (const issue of parsed.error.issues) {
        if (issue.path[0]) {
          fieldErrors[issue.path[0]] = issue.message
        }
      }
      return res.status(400).json({
        error: 'Validation failed',
        fields: fieldErrors,
      })
    }

    // Sanitize parsed data before insert (trim, strip tags, truncate)
    sanitized = sanitizePayload(parsed.data, {
      company: 200,
      name: 100,
      email: 254,
      phone: 50,
      zip: 10,
      message: 5000,
      service_interest: 50,
    })

    const stmt = db.prepare(`
      INSERT INTO leads (company, name, email, phone, zip, message, service_interest)
      VALUES (?, ?, ?, ?, ?, ?, ?)
    `)

    const result = stmt.run(
      sanitized.company,
      sanitized.name,
      sanitized.email,
      sanitized.phone || null,
      sanitized.zip || null,
      sanitized.message || null,
      sanitized.service_interest || null
    )

    log.info(`Lead submitted: ${sanitized.email} from ${sanitized.company} (id: ${result.lastInsertRowid})`)

    // Fire-and-forget Zoho forwarding (best-effort, non-blocking)
    forwardToZoho(sanitized).catch(err => log.error('[Zoho] Forwarding error:', err.message))

    res.json({ success: true, message: "Thanks! We'll be in touch shortly." })
  } catch (err) {
    // Issue #6: Handle duplicate email error with 409 Conflict
    const errorMsg = err.message?.toLowerCase() || ''
    if (errorMsg.includes('unique constraint') || errorMsg.includes('duplicate')) {
      log.warn(`Duplicate lead email: ${sanitized.email}`)
      
      // Still forward to Zoho (non-blocking) for existing leads
      forwardToZoho(sanitized).catch(err => log.error('[Zoho] Forwarding error:', err.message))
      
      return res.status(409).json({
        error: 'Duplicate lead',
        message: 'A lead with this email already exists'
      })
    }
    
    log.error('Error submitting lead:', err)
    res.status(500).json({ error: 'Failed to submit lead' })
  }
})

// Submit support request
app.post('/api/support', express.json({ limit: '1mb' }), (req, res) => {
  try {
    const parsed = supportSchema.safeParse(req.body)

    if (!parsed.success) {
      const fieldErrors = {}
      for (const issue of parsed.error.issues) {
        if (issue.path[0]) {
          fieldErrors[issue.path[0]] = issue.message
        }
      }
      return res.status(400).json({
        error: 'Validation failed',
        fields: fieldErrors,
      })
    }

    // Sanitize parsed data before insert (trim, strip tags, truncate)
    const sanitized = sanitizePayload(parsed.data, {
      name: 100,
      company: 200,
      email: 254,
      phone: 50,
      issue: 5000,
      priority: 10,
    })

    const stmt = db.prepare(`
      INSERT INTO support_requests (name, company, email, phone, issue, priority)
      VALUES (?, ?, ?, ?, ?, ?)
    `)

    const result = stmt.run(
      sanitized.name,
      sanitized.company,
      sanitized.email,
      sanitized.phone || null,
      sanitized.issue,
      sanitized.priority || 'medium'
    )

    log.info(`Support request submitted: ${sanitized.email} from ${sanitized.company} priority=${sanitized.priority || 'medium'} (id: ${result.lastInsertRowid})`)

    // Fire-and-forget Zoho Cases forwarding (best-effort, non-blocking)
    forwardSupportToZoho(sanitized).catch(err => log.error('[Zoho Cases] Forwarding error:', err.message))

    res.json({ success: true, message: "Thanks! We'll get back to you soon." })
  } catch (err) {
    log.error('Error submitting support request:', err)
    res.status(500).json({ error: 'Failed to submit support request' })
  }
})

// --- Request timeout middleware (30 seconds) ---
const REQUEST_TIMEOUT_MS = 30000

const timeoutMiddleware = (req, res, next) => {
  const timeout = setTimeout(() => {
    if (!res.headersSent) {
      log.warn(`Request timeout: ${req.method} ${req.originalUrl}`)
      res.status(504).json({ error: 'Request timeout' })
    }
  }, REQUEST_TIMEOUT_MS)

  res.on('finish', () => clearTimeout(timeout))
  res.on('close', () => clearTimeout(timeout))
  next()
}

// --- Global error handlers ---
process.on('uncaughtException', (err) => {
  log.error('Uncaught exception:', err.message)
  log.error('Stack:', err.stack)
  log.error('Shutting down due to uncaught exception...')
  process.exit(1)
})

process.on('unhandledRejection', (reason, promise) => {
  log.error('Unhandled rejection at:', promise)
  log.error('Reason:', reason)
  log.error('Shutting down due to unhandled rejection...')
  process.exit(1)
})

// --- Start Server ---
const PORT = process.env.SERVER_PORT || 3001

// Register timeout middleware BEFORE catch-all routes
app.use(timeoutMiddleware)

// --- 404 catch-all for API routes (must be after all API routes) ---
app.use((req, res, next) => {
  if (req.path.startsWith('/api')) {
    log.warn(`API route not found: ${req.method} ${req.originalUrl}`)
    return res.status(404).json({ error: 'Not found' })
  }
  next()
})

// Static file serving for SPA
app.use(express.static(path.join(__dirname, '../dist')))

// SPA catch-all — serve index.html for any non-API, non-asset route
// This lets React Router handle client-side routing
app.get('*', (req, res, next) => {
  // Skip API routes (already handled above) and requests for static assets
  if (req.path.startsWith('/api/') || req.path.includes('.')) {
    return next()
  }
  res.sendFile(path.join(__dirname, '../dist/index.html'))
})

app.listen(PORT, () => {
  log.info(`Server running on http://localhost:${PORT}`)
  log.info(`Health check: http://localhost:${PORT}/api/health`)
  if (ZOHO_ENABLED) {
    log.info(`Zoho CRM forwarding: ENABLED`)
    log.info(`Zoho API domain: ${ZOHO_API_DOMAIN}`)
    log.info(`Zoho Accounts domain: ${ZOHO_ACCOUNTS_DOMAIN}`)
    log.info(`Zoho Cases forwarding: ${process.env.ZOHO_CASES_ENABLED === 'true' ? 'ENABLED' : 'DISABLED'}`)
  } else {
    log.info('Zoho CRM forwarding: DISABLED (set ZOHO_ENABLED=true to enable)')
  }
  log.info(`Rate limiting: ${rateLimitMax} requests per ${rateLimitWindowMs / 1000} seconds`)
  log.info(`Security headers: Helmet enabled with CSP configured`)
  log.info(`CORS origin: ${corsOrigin}`)
})
