import { Request, Response, NextFunction } from 'express';

/**
 * IP-based rate limiter for live URL generation (Section 8.6)
 *
 * Limits: 10 new generations per hour per IP address
 * - Separate from API key rate limits
 * - Cache hits do NOT count toward limit
 * - Only new generations (cache MISS) count
 *
 * Implementation uses in-memory store with automatic cleanup
 */

interface RateLimitEntry {
  count: number;
  resetAt: number; // Timestamp when count resets
}

// In-memory store for IP rate limits
// Key: IP address, Value: { count, resetAt }
const ipRateLimits = new Map<string, RateLimitEntry>();

// Configuration
const RATE_LIMIT_WINDOW_MS = 60 * 60 * 1000; // 1 hour
const MAX_REQUESTS_PER_WINDOW = 10; // 10 new generations per hour

/**
 * Get client IP address from request
 * Supports X-Forwarded-For header for proxy/load balancer setups
 */
const getClientIp = (req: Request): string => {
  // Check X-Forwarded-For header (used by proxies/load balancers)
  const forwardedFor = req.headers['x-forwarded-for'];
  if (forwardedFor) {
    // X-Forwarded-For can contain multiple IPs, take the first one
    const ips = Array.isArray(forwardedFor) ? forwardedFor[0] : forwardedFor;
    return ips?.split(',')[0]?.trim() || req.ip || 'unknown';
  }

  // Fall back to req.ip
  return req.ip || 'unknown';
};

/**
 * Clean up expired entries from the rate limit store
 * Called periodically to prevent memory leaks
 */
const cleanupExpiredEntries = (): void => {
  const now = Date.now();
  for (const [ip, entry] of ipRateLimits.entries()) {
    if (now > entry.resetAt) {
      ipRateLimits.delete(ip);
    }
  }
};

// Run cleanup every 5 minutes
setInterval(cleanupExpiredEntries, 5 * 60 * 1000);

/**
 * Check if IP has exceeded rate limit
 * Returns true if limit exceeded, false otherwise
 */
export const checkIpRateLimit = (ip: string): boolean => {
  const now = Date.now();
  const entry = ipRateLimits.get(ip);

  if (!entry) {
    // First request from this IP
    ipRateLimits.set(ip, {
      count: 1,
      resetAt: now + RATE_LIMIT_WINDOW_MS,
    });
    return false; // Not limited
  }

  // Check if window has expired
  if (now > entry.resetAt) {
    // Reset the counter
    entry.count = 1;
    entry.resetAt = now + RATE_LIMIT_WINDOW_MS;
    return false; // Not limited
  }

  // Increment counter
  entry.count += 1;

  // Check if limit exceeded
  return entry.count > MAX_REQUESTS_PER_WINDOW;
};

/**
 * Get remaining requests for IP
 */
export const getRemainingRequests = (ip: string): number => {
  const now = Date.now();
  const entry = ipRateLimits.get(ip);

  if (!entry) {
    return MAX_REQUESTS_PER_WINDOW;
  }

  // Check if window has expired
  if (now > entry.resetAt) {
    return MAX_REQUESTS_PER_WINDOW;
  }

  return Math.max(0, MAX_REQUESTS_PER_WINDOW - entry.count);
};

/**
 * Get time until rate limit resets (in seconds)
 */
export const getResetTime = (ip: string): number => {
  const now = Date.now();
  const entry = ipRateLimits.get(ip);

  if (!entry || now > entry.resetAt) {
    return 0;
  }

  return Math.ceil((entry.resetAt - now) / 1000);
};

/**
 * Middleware: IP-based rate limiter for live URLs
 * Only increments counter on cache MISS (new generation)
 * Use this middleware BEFORE cache check, but only increment after cache MISS
 */
export const ipRateLimiterMiddleware = (req: Request, res: Response, next: NextFunction): void => {
  const ip = getClientIp(req);

  // Attach IP to request for later use
  (req as any).clientIp = ip;

  // Attach rate limit check function to request
  (req as any).checkIpRateLimit = () => {
    const limited = checkIpRateLimit(ip);
    if (limited) {
      const resetTime = getResetTime(ip);
      res.status(429).json({
        success: false,
        error: {
          message: `Rate limit exceeded. Try again in ${resetTime} seconds`,
          code: 'IP_RATE_LIMIT_EXCEEDED',
        },
      });
      res.setHeader('Retry-After', resetTime.toString());
      res.setHeader('X-RateLimit-Limit', MAX_REQUESTS_PER_WINDOW.toString());
      res.setHeader('X-RateLimit-Remaining', '0');
      res.setHeader('X-RateLimit-Reset', getResetTime(ip).toString());
      return true; // Limited
    }
    return false; // Not limited
  };

  // Set rate limit headers
  const remaining = getRemainingRequests(ip);
  const resetTime = getResetTime(ip);
  res.setHeader('X-RateLimit-Limit', MAX_REQUESTS_PER_WINDOW.toString());
  res.setHeader('X-RateLimit-Remaining', remaining.toString());
  if (resetTime > 0) {
    res.setHeader('X-RateLimit-Reset', resetTime.toString());
  }

  next();
};

/**
 * Helper function to manually increment IP rate limit counter
 * Use this after confirming cache MISS (new generation)
 */
export const incrementIpRateLimit = (_ip: string): void => {
  // Counter already incremented in checkIpRateLimit
  // This is a no-op, kept for API consistency
};
