/**
 * Advanced Rate Limiting Service for Redirect Intelligence v2
 * 
 * Implements user-tier rate limiting with organization-based quotas
 * using in-memory rate limiting with rate-limiter-flexible
 */

import { RateLimiterMemory } from 'rate-limiter-flexible';
import { Request, Response, NextFunction } from 'express';
import { z } from 'zod';
import { logger } from '../lib/logger';
import { prisma } from '../lib/prisma';
import { AuthenticatedRequest } from '../middleware/auth.middleware';

// Rate limit tiers based on organization plan
export interface RateLimitTier {
  name: string;
  requestsPerHour: number;
  requestsPerMinute: number;
  bulkJobsPerDay: number;
  maxUrls: number;
  exportLimit: number;
}

export const RATE_LIMIT_TIERS: Record<string, RateLimitTier> = {
  free: {
    name: 'Free',
    requestsPerHour: 100,
    requestsPerMinute: 10,
    bulkJobsPerDay: 2,
    maxUrls: 50,
    exportLimit: 5,
  },
  pro: {
    name: 'Pro',
    requestsPerHour: 1000,
    requestsPerMinute: 50,
    bulkJobsPerDay: 20,
    maxUrls: 1000,
    exportLimit: 100,
  },
  enterprise: {
    name: 'Enterprise',
    requestsPerHour: 10000,
    requestsPerMinute: 200,
    bulkJobsPerDay: 100,
    maxUrls: 10000,
    exportLimit: 1000,
  },
};

export const ANONYMOUS_TIER: RateLimitTier = {
  name: 'Anonymous',
  requestsPerHour: 50,
  requestsPerMinute: 5,
  bulkJobsPerDay: 0,
  maxUrls: 10,
  exportLimit: 0,
};

export interface RateLimitInfo {
  limit: number;
  remaining: number;
  reset: Date;
  tier: string;
}

export class RateLimitService {
  private rateLimiters: Map<string, RateLimiterMemory>;

  constructor() {
    this.rateLimiters = new Map();
    this.initializeRateLimiters();
  }

  /**
   * Initialize rate limiters for different endpoints and tiers
   */
  private initializeRateLimiters(): void {
    // Legacy endpoints (preserve existing behavior) - MEMORY (PERMANENT)
    this.rateLimiters.set('legacy', new RateLimiterMemory({
      keyPrefix: 'rl_legacy',
      points: 100, // requests
      duration: 3600, // per hour
      blockDuration: 3600, // block for 1 hour
      execEvenly: true,
    }));

    // Anonymous users - MEMORY (PERMANENT)
    this.rateLimiters.set('anonymous', new RateLimiterMemory({
      keyPrefix: 'rl_anon',
      points: ANONYMOUS_TIER.requestsPerHour,
      duration: 3600,
      blockDuration: 3600,
      execEvenly: true,
    }));

    // Authenticated users by tier
    Object.keys(RATE_LIMIT_TIERS).forEach(tier => {
      const config = RATE_LIMIT_TIERS[tier];
      
      // Hourly limits - MEMORY (PERMANENT)
      this.rateLimiters.set(`user_${tier}_hour`, new RateLimiterMemory({
        keyPrefix: `rl_user_${tier}_h`,
        points: config.requestsPerHour,
        duration: 3600,
        blockDuration: 900, // 15 minutes
        execEvenly: true,
      }));

      // Per-minute limits (burst protection) - MEMORY (PERMANENT)
      this.rateLimiters.set(`user_${tier}_minute`, new RateLimiterMemory({
        keyPrefix: `rl_user_${tier}_m`,
        points: config.requestsPerMinute,
        duration: 60,
        blockDuration: 60,
        execEvenly: true,
      }));

      // Bulk job limits (daily) - MEMORY (PERMANENT)
      this.rateLimiters.set(`bulk_${tier}_day`, new RateLimiterMemory({
        keyPrefix: `rl_bulk_${tier}_d`,
        points: config.bulkJobsPerDay,
        duration: 86400, // 24 hours
        blockDuration: 86400,
        execEvenly: false,
      }));

      // Export limits (daily) - MEMORY (PERMANENT)
      this.rateLimiters.set(`export_${tier}_day`, new RateLimiterMemory({
        keyPrefix: `rl_export_${tier}_d`,
        points: config.exportLimit,
        duration: 86400,
        blockDuration: 86400,
        execEvenly: false,
      }));
    });
  }

