/**
 * Rate Limiting Middleware for Redirect Intelligence v2
 * 
 * Integrates advanced rate limiting with Express middleware
 */

import { Request, Response, NextFunction } from 'express';
import { rateLimitService, RateLimitError, BurstLimitError } from '../services/rate-limit.service';
import { headerRedactionService } from '../services/header-redaction.service';
import { logger } from '../lib/logger';
import { AuthenticatedRequest } from './auth.middleware';

export interface RateLimitMiddlewareOptions {
  type: 'tracking' | 'bulk' | 'export' | 'legacy';
  keyGenerator?: (req: AuthenticatedRequest) => string;
  skipSuccessfulRequests?: boolean;
  skipFailedRequests?: boolean;
  onLimitReached?: (req: AuthenticatedRequest, res: Response) => void;
}

/**
 * Create rate limiting middleware for specific endpoint types
 */
export function createRateLimitMiddleware(options: RateLimitMiddlewareOptions) {
  const {
    type,
    keyGenerator = defaultKeyGenerator,
    skipSuccessfulRequests = false,
    skipFailedRequests = false,
    onLimitReached,
  } = options;

  return async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
    try {
      const key = keyGenerator(req);
      const userId = req.user?.id;

      // Check rate limits
      const rateLimitInfo = await rateLimitService.checkRateLimit(type, key, userId);

      // Set rate limit headers
      res.set({
        'X-RateLimit-Limit': rateLimitInfo.limit.toString(),
        'X-RateLimit-Remaining': rateLimitInfo.remaining.toString(),
        'X-RateLimit-Reset': rateLimitInfo.reset.toISOString(),
        'X-RateLimit-Tier': rateLimitInfo.tier,
      });

      // Check burst limits for authenticated users
      if (userId && (type === 'tracking' || type === 'bulk')) {
        await rateLimitService.checkBurstLimit(userId);
      }

      // Log rate limit usage (with redacted headers)
      logger.debug('Rate limit check passed', {
        type,
        key: headerRedactionService.partiallyRedactValue(key),
        userId: userId ? headerRedactionService.partiallyRedactValue(userId) : undefined,
        remaining: rateLimitInfo.remaining,
        tier: rateLimitInfo.tier,
        userAgent: req.get('User-Agent'),
        ip: req.ip,
      });

      next();
    } catch (error) {
      if (error instanceof RateLimitError) {
        // Set rate limit headers even when limit is exceeded
        res.set({
          'X-RateLimit-Limit': '0',
          'X-RateLimit-Remaining': '0',
          'X-RateLimit-Reset': error.reset.toISOString(),
          'X-RateLimit-Tier': error.tier,
          'Retry-After': Math.ceil((error.reset.getTime() - Date.now()) / 1000).toString(),
        });

        logger.warn('Rate limit exceeded', {
          type,
          tier: error.tier,
          userId: req.user?.id,
          ip: req.ip,
          userAgent: req.get('User-Agent'),
        });

        if (onLimitReached) {
          onLimitReached(req, res);
          return;
        }

        return res.status(429).json({
          success: false,
          error: 'Rate limit exceeded',
          message: `Too many requests for ${error.tier} tier. Please try again later.`,
          retryAfter: error.reset.toISOString(),
          tier: error.tier,
        });
      }

      if (error instanceof BurstLimitError) {
        res.set({
          'X-RateLimit-Type': 'burst',
          'X-RateLimit-Tier': error.tier,
          'Retry-After': '60', // 1 minute for burst limits
        });

        logger.warn('Burst limit exceeded', {
          type,
          tier: error.tier,
          limit: error.limit,
          userId: req.user?.id,
          ip: req.ip,
        });

        return res.status(429).json({
          success: false,
          error: 'Burst limit exceeded',
          message: `Too many requests per minute for ${error.tier} tier (limit: ${error.limit}/min).`,
          retryAfter: new Date(Date.now() + 60000).toISOString(),
          tier: error.tier,
        });
      }

      // Other errors
      logger.error('Rate limit middleware error:', error);
      next(error);
    }
  };
}

/**
 * Default key generator function
 */
