/**
 * Rate Limiting Middleware
 *
 * Express rate limiting with Redis store for production
 * Falls back to in-memory store when Redis is not available
 */

import { Request, Response } from 'express';
import rateLimit, { Options, Store } from 'express-rate-limit';
import RedisStore from 'rate-limit-redis';
import { getUserRateLimitKey } from './auth.middleware';
import { getRedisClient } from '../config/redis';
import { logger } from '../utils/logger';

/**
 * Create Redis store if available, otherwise return undefined
 */
function createStore(prefix: string): RedisStore | undefined {
  const redisClient = getRedisClient();

  if (redisClient) {
    try {
      return new RedisStore({
        // @ts-expect-error - rate-limit-redis expects sendCommand function
        sendCommand: (...args: string[]) => redisClient.call(...args) as Promise<unknown>,
        prefix: `rl:${prefix}:`,
      });
    } catch (error) {
      logger.warn({ error, prefix }, 'Failed to create Redis store, using in-memory fallback');
      return undefined;
    }
  }

  return undefined;
}

/**
 * Create base rate limit options
 */
function createRateLimitOptions(
  windowMs: number,
  maxRequests: number,
  message: string,
  prefix: string,
  skipSuccessfulRequests = false
): Options {
  const store = createStore(prefix);
  
  const options = {
    windowMs,
    limit: maxRequests,
    message: {
      success: false,
      error: {
        code: 'RATE_LIMIT_ERROR',
        message,
      },
    },
    standardHeaders: true,
    legacyHeaders: false,
    keyGenerator: (req: Request): string => {
      return getUserRateLimitKey(req);
    },
    handler: (_req: Request, res: Response): void => {
      res.status(429).json({
        success: false,
        error: {
          code: 'RATE_LIMIT_ERROR',
          message,
          retryAfter: Math.floor(windowMs / 1000),
        },
      });
    },
  } as unknown as Options;

  if (skipSuccessfulRequests) {
    options.skipSuccessfulRequests = true;
  }

  // Only add store if it's defined
  if (store) {
    options.store = store as unknown as Store;
  }

  return options;
}

/**
 * Standard rate limiter for general API endpoints
 */
export const standardRateLimiter = rateLimit(
  createRateLimitOptions(
    parseInt(process.env.RATE_LIMIT_WINDOW_MS || '900000'), // 15 minutes
    parseInt(process.env.RATE_LIMIT_MAX_REQUESTS || '100'),
    'Too many requests, please try again later',
    'std'
  )
);

/**
 * Strict rate limiter for authentication endpoints
 */
export const authRateLimiter = rateLimit({
  windowMs: parseInt(process.env.AUTH_RATE_LIMIT_WINDOW_MS || '900000'), // 15 minutes
  max: parseInt(process.env.AUTH_RATE_LIMIT_MAX || '20'), // 20 attempts per window
  message: {
    success: false,
    error: {
      code: 'RATE_LIMIT_ERROR',
      message: 'Too many authentication attempts, please try again later',
    },
  },
  standardHeaders: true,
  legacyHeaders: false,
  keyGenerator: (req: Request): string => {
    // Use email from body + IP to avoid shared limits in Docker/proxy
    const email = (req.body as Record<string, unknown>)?.email;
    const ip = req.ip || req.socket.remoteAddress || 'unknown';
    if (typeof email === 'string' && email) {
      return `ratelimit:auth:${email.toLowerCase()}:${ip}`;
    }
    return `ratelimit:auth:ip:${ip}`;
  },
  skipSuccessfulRequests: true,
  handler: (_req: Request, res: Response): void => {
    res.status(429).json({
      success: false,
      error: {
        code: 'RATE_LIMIT_ERROR',
        message: 'Too many authentication attempts, please try again later',
        retryAfter: 900, // 15 minutes
      },
    });
  },
});

/**
 * Rate limiter for exercise attempts
 */
export const exerciseRateLimiter = rateLimit(
  createRateLimitOptions(
    60 * 1000, // 1 minute
    20, // 20 attempts per minute
    'Too many exercise attempts, please slow down',
    'ex'
  )
);

/**
 * Rate limiter for AI generation endpoints
 */
export const aiRateLimiter = rateLimit(
  createRateLimitOptions(
    60 * 60 * 1000, // 1 hour
    50, // 50 AI requests per hour
    'AI generation limit reached, please try again later',
    'ai'
  )
);

/**
 * Custom rate limiter factory
 */
export function createRateLimiter(options: {
  windowMs: number;
  max: number;
  prefix?: string;
  skipSuccessfulRequests?: boolean;
}) {
  return rateLimit(
    createRateLimitOptions(
      options.windowMs,
      options.max,
      'Rate limit exceeded',
      options.prefix || 'custom',
      options.skipSuccessfulRequests || false
    )
  );
}
