/**
 * MailFlow Backend Server
 * Main entry point
 */

import 'dotenv/config'
import express from 'express'
import cors from 'cors'
import { fileURLToPath } from 'url'
import { dirname, join } from 'path'

// Config & Middleware
import { config, validateConfig } from './config/index.mjs'
import { errorHandler, asyncHandler, AppError, NotFoundError, ValidationError, AuthorizationError } from './middleware/errorHandler.mjs'
import { respond } from './utils/response.mjs'
import { logger, log } from './middleware/logger.mjs'
import { limiters } from './middleware/rateLimit.mjs'
import { requireAuth } from './middleware/auth.mjs'

// Routes
import oauthRoutes from './routes/oauth.mjs'
import emailRoutes from './routes/email.mjs'
import stripeRoutes from './routes/stripe.mjs'
import apiRoutes from './routes/api.mjs'
import analyticsRoutes from './routes/analytics.mjs'
import webhookRoutes from './routes/webhook.mjs'
import { startCounterJobs } from './jobs/reset-counters.mjs'

const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)

// Validate configuration
validateConfig()

// Create Express app
const app = express()

// Trust proxy (for rate limiting behind reverse proxy)
app.set('trust proxy', 1)

// Request ID middleware
app.use((req, res, next) => {
  req.id = Math.random().toString(36).substring(2, 15)
  res.setHeader('X-Request-ID', req.id)
  next()
})

// CORS
app.use(cors(config.cors))

// Request logging
app.use(logger({
  skip: (req) => req.path === '/api/health' || req.path.startsWith('/assets'),
}))

// Rate limiting
app.use('/api', limiters.api)

// Static files
app.use(express.static(join(__dirname, '..', 'public')))

// Gitea webhook: raw body for X-Gitea-Signature (must match signed bytes exactly).
// type: () => true — Gitea may send application/json; charset=utf-8 or similar; strict 'application/json' can skip parsing and leave body empty.
app.use('/api/webhook', express.raw({ type: () => true, limit: '2mb' }))
app.use('/api/webhook', webhookRoutes)

// Body parsing (BEFORE routes, AFTER static)
// Note: Stripe webhook needs raw body, handled in stripe routes
app.use('/api', express.json({ limit: '1mb' }))
app.use('/api', express.urlencoded({ extended: true }))

// Health check (no rate limit)
app.get('/api/health', (req, res) => {
  res.json({
    success: true,
    data: {
      status: 'healthy',
      timestamp: new Date().toISOString(),
      version: process.env.npm_package_version || '1.0.0',
      environment: config.nodeEnv,
      uptime: Math.floor(process.uptime()),
    },
  })
})

// API Routes
app.use('/api/oauth', oauthRoutes)
app.use('/api/email', emailRoutes)
app.use('/api/subscription', stripeRoutes)
app.use('/api/analytics', analyticsRoutes)
app.use('/api', apiRoutes)

// Preferences endpoints (inline for simplicity)
import { userPreferences } from './services/database.mjs'
import { isAdmin } from './config/index.mjs'

/**
 * GET /api/me
 * Returns current user context (JWT). isAdmin from verified email.
 */
app.get('/api/me', requireAuth, asyncHandler(async (req, res) => {
  respond.success(res, { isAdmin: isAdmin(req.appwriteUser.email) })
}))

app.get('/api/preferences', requireAuth, asyncHandler(async (req, res) => {
  const userId = req.appwriteUser.id

  const prefs = await userPreferences.getByUser(userId)
  respond.success(res, prefs?.preferences || {
    vipSenders: [],
    blockedSenders: [],
    customRules: [],
    priorityTopics: [],
  })
}))

app.post('/api/preferences', requireAuth, asyncHandler(async (req, res) => {
  const userId = req.appwriteUser.id
  const { ...preferences } = req.body

  await userPreferences.upsert(userId, preferences)
  respond.success(res, null, 'Einstellungen gespeichert')
}))

/**
 * PATCH /api/preferences/profile
 * { displayName?, timezone?, notificationPrefs? }
 */
app.patch('/api/preferences/profile', requireAuth, asyncHandler(async (req, res) => {
  const userId = req.appwriteUser.id
  const { displayName, timezone, notificationPrefs } = req.body
  const prefs = await userPreferences.getByUser(userId)
  const current = prefs?.preferences?.profile || userPreferences.getDefaults().profile
  const profile = {
    ...current,
    ...(displayName !== undefined && { displayName }),
    ...(timezone !== undefined && { timezone }),
    ...(notificationPrefs !== undefined && { notificationPrefs }),
  }
  await userPreferences.upsert(userId, { profile })
  respond.success(res, { profile }, 'Profile saved')
}))

