// src/middleware/rateLimitMiddleware.js
// === ADDED FOR RATE LIMITING ===
// Rate limiting middleware for OTP requests and verification

const { getRedisClient, isRedisReady } = require('../services/redisClient');

// In-memory fallback store (used when Redis is not available)
// Unified store for all rate limiting keys
const memoryStore = {};

// Clean up expired entries from memory store periodically
setInterval(() => {
  const now = Date.now();
  Object.keys(memoryStore).forEach((key) => {
    if (memoryStore[key].expiresAt && memoryStore[key].expiresAt < now) {
      delete memoryStore[key];
    }
  });
}, 60000); // Clean up every minute

// Configuration from environment variables
const config = {
  // OTP request limits
  OTP_REQ_PHONE_10MIN_LIMIT: parseInt(process.env.OTP_REQ_PHONE_10MIN_LIMIT || '3', 10),
  OTP_REQ_PHONE_DAY_LIMIT: parseInt(process.env.OTP_REQ_PHONE_DAY_LIMIT || '10', 10),
  OTP_REQ_IP_10MIN_LIMIT: parseInt(process.env.OTP_REQ_IP_10MIN_LIMIT || '20', 10),
  OTP_REQ_IP_DAY_LIMIT: parseInt(process.env.OTP_REQ_IP_DAY_LIMIT || '100', 10),
  
  // OTP verification limits
  OTP_VERIFY_MAX_ATTEMPTS: parseInt(process.env.OTP_VERIFY_MAX_ATTEMPTS || '5', 10),
  OTP_VERIFY_FAILED_PER_HOUR_LIMIT: parseInt(process.env.OTP_VERIFY_FAILED_PER_HOUR_LIMIT || '10', 10),
  
  // OTP validity
  OTP_TTL_SECONDS: parseInt(process.env.OTP_TTL_SECONDS || '120', 10), // 2 minutes
};

/**
 * Helper: Increment counter in Redis or memory store
 */
async function incrementCounter(key, ttlSeconds) {
  const redis = await getRedisClient();
  
  if (isRedisReady() && redis) {
    try {
      const count = await redis.incr(key);
      if (count === 1) {
        // First increment, set TTL
        await redis.expire(key, ttlSeconds);
      }
      return count;
    } catch (err) {
      console.error('Redis increment error, falling back to memory:', err);
      // Fall through to memory store
    }
  }
  
  // Memory store fallback
  const now = Date.now();
  if (!memoryStore[key]) {
    memoryStore[key] = {
      count: 0,
      expiresAt: now + ttlSeconds * 1000,
    };
  }
  
  memoryStore[key].count++;
  return memoryStore[key].count;
}

/**
 * Helper: Get counter value from Redis or memory store
 */
async function getCounter(key) {
  const redis = await getRedisClient();
  
  if (isRedisReady() && redis) {
    try {
      const count = await redis.get(key);
      return count ? parseInt(count, 10) : 0;
    } catch (err) {
      console.error('Redis get error, falling back to memory:', err);
      // Fall through to memory store
    }
  }
  
  // Memory store fallback
  if (memoryStore[key] && memoryStore[key].expiresAt > Date.now()) {
    return memoryStore[key].count || 0;
  }
  
  return 0;
}

/**
 * Helper: Check if key exists in Redis or memory store
 */
async function exists(key) {
  const redis = await getRedisClient();
  
  if (isRedisReady() && redis) {
    try {
      const result = await redis.exists(key);
      return result === 1;
    } catch (err) {
      console.error('Redis exists error, falling back to memory:', err);
      // Fall through to memory store
    }
  }
  
  // Memory store fallback
  if (memoryStore[key] && memoryStore[key].expiresAt > Date.now()) {
    return true;
  }
  
  return false;
}

/**
 * Helper: Set key with TTL in Redis or memory store
 */
async function setWithTTL(key, value, ttlSeconds) {
  const redis = await getRedisClient();
  
  if (isRedisReady() && redis) {
    try {
      await redis.setEx(key, ttlSeconds, value);
      return true;
    } catch (err) {
      console.error('Redis setEx error, falling back to memory:', err);
      // Fall through to memory store
    }
  }
  
  // Memory store fallback
  memoryStore[key] = {
    value,
    expiresAt: Date.now() + ttlSeconds * 1000,
  };
  
  return true;
}

/**
 * === ADDED FOR 2-MIN OTP VALIDITY & NO-RESEND ===
 * Middleware: Check if there's an active OTP for the phone number
 * Prevents sending a new OTP if one is already active (within 2 minutes)
 */
async function checkActiveOtpForPhone(req, res, next) {
  try {
    const { phone_number } = req.body;
    if (!phone_number) {
      return next(); // Let validation handle this
    }

    // Normalize phone (same logic as in routes)
    const normalizedPhone = phone_number.trim().replace(/\s+/g, '');
    const phone = normalizedPhone.startsWith('+') 
      ? normalizedPhone 
      : (normalizedPhone.length === 10 ? '+91' + normalizedPhone : normalizedPhone);

    const activeOtpKey = `otp_active:phone:${phone}`;
    const hasActiveOtp = await exists(activeOtpKey);

    if (hasActiveOtp) {
      return res.status(429).json({
        success: false,
        message: 'An OTP is already active. Please wait a moment before requesting a new one.',
      });
    }

    next();
  } catch (err) {
    console.error('checkActiveOtpForPhone error:', err);
    // On error, allow the request to proceed (fail open)
    next();
  }
}

