/**
 * 安全增强中间件
 * @file security.js
 * @description 实现登录失败限制、会话超时、输入验证等安全功能
 */
const rateLimit = require('express-rate-limit');
const logger = require('../utils/logger');

// 登录失败次数记录
const loginAttempts = new Map();
const MAX_LOGIN_ATTEMPTS = 5; // 最大登录尝试次数
const LOCKOUT_DURATION = 15 * 60 * 1000; // 锁定15分钟

/**
 * 登录失败次数限制中间件
 */
const loginAttemptsLimiter = (req, res, next) => {
  const clientIP = req.ip || req.connection.remoteAddress;
  const identifier = req.body.username || clientIP; // 使用用户名或IP作为标识
  
  const now = Date.now();
  const attempts = loginAttempts.get(identifier) || { count: 0, firstAttempt: now, lockedUntil: 0 };
  
  // 检查是否仍在锁定期内
  if (attempts.lockedUntil > now) {
    const remainingTime = Math.ceil((attempts.lockedUntil - now) / 1000 / 60); // 分钟
    logger.warn(`用户 ${identifier} 尝试在锁定期内登录, IP: ${clientIP}`);
    
    return res.status(429).json({
      success: false,
      message: `登录失败次数过多，请 ${remainingTime} 分钟后再试`,
      lockedUntil: new Date(attempts.lockedUntil).toISOString()
    });
  }
  
  // 重置过期的记录
  if (now - attempts.firstAttempt > LOCKOUT_DURATION) {
    attempts.count = 0;
    attempts.firstAttempt = now;
  }
  
  // 检查失败次数
  if (attempts.count >= MAX_LOGIN_ATTEMPTS) {
    attempts.lockedUntil = now + LOCKOUT_DURATION;
    loginAttempts.set(identifier, attempts);
    
    logger.warn(`用户 ${identifier} 登录失败次数达到上限，已锁定, IP: ${clientIP}`);
    
    return res.status(429).json({
      success: false,
      message: `登录失败次数过多，已锁定15分钟`,
      lockedUntil: new Date(attempts.lockedUntil).toISOString()
    });
  }
  
  // 将attempts信息附加到请求对象，供后续使用
  req.loginAttempts = attempts;
  req.loginIdentifier = identifier;
  
  next();
};

/**
 * 记录登录失败
 */
const recordLoginFailure = (req, res, next) => {
  // 检查响应状态，如果是401（认证失败），记录失败次数
  const originalSend = res.send;
  
  res.send = function(data) {
    if (res.statusCode === 401 && req.loginIdentifier) {
      const attempts = req.loginAttempts;
      attempts.count++;
      loginAttempts.set(req.loginIdentifier, attempts);
      
      logger.warn(`用户 ${req.loginIdentifier} 登录失败，失败次数: ${attempts.count}/${MAX_LOGIN_ATTEMPTS}, IP: ${req.ip}`);
    } else if (res.statusCode === 200 && req.loginIdentifier) {
      // 登录成功，清除失败记录
      loginAttempts.delete(req.loginIdentifier);
      logger.info(`用户 ${req.loginIdentifier} 登录成功，已清除失败记录, IP: ${req.ip}`);
    }
    
    return originalSend.call(this, data);
  };
  
  next();
};

/**
 * 清除过期的登录失败记录
 */
const cleanupExpiredAttempts = () => {
  const now = Date.now();
  const expiredKeys = [];
  
  for (const [key, attempts] of loginAttempts.entries()) {
    if (now - attempts.firstAttempt > LOCKOUT_DURATION * 2) { // 保留双倍锁定时间
      expiredKeys.push(key);
    }
  }
  
  expiredKeys.forEach(key => loginAttempts.delete(key));
  
  if (expiredKeys.length > 0) {
    logger.info(`清理了 ${expiredKeys.length} 个过期的登录失败记录`);
  }
};

// 每小时清理一次过期记录
setInterval(cleanupExpiredAttempts, 60 * 60 * 1000);

/**
 * API请求频率限制
 */
const apiRateLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15分钟窗口
  max: 1000, // 限制每个IP每15分钟最多1000个请求
  message: {
    success: false,
    message: '请求过于频繁，请稍后再试'
  },
  standardHeaders: true,
  legacyHeaders: false,
  handler: (req, res) => {
    logger.warn(`API请求频率超限, IP: ${req.ip}, URL: ${req.originalUrl}`);
    res.status(429).json({
      success: false,
      message: '请求过于频繁，请稍后再试'
    });
  }
});

/**
 * 登录请求频率限制
 */
const loginRateLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15分钟窗口
  max: 10, // 限制每个IP每15分钟最多10次登录尝试
  message: {
    success: false,
    message: '登录请求过于频繁，请稍后再试'
  },
  skipSuccessfulRequests: true, // 成功请求不计入限制
  handler: (req, res) => {
    logger.warn(`登录请求频率超限, IP: ${req.ip}`);
    res.status(429).json({
      success: false,
      message: '登录请求过于频繁，请15分钟后再试'
    });
  }
});