  /**
   * Get user's rate limit tier based on organization plan
   */
  async getUserTier(userId?: string): Promise<RateLimitTier> {
    if (!userId || userId === 'anonymous-user') {
      return ANONYMOUS_TIER;
    }

    // TEMPORARILY RETURN FREE TIER FOR ALL AUTHENTICATED USERS TO AVOID DB HANGS
    return RATE_LIMIT_TIERS.free;

    /* ORIGINAL CODE - TEMPORARILY DISABLED DUE TO DB HANGING
    try {
      const user = await prisma.user.findUnique({
        where: { id: userId },
        include: {
          memberships: {
            include: {
              organization: true,
            },
          },
        },
      });

      if (!user || !user.memberships.length) {
        return RATE_LIMIT_TIERS.free;
      }

      // Use the highest tier from all organizations
      const plans = user.memberships.map(m => m.organization.plan);
      if (plans.includes('enterprise')) return RATE_LIMIT_TIERS.enterprise;
      if (plans.includes('pro')) return RATE_LIMIT_TIERS.pro;
      return RATE_LIMIT_TIERS.free;
    } catch (error) {
      logger.error('Failed to get user tier:', error);
      return RATE_LIMIT_TIERS.free;
    }
    */
  }

  /**
   * Check if request is allowed under rate limits
   */
  async checkRateLimit(
    type: 'tracking' | 'bulk' | 'export' | 'legacy',
    key: string,
    userId?: string
  ): Promise<RateLimitInfo> {
    try {
      const tier = await this.getUserTier(userId);
      let limiterKey: string;
      let limit: number;

      if (type === 'legacy') {
        limiterKey = 'legacy';
        limit = 100;
      } else if (!userId || userId === 'anonymous-user') {
        limiterKey = 'anonymous';
        limit = ANONYMOUS_TIER.requestsPerHour;
      } else {
        const tierName = tier.name.toLowerCase();
        
        switch (type) {
          case 'tracking':
            limiterKey = `user_${tierName}_hour`;
            limit = tier.requestsPerHour;
            break;
          case 'bulk':
            limiterKey = `bulk_${tierName}_day`;
            limit = tier.bulkJobsPerDay;
            break;
          case 'export':
            limiterKey = `export_${tierName}_day`;
            limit = tier.exportLimit;
            break;
          default:
            throw new Error(`Unknown rate limit type: ${type}`);
        }
      }

      const rateLimiter = this.rateLimiters.get(limiterKey);
      if (!rateLimiter) {
        throw new Error(`Rate limiter not found: ${limiterKey}`);
      }

      const result = await rateLimiter.consume(key, 1);
      
      return {
        limit,
        remaining: result.remainingPoints || 0,
        reset: new Date(Date.now() + (result.msBeforeNext || 0)),
        tier: tier.name,
      };
    } catch (error) {
      if (error instanceof Error && error.message.includes('Rate limit')) {
        // This is a rate limit exceeded error
        const tier = await this.getUserTier(userId);
        throw new RateLimitError(tier.name, 0, new Date(Date.now() + 3600000));
      }
      
      logger.error('Rate limit check failed:', error);
      throw error;
    }
  }

  /**
   * Check burst protection (per-minute limits for authenticated users)
   */
  async checkBurstLimit(userId: string): Promise<void> {
    const tier = await this.getUserTier(userId);
    if (tier === ANONYMOUS_TIER || userId === 'anonymous-user') return;

    const tierName = tier.name.toLowerCase();
    const limiterKey = `user_${tierName}_minute`;
    const rateLimiter = this.rateLimiters.get(limiterKey);

    if (!rateLimiter) {
      logger.warn(`Burst rate limiter not found: ${limiterKey}`);
      return;
    }

    try {
      await rateLimiter.consume(userId, 1);
    } catch (error) {
      throw new BurstLimitError(tier.name, tier.requestsPerMinute);
    }
  }