/**
 * === ADDED FOR RATE LIMITING ===
 * Middleware: Rate limit OTP requests per phone number
 */
async function rateLimitRequestOtpByPhone(req, res, next) {
  try {
    const { phone_number } = req.body;
    if (!phone_number) {
      return next(); // Let validation handle this
    }

    // Normalize phone (same logic as in routes)
    const normalizedPhone = phone_number.trim().replace(/\s+/g, '');
    const phone = normalizedPhone.startsWith('+') 
      ? normalizedPhone 
      : (normalizedPhone.length === 10 ? '+91' + normalizedPhone : normalizedPhone);

    // Check 10-minute limit
    const key10min = `otp_req:phone:${phone}:10min`;
    const count10min = await incrementCounter(key10min, 600); // 10 minutes = 600 seconds

    if (count10min > config.OTP_REQ_PHONE_10MIN_LIMIT) {
      return res.status(429).json({
        success: false,
        message: 'Too many OTP requests. Please try again later.',
      });
    }

    // Check 24-hour limit
    const keyDay = `otp_req:phone:${phone}:day`;
    const countDay = await incrementCounter(keyDay, 86400); // 24 hours = 86400 seconds

    if (countDay > config.OTP_REQ_PHONE_DAY_LIMIT) {
      return res.status(429).json({
        success: false,
        message: 'Too many OTP requests. Please try again later.',
      });
    }

    next();
  } catch (err) {
    console.error('rateLimitRequestOtpByPhone error:', err);
    // On error, allow the request to proceed (fail open)
    next();
  }
}

/**
 * === ADDED FOR RATE LIMITING ===
 * Middleware: Rate limit OTP requests per IP address
 */
async function rateLimitRequestOtpByIp(req, res, next) {
  try {
    // Get client IP (considering proxies)
    const ip = req.ip || req.connection.remoteAddress || 'unknown';

    // Check 10-minute limit
    const key10min = `otp_req:ip:${ip}:10min`;
    const count10min = await incrementCounter(key10min, 600); // 10 minutes = 600 seconds

    if (count10min > config.OTP_REQ_IP_10MIN_LIMIT) {
      return res.status(429).json({
        success: false,
        message: 'Too many OTP requests. Please try again later.',
      });
    }

    // Check 24-hour limit
    const keyDay = `otp_req:ip:${ip}:day`;
    const countDay = await incrementCounter(keyDay, 86400); // 24 hours = 86400 seconds

    if (countDay > config.OTP_REQ_IP_DAY_LIMIT) {
      return res.status(429).json({
        success: false,
        message: 'Too many OTP requests. Please try again later.',
      });
    }

    next();
  } catch (err) {
    console.error('rateLimitRequestOtpByIp error:', err);
    // On error, allow the request to proceed (fail open)
    next();
  }
}

/**
 * === ADDED FOR OTP ATTEMPT LIMIT ===
 * Middleware: Rate limit failed OTP verification attempts per phone
 */
async function rateLimitVerifyOtpByPhone(req, res, next) {
  try {
    const { phone_number } = req.body;
    if (!phone_number) {
      return next(); // Let validation handle this
    }

    // Normalize phone (same logic as in routes)
    const normalizedPhone = phone_number.trim().replace(/\s+/g, '');
    const phone = normalizedPhone.startsWith('+') 
      ? normalizedPhone 
      : (normalizedPhone.length === 10 ? '+91' + normalizedPhone : normalizedPhone);

    // Check failed verification limit per hour
    const key = `otp_verify_failed:phone:${phone}:hour`;
    const count = await getCounter(key);

    if (count >= config.OTP_VERIFY_FAILED_PER_HOUR_LIMIT) {
      return res.status(429).json({
        success: false,
        message: 'Too many attempts. Please try again later.',
      });
    }

    next();
  } catch (err) {
    console.error('rateLimitVerifyOtpByPhone error:', err);
    // On error, allow the request to proceed (fail open)
    next();
  }
}

/**
 * Helper: Mark active OTP (called after OTP is created)
 */
async function markActiveOtp(phone, ttlSeconds) {
  const key = `otp_active:phone:${phone}`;
  await setWithTTL(key, '1', ttlSeconds);
}

/**
 * Helper: Increment failed verification counter
 */
async function incrementFailedVerify(phone) {
  const key = `otp_verify_failed:phone:${phone}:hour`;
  await incrementCounter(key, 3600); // 1 hour = 3600 seconds
}

module.exports = {
  checkActiveOtpForPhone,
  rateLimitRequestOtpByPhone,
  rateLimitRequestOtpByIp,
  rateLimitVerifyOtpByPhone,
  markActiveOtp,
  incrementFailedVerify,
  config,
};