/**
 * 输入验证和XSS防护
 */
const inputSanitizer = (req, res, next) => {
  // 递归清理对象中的危险字符
  const sanitizeObject = (obj) => {
    if (obj === null || obj === undefined) return obj;
    
    if (typeof obj === 'string') {
      // 移除潜在的XSS攻击字符
      return obj
        .replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '') // 移除script标签
        .replace(/javascript:/gi, '') // 移除javascript协议
        .replace(/on\w+\s*=/gi, '') // 移除事件处理器
        .trim();
    }
    
    if (Array.isArray(obj)) {
      return obj.map(sanitizeObject);
    }
    
    if (typeof obj === 'object') {
      const sanitized = {};
      for (const [key, value] of Object.entries(obj)) {
        sanitized[key] = sanitizeObject(value);
      }
      return sanitized;
    }
    
    return obj;
  };
  
  // 清理请求体
  if (req.body) {
    req.body = sanitizeObject(req.body);
  }
  
  // 清理查询参数
  if (req.query) {
    req.query = sanitizeObject(req.query);
  }
  
  next();
};

/**
 * 会话超时检查
 */
const sessionTimeoutCheck = (req, res, next) => {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1];
  
  if (token) {
    try {
      const jwt = require('jsonwebtoken');
      const decoded = jwt.verify(token, process.env.JWT_SECRET || 'your_jwt_secret_key');
      
      // 检查token是否即将过期（剩余时间少于1小时）
      const now = Math.floor(Date.now() / 1000);
      const timeUntilExpiry = decoded.exp - now;
      
      if (timeUntilExpiry < 3600) { // 1小时 = 3600秒
        res.set('X-Token-Expiry-Warning', 'true');
        res.set('X-Token-Expires-In', timeUntilExpiry.toString());
      }
    } catch (error) {
      // Token无效或已过期，不做特殊处理，让后续中间件处理
    }
  }
  
  next();
};

/**
 * 安全响应头设置
 */
const securityHeaders = (req, res, next) => {
  // 防止点击劫持
  res.set('X-Frame-Options', 'DENY');
  
  // 防止MIME类型嗅探
  res.set('X-Content-Type-Options', 'nosniff');
  
  // XSS保护
  res.set('X-XSS-Protection', '1; mode=block');
  
  // 引用者策略
  res.set('Referrer-Policy', 'strict-origin-when-cross-origin');
  
  // 内容安全策略 - 允许百度地图API
  const cspPolicy = [
    "default-src 'self'",
    "script-src 'self' 'unsafe-inline' 'unsafe-eval' api.map.baidu.com apimaponline0.bdimg.com apimaponline1.bdimg.com apimaponline2.bdimg.com apimaponline3.bdimg.com dlswbr.baidu.com miao.baidu.com",
    "style-src 'self' 'unsafe-inline'",
    "img-src 'self' data: api.map.baidu.com apimaponline0.bdimg.com apimaponline1.bdimg.com apimaponline2.bdimg.com apimaponline3.bdimg.com dlswbr.baidu.com miao.baidu.com",
    "connect-src 'self' api.map.baidu.com apimaponline0.bdimg.com apimaponline1.bdimg.com apimaponline2.bdimg.com apimaponline3.bdimg.com dlswbr.baidu.com miao.baidu.com",
    "frame-src 'self'"
  ].join('; ');
  
  res.set('Content-Security-Policy', cspPolicy);
  
  next();
};

/**
 * 获取登录失败统计信息
 * @returns {Object} 统计信息
 */
const getLoginAttemptStats = () => {
  const now = Date.now();
  let totalAttempts = 0;
  let lockedAccounts = 0;
  let recentFailures = 0;
  
  for (const [identifier, attempts] of loginAttempts.entries()) {
    totalAttempts += attempts.count;
    
    if (attempts.lockedUntil > now) {
      lockedAccounts++;
    }
    
    if (now - attempts.firstAttempt < 60 * 60 * 1000) { // 最近1小时
      recentFailures += attempts.count;
    }
  }
  
  return {
    totalTrackedIdentifiers: loginAttempts.size,
    totalFailedAttempts: totalAttempts,
    currentlyLockedAccounts: lockedAccounts,
    recentHourFailures: recentFailures,
    maxAttemptsAllowed: MAX_LOGIN_ATTEMPTS,
    lockoutDurationMinutes: LOCKOUT_DURATION / 60 / 1000
  };
};

module.exports = {
  loginAttemptsLimiter,
  recordLoginFailure,
  apiRateLimiter,
  loginRateLimiter,
  inputSanitizer,
  sessionTimeoutCheck,
  securityHeaders,
  getLoginAttemptStats,
  cleanupExpiredAttempts
};