function defaultKeyGenerator(req: AuthenticatedRequest): string {
  // Use user ID for authenticated requests, IP for anonymous
  return req.user ? `user:${req.user.id}` : `ip:${req.ip}`;
}

/**
 * Key generator for organization-based limits
 */
export function organizationKeyGenerator(req: AuthenticatedRequest): string {
  if (req.user?.memberships?.[0]?.organizationId) {
    return `org:${req.user.memberships[0].organizationId}`;
  }
  return defaultKeyGenerator(req);
}

/**
 * Key generator for project-based limits
 */
export function projectKeyGenerator(req: AuthenticatedRequest): string {
  const projectId = req.body?.projectId || req.params?.projectId || req.query?.projectId;
  if (projectId) {
    return `project:${projectId}`;
  }
  return defaultKeyGenerator(req);
}

/**
 * Middleware to add rate limit status to response headers
 */
export function addRateLimitStatus(type: 'tracking' | 'bulk' | 'export' | 'legacy') {
  return async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
    try {
      const key = defaultKeyGenerator(req);
      const userId = req.user?.id;
      
      const status = await rateLimitService.getRateLimitStatus(type, key, userId);
      
      res.set({
        'X-RateLimit-Limit': status.limit.toString(),
        'X-RateLimit-Remaining': status.remaining.toString(),
        'X-RateLimit-Reset': status.reset.toISOString(),
        'X-RateLimit-Tier': status.tier,
      });

      next();
    } catch (error) {
      logger.warn('Failed to add rate limit status headers:', error);
      next(); // Continue even if we can't add headers
    }
  };
}

/**
 * Predefined middleware instances for common use cases
 */

// Legacy endpoints (maintain existing behavior)
export const legacyRateLimit = createRateLimitMiddleware({
  type: 'legacy',
});

// Tracking endpoints
export const trackingRateLimit = createRateLimitMiddleware({
  type: 'tracking',
});

// Bulk processing endpoints
export const bulkRateLimit = createRateLimitMiddleware({
  type: 'bulk',
});

// Export endpoints
export const exportRateLimit = createRateLimitMiddleware({
  type: 'export',
});

// Organization-based tracking limits
export const orgTrackingRateLimit = createRateLimitMiddleware({
  type: 'tracking',
  keyGenerator: organizationKeyGenerator,
});

// Project-based tracking limits
export const projectTrackingRateLimit = createRateLimitMiddleware({
  type: 'tracking',
  keyGenerator: projectKeyGenerator,
});

/**
 * Middleware to log requests with redacted headers
 */
export function requestLogger(options: { includeBody?: boolean; redactionLevel?: 'full' | 'partial' } = {}) {
  const { includeBody = false, redactionLevel = 'full' } = options;

  return (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
    const startTime = Date.now();

    // Redact request data for logging
    const redactedRequest = headerRedactionService.redactLogData({
      method: req.method,
      url: req.url,
      headers: req.headers,
      body: includeBody ? req.body : undefined,
      user: req.user ? {
        id: req.user.id,
        email: headerRedactionService.partiallyRedactValue(req.user.email),
      } : undefined,
      ip: req.ip,
      userAgent: req.get('User-Agent'),
    }, { redactionLevel });

    // Override res.end to log response
    const originalEnd = res.end;
    res.end = function(chunk?: any, encoding?: any) {
      const duration = Date.now() - startTime;
      
      logger.info('Request completed', {
        ...redactedRequest,
        statusCode: res.statusCode,
        duration,
        contentLength: res.get('Content-Length'),
      });

      originalEnd.call(this, chunk, encoding);
    };

    next();
  };
}

/**
 * Error handler for rate limiting errors
 */
export function rateLimitErrorHandler(
  error: Error,
  req: AuthenticatedRequest,
  res: Response,
  next: NextFunction
) {
  if (error instanceof RateLimitError || error instanceof BurstLimitError) {
    // These should have been handled by the middleware, but just in case
    return res.status(429).json({
      success: false,
      error: 'Rate limit exceeded',
      message: error.message,
    });
  }

  next(error);
}
