import { RateLimiterMemory, RateLimiterRedis, RateLimiterRes } from "rate-limiter-flexible";
import { createLogger } from "../logger/index.js";
import { parseRedisUrl } from "../redis/connection.js";
const logger = createLogger("ratelimit");
class RateLimiterService {
  constructor(globalConfig) {
    this.limiters = /* @__PURE__ */ new Map();
    this.redisClient = null;
    this.globalConfig = globalConfig;
    this.trustProxyDepth = globalConfig.trustProxyDepth ?? 0;
  }
  async initialize() {
    if (this.globalConfig.strategy === "redis" && this.globalConfig.redisUrl) {
      const { default: Redis } = await import("ioredis");
      this.redisClient = new Redis({
        ...parseRedisUrl(this.globalConfig.redisUrl),
        enableOfflineQueue: false,
        maxRetriesPerRequest: 1
      });
    }
  }
  async consume(key, config) {
    if (!this.globalConfig.enabled) {
      return this.disabledResult(config);
    }
    const limiter = this.getOrCreateLimiter(config);
    try {
      const res = await limiter.consume(key, 1);
      return this.toResult(res, true);
    } catch (error) {
      if (error instanceof RateLimiterRes) {
        return this.toResult(error, false);
      }
      logger.error("Rate limiter unavailable, request was not counted", {
        err: error,
        keyPrefix: config.keyPrefix,
        strategy: this.globalConfig.strategy
      });
      return this.degradedResult(config);
    }
  }
  async get(key, config) {
    if (!this.globalConfig.enabled) return null;
    const limiter = this.getOrCreateLimiter(config);
    const res = await limiter.get(key);
    return res ? this.toResult(res, res.remainingPoints > 0) : null;
  }
  async delete(key, config) {
    if (!this.globalConfig.enabled) return;
    const limiter = this.getOrCreateLimiter(config);
    await limiter.delete(key);
  }
  async penalty(key, points, config) {
    if (!this.globalConfig.enabled) {
      return this.disabledResult(config);
    }
    const limiter = this.getOrCreateLimiter(config);
    const res = await limiter.penalty(key, points);
    return this.toResult(res, res.remainingPoints > 0);
  }
  async reward(key, points, config) {
    if (!this.globalConfig.enabled) {
      return this.disabledResult(config);
    }
    const limiter = this.getOrCreateLimiter(config);
    const res = await limiter.reward(key, points);
    return this.toResult(res, true);
  }
  async block(key, durationSec, config) {
    if (!this.globalConfig.enabled) return;
    const limiter = this.getOrCreateLimiter(config);
    await limiter.block(key, durationSec);
  }
  async destroy() {
    if (this.redisClient) {
      this.redisClient.disconnect();
    }
    this.limiters.clear();
  }
  disabledResult(config) {
    return { allowed: true, remainingPoints: config.points, msBeforeNext: 0, consumedPoints: 0 };
  }
  degradedResult(config) {
    return { ...this.disabledResult(config), degraded: true };
  }
  getOrCreateLimiter(config) {
    const cacheKey = `${config.keyPrefix ?? "default"}:${config.points}:${config.duration}:${config.blockDuration ?? 0}`;
    let limiter = this.limiters.get(cacheKey);
    if (limiter) return limiter;
    const prefix = [this.globalConfig.keyPrefix, config.keyPrefix].filter(Boolean).join(":");
    const baseOpts = {
      keyPrefix: prefix,
      points: config.points,
      duration: config.duration,
      blockDuration: config.blockDuration ?? 0
    };
    if (this.globalConfig.strategy === "redis" && this.redisClient) {
      const insuranceLimiter = new RateLimiterMemory(baseOpts);
      limiter = new RateLimiterRedis({
        ...baseOpts,
        storeClient: this.redisClient,
        insuranceLimiter,
        rejectIfRedisNotReady: false
      });
    } else {
      limiter = new RateLimiterMemory(baseOpts);
    }
    this.limiters.set(cacheKey, limiter);
    return limiter;
  }
  toResult(res, allowed) {
    return {
      allowed,
      remainingPoints: Math.max(res.remainingPoints, 0),
      msBeforeNext: res.msBeforeNext,
      consumedPoints: res.consumedPoints
    };
  }
}
export {
  RateLimiterService
};
//# sourceMappingURL=service.js.map