  /**
   * Get current rate limit status without consuming points
   */
  async getRateLimitStatus(
    type: 'tracking' | 'bulk' | 'export' | 'legacy',
    key: string,
    userId?: string
  ): Promise<RateLimitInfo> {
    const tier = await this.getUserTier(userId);
    let limiterKey: string;
    let limit: number;

    if (type === 'legacy') {
      limiterKey = 'legacy';
      limit = 100;
    } else if (!userId || userId === 'anonymous-user') {
      limiterKey = 'anonymous';
      limit = ANONYMOUS_TIER.requestsPerHour;
    } else {
      const tierName = tier.name.toLowerCase();
      
      switch (type) {
        case 'tracking':
          limiterKey = `user_${tierName}_hour`;
          limit = tier.requestsPerHour;
          break;
        case 'bulk':
          limiterKey = `bulk_${tierName}_day`;
          limit = tier.bulkJobsPerDay;
          break;
        case 'export':
          limiterKey = `export_${tierName}_day`;
          limit = tier.exportLimit;
          break;
        default:
          throw new Error(`Unknown rate limit type: ${type}`);
      }
    }

    const rateLimiter = this.rateLimiters.get(limiterKey);
    if (!rateLimiter) {
      throw new Error(`Rate limiter not found: ${limiterKey}`);
    }

    try {
      const result = await rateLimiter.get(key);
      
      return {
        limit,
        remaining: result ? result.remainingPoints || 0 : limit,
        reset: result ? new Date(Date.now() + (result.msBeforeNext || 0)) : new Date(),
        tier: tier.name,
      };
    } catch (error) {
      logger.error('Failed to get rate limit status:', error);
      return {
        limit,
        remaining: limit,
        reset: new Date(),
        tier: tier.name,
      };
    }
  }

  /**
   * Reset rate limits for a specific key (admin function)
   */
  async resetRateLimit(key: string, type?: string): Promise<void> {
    try {
      if (type) {
        const rateLimiter = this.rateLimiters.get(type);
        if (rateLimiter) {
          await rateLimiter.delete(key);
        }
      } else {
        // Reset all rate limiters for this key
        for (const rateLimiter of this.rateLimiters.values()) {
          await rateLimiter.delete(key).catch(() => {}); // Ignore errors
        }
      }
      
      logger.info(`Rate limit reset for key: ${key}`, { type });
    } catch (error) {
      logger.error('Failed to reset rate limit:', error);
      throw error;
    }
  }

  /**
   * Get rate limit statistics
   */
  async getStatistics(): Promise<{
    totalRequests: number;
    activeKeys: number;
    tierDistribution: Record<string, number>;
  }> {
    try {
      // In-memory rate limiting doesn't expose detailed statistics
      // Return basic info about configured rate limiters
      return {
        totalRequests: 0, // Not available with in-memory limiting
        activeKeys: this.rateLimiters.size,
        tierDistribution: {
          anonymous: 1,
          free: Object.keys(RATE_LIMIT_TIERS).length,
          pro: Object.keys(RATE_LIMIT_TIERS).length,
          enterprise: Object.keys(RATE_LIMIT_TIERS).length,
        },
      };
    } catch (error) {
      logger.error('Failed to get rate limit statistics:', error);
      return {
        totalRequests: 0,
        activeKeys: 0,
        tierDistribution: { anonymous: 0, free: 0, pro: 0, enterprise: 0 },
      };
    }
  }
}

/**
 * Custom error classes for rate limiting
 */
export class RateLimitError extends Error {
  constructor(
    public tier: string,
    public remaining: number,
    public reset: Date
  ) {
    super(`Rate limit exceeded for ${tier} tier`);
    this.name = 'RateLimitError';
  }
}

export class BurstLimitError extends Error {
  constructor(
    public tier: string,
    public limit: number
  ) {
    super(`Burst limit exceeded for ${tier} tier (${limit} requests per minute)`);
    this.name = 'BurstLimitError';
  }
}

// Singleton instance
export const rateLimitService = new RateLimitService();
