import { fileURLToPath as __fileURLToPath } from 'url';
import { dirname as __pathDirname } from 'path';
const __filename = __fileURLToPath(import.meta.url);
const __dirname = __pathDirname(__filename);
import { logger } from "../core/monitoring/logger.js";
import { metrics } from "../core/monitoring/metrics.js";
class ExponentialRateLimiter {
  redis;
  localCache = /* @__PURE__ */ new Map();
  localCacheOrder = [];
  config;
  constructor(redis, config = {}) {
    this.redis = redis;
    this.config = {
      baseLimit: 10,
      windowMs: 60 * 1e3,
      // 1 minute
      maxBackoff: 32,
      backoffMultiplier: 2,
      localCacheSize: 1e4,
      localCacheTTL: 5 * 60 * 1e3,
      // 5 minutes
      whitelistIPs: [],
      blacklistIPs: [],
      customKeyGenerator: (req) => this.getClientIdentifier(req),
      ...config
    };
    setInterval(() => this.cleanupLocalCache(), this.config.localCacheTTL);
  }
  /**
   * Main middleware function with exponential backoff
   */
  middleware() {
    return async (req, res, next) => {
      const clientId = this.config.customKeyGenerator(req);
      if (this.isWhitelisted(clientId)) {
        return next();
      }
      if (this.isBlacklisted(clientId)) {
        metrics.increment("rate_limit.blacklisted", { ip: clientId });
        res.status(403).json({
          error: "Access denied",
          code: "BLACKLISTED_IP"
        });
        return;
      }
      try {
        let entry = this.getFromLocalCache(clientId);
        if (!entry) {
          entry = await this.getFromRedis(clientId);
        }
        const now = Date.now();
        if (entry.blockedUntil && entry.blockedUntil > now) {
          const retryAfter = Math.ceil((entry.blockedUntil - now) / 1e3);
          metrics.increment("rate_limit.blocked", {
            ip: clientId,
            backoffLevel: String(entry.backoffLevel)
          });
          res.status(429).json({
            error: "Too many requests - exponential backoff applied",
            code: "RATE_LIMIT_BACKOFF",
            retryAfter,
            backoffLevel: entry.backoffLevel
          });
          res.setHeader("Retry-After", String(retryAfter));
          res.setHeader("X-RateLimit-BackoffLevel", String(entry.backoffLevel));
          return;
        }
        if (now - entry.firstRequest > this.config.windowMs) {
          entry = {
            requests: 1,
            violations: Math.max(0, entry.violations - 1),
            // Decay violations
            backoffLevel: Math.max(0, entry.backoffLevel - 1),
            // Decay backoff
            firstRequest: now,
            lastRequest: now
          };
        } else {
          entry.requests++;
          entry.lastRequest = now;
        }
        const currentLimit = Math.max(
          1,
          Math.floor(
            this.config.baseLimit / Math.pow(this.config.backoffMultiplier, entry.backoffLevel)
          )
        );
        if (entry.requests > currentLimit) {
          entry.violations++;
          if (entry.backoffLevel < Math.log2(this.config.maxBackoff)) {
            entry.backoffLevel++;
          }
          const backoffDuration = this.config.windowMs * Math.pow(this.config.backoffMultiplier, entry.backoffLevel);
          entry.blockedUntil = now + backoffDuration;
          await this.updateCaches(clientId, entry);
          const retryAfter = Math.ceil(backoffDuration / 1e3);
          metrics.increment("rate_limit.exceeded", {
            ip: clientId,
            violations: String(entry.violations),
            backoffLevel: String(entry.backoffLevel)
          });
          res.status(429).json({
            error: "Rate limit exceeded - entering exponential backoff",
            code: "RATE_LIMIT_EXCEEDED",
            retryAfter,
            violations: entry.violations,
            backoffLevel: entry.backoffLevel,
            currentLimit
          });
          res.setHeader("Retry-After", String(retryAfter));
          res.setHeader("X-RateLimit-Limit", String(currentLimit));
          res.setHeader("X-RateLimit-Remaining", "0");
          res.setHeader("X-RateLimit-BackoffLevel", String(entry.backoffLevel));
          return;
        }
        await this.updateCaches(clientId, entry);
        res.setHeader("X-RateLimit-Limit", String(currentLimit));
        res.setHeader(
          "X-RateLimit-Remaining",
          String(currentLimit - entry.requests)
        );
        res.setHeader(
          "X-RateLimit-Reset",
          String(new Date(entry.firstRequest + this.config.windowMs).getTime())
        );
        if (entry.backoffLevel > 0) {
          res.setHeader("X-RateLimit-BackoffLevel", String(entry.backoffLevel));
        }
        next();
      } catch (error) {
        logger.error(
          "Rate limiter error",
          error instanceof Error ? error : new Error(String(error))
        );
        next();
      }
    };
  }
  /**
   * Get client identifier from request
   */
  getClientIdentifier(req) {
    const forwarded = req.headers["x-forwarded-for"];
    const realIp = req.headers["x-real-ip"];
    const cfIp = req.headers["cf-connecting-ip"];
    if (typeof forwarded === "string") {
      return forwarded.split(",")[0].trim();
    }
    if (typeof realIp === "string") {
      return realIp;
    }
    if (typeof cfIp === "string") {
      return cfIp;
    }
    return req.ip || req.socket.remoteAddress || "unknown";
  }
  /**
   * Check if IP is whitelisted
   */
  isWhitelisted(ip) {
    return this.config.whitelistIPs.includes(ip) || ip === "127.0.0.1" || ip === "::1" || ip.startsWith("192.168.") || ip.startsWith("10.");
  }
  /**
   * Check if IP is blacklisted
   */
  isBlacklisted(ip) {
    return this.config.blacklistIPs.includes(ip);
  }
  /**
   * Get rate limit entry from local cache
   */
  getFromLocalCache(clientId) {
    const cached = this.localCache.get(clientId);
    if (cached) {
      const now = Date.now();
      if (now - cached.lastRequest < this.config.localCacheTTL) {
        return cached;
      }
      this.localCache.delete(clientId);
      const index = this.localCacheOrder.indexOf(clientId);
      if (index > -1) {
        this.localCacheOrder.splice(index, 1);
      }
    }
    return null;
  }
  /**
   * Get rate limit entry from Redis
   */
  async getFromRedis(clientId) {
    const key = `rate_limit:${clientId}`;
    const data = await this.redis.get(key);
    if (data) {
      return JSON.parse(data);
    }
    return {
      requests: 0,
      violations: 0,
      backoffLevel: 0,
      firstRequest: Date.now(),
      lastRequest: Date.now()
    };
  }
  /**
   * Update both local cache and Redis
   */
  async updateCaches(clientId, entry) {
    if (!this.localCache.has(clientId)) {
      if (this.localCache.size >= this.config.localCacheSize) {
        const oldest = this.localCacheOrder.shift();
        if (oldest) {
          this.localCache.delete(oldest);
        }
      }
      this.localCacheOrder.push(clientId);
    }
    this.localCache.set(clientId, entry);
    const key = `rate_limit:${clientId}`;
    const ttl = Math.ceil(
      this.config.windowMs * Math.pow(2, entry.backoffLevel) / 1e3
    );
    await this.redis.setex(key, ttl, JSON.stringify(entry));
  }
  /**
   * Clean up stale entries from local cache
   */
  cleanupLocalCache() {
    const now = Date.now();
    const staleThreshold = now - this.config.localCacheTTL;
    for (const [clientId, entry] of this.localCache.entries()) {
      if (entry.lastRequest < staleThreshold) {
        this.localCache.delete(clientId);
        const index = this.localCacheOrder.indexOf(clientId);
        if (index > -1) {
          this.localCacheOrder.splice(index, 1);
        }
      }
    }
    metrics.record("rate_limit.local_cache_size", this.localCache.size);
  }
  /**
   * Reset rate limit for a specific client
   */
  async reset(clientId) {
    this.localCache.delete(clientId);
    const index = this.localCacheOrder.indexOf(clientId);
    if (index > -1) {
      this.localCacheOrder.splice(index, 1);
    }
    await this.redis.del(`rate_limit:${clientId}`);
  }
  /**
   * Get current rate limit status for a client
   */
  async getStatus(clientId) {
    let entry = this.getFromLocalCache(clientId);
    if (!entry) {
      const data = await this.redis.get(`rate_limit:${clientId}`);
      if (data) {
        entry = JSON.parse(data);
      }
    }
    return entry;
  }
  /**
   * Add IP to blacklist
   */
  blacklistIP(ip) {
    if (!this.config.blacklistIPs.includes(ip)) {
      this.config.blacklistIPs.push(ip);
      logger.warn("IP blacklisted", { ip });
    }
  }
  /**
   * Remove IP from blacklist
   */
  unblacklistIP(ip) {
    const index = this.config.blacklistIPs.indexOf(ip);
    if (index > -1) {
      this.config.blacklistIPs.splice(index, 1);
      logger.info("IP unblacklisted", { ip });
    }
  }
}
export {
  ExponentialRateLimiter
};
//# sourceMappingURL=exponential-rate-limiter.js.map