/**
 * GET /api/preferences/ai-control
 * Get AI Control settings
 */
app.get('/api/preferences/ai-control', requireAuth, asyncHandler(async (req, res) => {
  const userId = req.appwriteUser.id

  const prefs = await userPreferences.getByUser(userId)
  const preferences = prefs?.preferences || userPreferences.getDefaults()
  
  respond.success(res, {
    enabledCategories: preferences.enabledCategories || [],
    categoryActions: preferences.categoryActions || {},
    autoDetectCompanies: preferences.autoDetectCompanies !== undefined ? preferences.autoDetectCompanies : true,
    cleanup: preferences.cleanup || userPreferences.getDefaults().cleanup,
  })
}))

/**
 * POST /api/preferences/ai-control
 * Save AI Control settings
 */
app.post('/api/preferences/ai-control', requireAuth, asyncHandler(async (req, res) => {
  const userId = req.appwriteUser.id
  const { enabledCategories, categoryActions, autoDetectCompanies, cleanup } = req.body

  const updates = {}
  if (enabledCategories !== undefined) updates.enabledCategories = enabledCategories
  if (categoryActions !== undefined) updates.categoryActions = categoryActions
  if (autoDetectCompanies !== undefined) updates.autoDetectCompanies = autoDetectCompanies
  if (cleanup !== undefined) updates.cleanup = cleanup
  
  await userPreferences.upsert(userId, updates)
  respond.success(res, null, 'AI Control settings saved')
}))

/**
 * GET /api/preferences/company-labels
 * Get company labels
 */
app.get('/api/preferences/company-labels', requireAuth, asyncHandler(async (req, res) => {
  const userId = req.appwriteUser.id

  const prefs = await userPreferences.getByUser(userId)
  const preferences = prefs?.preferences || userPreferences.getDefaults()
  
  respond.success(res, preferences.companyLabels || [])
}))

/**
 * POST /api/preferences/company-labels
 * Save/Update company label
 */
app.post('/api/preferences/company-labels', requireAuth, asyncHandler(async (req, res) => {
  const userId = req.appwriteUser.id
  const { companyLabel } = req.body
  if (!companyLabel) throw new ValidationError('companyLabel is required')
  
  const prefs = await userPreferences.getByUser(userId)
  const preferences = prefs?.preferences || userPreferences.getDefaults()
  
  const companyLabels = preferences.companyLabels || []
  
  // Generate ID if not provided
  if (!companyLabel.id) {
    companyLabel.id = `label_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
  }
  
  // Update or add label
  const existingIndex = companyLabels.findIndex(l => l.id === companyLabel.id)
  if (existingIndex >= 0) {
    companyLabels[existingIndex] = companyLabel
  } else {
    companyLabels.push(companyLabel)
  }
  
  await userPreferences.upsert(userId, { companyLabels })
  respond.success(res, companyLabel, 'Company label saved')
}))

/**
 * DELETE /api/preferences/company-labels/:id
 * Delete company label
 */
app.delete('/api/preferences/company-labels/:id', requireAuth, asyncHandler(async (req, res) => {
  const userId = req.appwriteUser.id
  const { id } = req.params
  if (!id) throw new ValidationError('label id is required')
  
  const prefs = await userPreferences.getByUser(userId)
  const preferences = prefs?.preferences || userPreferences.getDefaults()
  
  const companyLabels = (preferences.companyLabels || []).filter(l => l.id !== id)
  
  await userPreferences.upsert(userId, { companyLabels })
  respond.success(res, null, 'Company label deleted')
}))

/**
 * GET /api/preferences/name-labels
 * Get name labels (worker labels). Admin only.
 */
app.get('/api/preferences/name-labels', requireAuth, asyncHandler(async (req, res) => {
  if (!isAdmin(req.appwriteUser.email)) throw new AuthorizationError('Admin access required for name labels')

  const userId = req.appwriteUser.id
  const prefs = await userPreferences.getByUser(userId)
  const preferences = prefs?.preferences || userPreferences.getDefaults()
  respond.success(res, preferences.nameLabels || [])
}))

/**
 * POST /api/preferences/name-labels
 * Save/Update name label (worker). Admin only.
 */
app.post('/api/preferences/name-labels', requireAuth, asyncHandler(async (req, res) => {
  if (!isAdmin(req.appwriteUser.email)) throw new AuthorizationError('Admin access required for name labels')

  const userId = req.appwriteUser.id
  const { nameLabel } = req.body
  if (!nameLabel) throw new ValidationError('nameLabel is required')

  const prefs = await userPreferences.getByUser(userId)
  const preferences = prefs?.preferences || userPreferences.getDefaults()
  const nameLabels = preferences.nameLabels || []

  if (!nameLabel.id) {
    nameLabel.id = `namelabel_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
  }
  const existingIndex = nameLabels.findIndex(l => l.id === nameLabel.id)
  if (existingIndex >= 0) {
    nameLabels[existingIndex] = nameLabel
  } else {
    nameLabels.push(nameLabel)
  }

  await userPreferences.upsert(userId, { nameLabels })
  respond.success(res, nameLabel, 'Name label saved')
}))

