/**
 * Rate limiting using Redis sliding window algorithm.
 *
 * Tiers:
 * - free: 100 requests/minute
 * - developer: 1000 requests/minute
 * - enterprise: unlimited
 */

// Use require for ioredis to avoid ESM import issues
import type { Redis } from 'ioredis';
import IORedisModule from 'ioredis';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const IORedis = (IORedisModule as any).default || IORedisModule;

export type RateLimitTier = 'free' | 'developer' | 'enterprise';

interface TierConfig {
  requestsPerMinute: number;
  burstLimit: number;
}

const TIER_CONFIGS: Record<RateLimitTier, TierConfig> = {
  free: { requestsPerMinute: 100, burstLimit: 20 },
  developer: { requestsPerMinute: 1000, burstLimit: 100 },
  enterprise: { requestsPerMinute: Infinity, burstLimit: Infinity },
};

export interface RateLimitResult {
  allowed: boolean;
  remaining: number;
  resetAt: number;
  tier: RateLimitTier;
}

export class RateLimiter {
  private redis: Redis;
  private windowMs = 60_000; // 1 minute

  constructor(redisUrl: string) {
    this.redis = new IORedis(redisUrl);
  }

  async checkLimit(key: string, tier: RateLimitTier): Promise<RateLimitResult> {
    const config = TIER_CONFIGS[tier];

    // Enterprise tier bypasses rate limiting
    if (tier === 'enterprise') {
      return {
        allowed: true,
        remaining: Infinity,
        resetAt: 0,
        tier,
      };
    }

    const now = Date.now();
    const windowStart = now - this.windowMs;
    const redisKey = `ratelimit:${key}`;

    // Use Redis sorted set for sliding window
    const pipeline = this.redis.pipeline();

    // Remove expired entries
    pipeline.zremrangebyscore(redisKey, 0, windowStart);

    // Count current requests in window
    pipeline.zcard(redisKey);

    // Add current request
    pipeline.zadd(redisKey, now, `${now}-${Math.random()}`);

    // Set expiry on the key
    pipeline.expire(redisKey, 120);

    const results = await pipeline.exec();
    const currentCount = (results?.[1]?.[1] as number) || 0;

    const allowed = currentCount < config.requestsPerMinute;
    const remaining = Math.max(0, config.requestsPerMinute - currentCount - 1);

    // Calculate reset time
    const oldestRequest = await this.redis.zrange(redisKey, 0, 0, 'WITHSCORES');
    const resetAt = oldestRequest.length >= 2
      ? parseInt(oldestRequest[1]) + this.windowMs
      : now + this.windowMs;

    return {
      allowed,
      remaining: allowed ? remaining : 0,
      resetAt,
      tier,
    };
  }

  async getUsageStats(key: string): Promise<{
    requestsLastMinute: number;
    requestsLastHour: number;
    requestsLastDay: number;
  }> {
    const now = Date.now();
    const redisKey = `ratelimit:${key}`;

    const pipeline = this.redis.pipeline();
    pipeline.zcount(redisKey, now - 60_000, now);
    pipeline.zcount(redisKey, now - 3600_000, now);
    pipeline.zcount(redisKey, now - 86400_000, now);

    const results = await pipeline.exec();

    return {
      requestsLastMinute: (results?.[0]?.[1] as number) || 0,
      requestsLastHour: (results?.[1]?.[1] as number) || 0,
      requestsLastDay: (results?.[2]?.[1] as number) || 0,
    };
  }

  async close(): Promise<void> {
    await this.redis.quit();
  }
}
