/**
 * Math Platform Backend Server
 *
 * Main Express server configuration with all middleware and routes
 */

import express, { Application } from 'express';
import cors from 'cors';
import helmet from 'helmet';
import compression from 'compression';
import morgan from 'morgan';
import { logger } from './shared/utils/logger';
import { prisma } from './shared/database/prisma.client';
import {
  errorHandler,
  notFoundHandler,
  requestId,
} from './shared/middleware/validation.middleware';
import {
  standardRateLimiter,
  exerciseRateLimiter,
  aiRateLimiter,
} from './shared/middleware/rate-limit.middleware';
import { authRoutes } from './modules/auth';
import moduleRoutes from './modules/module/module.routes';
import exerciseRoutes from './modules/exercise/exercise.routes';
import progressRoutes from './modules/progress/progress.routes';
import { rankingRoutes } from './modules/ranking';
import userRoutes from './modules/user/user.routes';
import { startNotificationWorker } from './workers/notification-sender.worker';
import { createAdminRoutes } from './modules/admin/admin.routes';
import { checkAIHealth } from './config/ai.health';
import { initializeRedis } from './shared/config/redis';
import { disconnectRedis } from './shared/database/redis.client';

/**
 * Create and configure Express application
 */
function createApp(): Application {
  const app = express();

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

  // Security middleware
  app.use(helmet({
    contentSecurityPolicy: {
      directives: {
        defaultSrc: ["'self'"],
        styleSrc: ["'self'", "'unsafe-inline'"],
        scriptSrc: ["'self'"],
        imgSrc: ["'self'", 'data:', 'https:'],
      },
    },
  }));

  // CORS configuration
  app.use(cors({
    origin: process.env.CORS_ORIGIN?.split(',') || 'http://localhost:3000',
    credentials: true,
    methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
    allowedHeaders: ['Content-Type', 'Authorization', 'X-Request-ID'],
  }));

  // Compression
  app.use(compression());

  // Body parsing
  app.use(express.json({ limit: '10mb' }));
  app.use(express.urlencoded({ extended: true, limit: '10mb' }));

  // Request ID middleware
  app.use(requestId);

  // HTTP request logging
  if (process.env.NODE_ENV !== 'test') {
    app.use(morgan('combined', {
      stream: {
        write: (message: string) => logger.info(message.trim(), 'HTTP Request'),
      },
    }));
  }

  // Health check endpoint (no rate limiting)
  app.get('/health', async (_req, res) => {
    try {
      const dbHealth = await prisma.healthCheck();
      const aiHealth = await checkAIHealth();

      // Determine overall status
      const overallStatus = dbHealth && aiHealth.status !== 'error' ? 'ok' : 'error';

      res.json({
        status: overallStatus,
        timestamp: new Date().toISOString(),
        uptime: process.uptime(),
        database: {
          status: dbHealth ? 'ok' : 'error',
        },
        ai: aiHealth,
        environment: process.env.NODE_ENV,
      });
    } catch (error) {
      res.status(503).json({
        status: 'error',
        timestamp: new Date().toISOString(),
        database: {
          status: 'error',
        },
        ai: {
          status: 'error',
          model: 'MiniMax-M2.5',
          latency: null,
          lastChecked: new Date().toISOString(),
          error: 'Health check failed',
        },
      });
    }
  });

  // API routes
  const apiRouter = express.Router();

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

  // Note: authRateLimiter is applied to specific routes in auth.routes.ts (login/register/forgot-password only)
  // GET /me, POST /refresh, POST /logout are NOT rate-limited to avoid false positives

  // Apply standard rate limiting to non-auth API routes only
  // Auth routes have their own rate limiter and shouldn't be double-counted
  apiRouter.use('/modules', standardRateLimiter);
  apiRouter.use('/exercises', standardRateLimiter, exerciseRateLimiter);
  apiRouter.use('/progress', standardRateLimiter);
  apiRouter.use('/ranking', standardRateLimiter);
  apiRouter.use('/users', standardRateLimiter);
  apiRouter.use('/admin', standardRateLimiter);
  apiRouter.use('/ai', standardRateLimiter, aiRateLimiter);

  // Mount route modules (auth routes bypass standard limiter - only authRateLimiter applies)
  apiRouter.use('/auth', authRoutes);
  apiRouter.use('/modules', moduleRoutes);
  apiRouter.use('/exercises', exerciseRoutes);
  apiRouter.use('/progress', progressRoutes);
  apiRouter.use('/ranking', rankingRoutes);
  apiRouter.use('/users', userRoutes);
  apiRouter.use('/admin', createAdminRoutes());

  // Mount API router
  app.use('/api', apiRouter);

  // 404 handler
  app.use(notFoundHandler);

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

  return app;
}

/**
 * Start the server
 */
async function startServer(): Promise<void> {
  const port = process.env.PORT || 3001;

  // Initialize Redis connection
  initializeRedis();

  const app = createApp();

  // Start background workers
  startNotificationWorker();
  logger.info('Notification worker started');

  const server = app.listen(port, () => {
    logger.info({
      port,
      environment: process.env.NODE_ENV,
      url: `http://localhost:${port}`,
    }, 'Server started');
  });

  // Graceful shutdown
  const shutdown = async (signal: string) => {
    logger.info({ signal }, 'Shutdown signal received');

    server.close(async () => {
      logger.info('HTTP server closed');

      try {
        await prisma.$disconnect();
        await disconnectRedis();
        logger.info('Database and Redis connections closed');

        process.exit(0);
      } catch (error) {
        logger.error({ error }, 'Error during shutdown');
        process.exit(1);
      }
    });

    // Force shutdown after 10 seconds
    setTimeout(() => {
      logger.error('Forced shutdown after timeout');
      process.exit(1);
    }, 10000);
  };

  process.on('SIGTERM', () => shutdown('SIGTERM'));
  process.on('SIGINT', () => shutdown('SIGINT'));
}

/**
 * Start server only if not in test mode
 */
if (process.env.NODE_ENV !== 'test') {
  startServer().catch((error) => {
    logger.error({ error }, 'Failed to start server');
    process.exit(1);
  });
}

export { createApp, startServer };
export default createApp;