/**
 * DELETE /api/preferences/name-labels/:id
 * Delete name label. Admin only.
 */
app.delete('/api/preferences/name-labels/:id', requireAuth, asyncHandler(async (req, res) => {
  if (!isAdmin(req.appwriteUser.email)) throw new AuthorizationError('Admin access required for name labels')

  const userId = req.appwriteUser.id
  const { id } = req.params
  if (!id) throw new ValidationError('label id is required')

  const prefs = await userPreferences.getByUser(userId)
  const preferences = prefs?.preferences || userPreferences.getDefaults()
  const nameLabels = (preferences.nameLabels || []).filter(l => l.id !== id)
  await userPreferences.upsert(userId, { nameLabels })
  respond.success(res, null, 'Name label deleted')
}))

// Legacy Stripe webhook endpoint
app.use('/stripe', stripeRoutes)

// Unmatched /api → JSON 404 (Express 4 treats '/api/*' as a literal path, not a wildcard)
app.use((req, res, next) => {
  const pathOnly = req.originalUrl.split('?')[0]
  if (!pathOnly.startsWith('/api')) {
    return next()
  }
  next(new NotFoundError('Endpoint'))
})

// SPA fallback: never send index.html for /api (avoids 404/HTML when public/index.html is missing)
app.get('*', (req, res, next) => {
  const pathOnly = req.originalUrl.split('?')[0]
  if (pathOnly.startsWith('/api')) {
    return next(new NotFoundError('Endpoint'))
  }
  const indexPath = join(__dirname, '..', 'public', 'index.html')
  res.sendFile(indexPath, (err) => {
    if (err) {
      next(
        new AppError(
          'public/index.html fehlt. In Entwicklung: Frontend über Vite (z. B. http://localhost:5173) starten; für Produktion: Client-Build nach public/ legen.',
          404,
          'NOT_FOUND'
        )
      )
    }
  })
})

// Global error handler (must be last)
app.use(errorHandler)

// Graceful shutdown
let server

function gracefulShutdown(signal) {
  log.info(`${signal} empfangen, Server wird heruntergefahren...`)

  server.close(() => {
    log.info('HTTP Server geschlossen')
    process.exit(0)
  })

  // Force close after 10 seconds
  setTimeout(() => {
    log.error('Erzwungenes Herunterfahren')
    process.exit(1)
  }, 10000)
}

process.on('SIGTERM', () => gracefulShutdown('SIGTERM'))
process.on('SIGINT', () => gracefulShutdown('SIGINT'))

// Handle uncaught errors
process.on('uncaughtException', (err) => {
  log.error('Uncaught Exception:', { error: err.message, stack: err.stack })
  process.exit(1)
})

process.on('unhandledRejection', (reason, promise) => {
  log.error('Unhandled Rejection:', { reason, promise })
})

// Start server
server = app.listen(config.port, () => {
  console.log('')
  log.success(`Server gestartet auf Port ${config.port}`)
  log.info(`Frontend URL: ${config.frontendUrl}`)
  log.info(`Environment: ${config.nodeEnv}`)
  console.log('')
  console.log(`  🌐 API:      http://localhost:${config.port}/api`)
  console.log(`  💚 Health:   http://localhost:${config.port}/api/health`)
  console.log('')
  startCounterJobs()
})

export default app
