// src/auth/rate_limit.ts
var g = globalThis;
if (!g.__caRateLimitBuckets) g.__caRateLimitBuckets = /* @__PURE__ */ new Map();
var buckets = g.__caRateLimitBuckets;
var WINDOW_MS = 6e4;
function memoryCheckAndBump(keyId, limitPerMinute) {
  const now = Date.now();
  let b = buckets.get(keyId);
  if (!b || now - b.windowStart >= WINDOW_MS) {
    b = { windowStart: now, count: 1 };
    buckets.set(keyId, b);
    return null;
  }
  if (b.count >= limitPerMinute) {
    const retryAfterMs = WINDOW_MS - (now - b.windowStart);
    return Math.max(1, Math.ceil(retryAfterMs / 1e3));
  }
  b.count++;
  return null;
}
var redisLoadPromise = null;
function allowFallback() {
  const v = (process.env.RATE_LIMIT_BACKEND_ALLOW_FALLBACK ?? "").toLowerCase();
  return v === "1" || v === "true" || v === "yes";
}
async function loadRedis() {
  if (g.__caRateLimitRedis != null) return g.__caRateLimitRedis;
  if (redisLoadPromise) return redisLoadPromise;
  redisLoadPromise = (async () => {
    const url = process.env.REDIS_URL;
    if (!url) {
      const msg = "[rate_limit] RATE_LIMIT_BACKEND=redis but REDIS_URL is unset.";
      if (allowFallback()) {
        console.warn(`${msg} Falling back to memory (RATE_LIMIT_BACKEND_ALLOW_FALLBACK=true).`);
        g.__caRateLimitRedis = "unavailable";
        return "unavailable";
      }
      throw new Error(
        `${msg} Set REDIS_URL or opt into the memory fallback with RATE_LIMIT_BACKEND_ALLOW_FALLBACK=true.`
      );
    }
    try {
      const mod = await import("ioredis");
      const Ctor = mod.default ?? mod.Redis;
      const client = new Ctor(url);
      const asEmitter = client;
      if (typeof asEmitter.on === "function") {
        asEmitter.on("error", (err) => {
          console.warn("[rate_limit] redis client error:", err);
        });
      }
      g.__caRateLimitRedis = client;
      return client;
    } catch (err) {
      const detail = err instanceof Error ? err.message : String(err);
      const msg = "[rate_limit] RATE_LIMIT_BACKEND=redis but ioredis isn't installed. Run `npm i ioredis` in the deployment image.";
      if (allowFallback()) {
        console.warn(`${msg} Falling back to memory (RATE_LIMIT_BACKEND_ALLOW_FALLBACK=true). detail=${detail}`);
        g.__caRateLimitRedis = "unavailable";
        return "unavailable";
      }
      throw new Error(`${msg} detail=${detail}`);
    }
  })();
  return redisLoadPromise;
}
async function redisCheckAndBump(client, keyId, limitPerMinute) {
  const now = Date.now();
  const windowStart = Math.floor(now / WINDOW_MS) * WINDOW_MS;
  const bucketKey = `as:rl:${keyId}:${windowStart}`;
  try {
    const count = await client.incr(bucketKey);
    if (count === 1) {
      await client.pexpire(bucketKey, WINDOW_MS + 1e3);
    }
    if (count > limitPerMinute) {
      const retryAfterMs = windowStart + WINDOW_MS - now;
      return Math.max(1, Math.ceil(retryAfterMs / 1e3));
    }
    return null;
  } catch (err) {
    console.warn("[rate_limit] redis error, falling back to memory for this request:", err);
    return memoryCheckAndBump(keyId, limitPerMinute);
  }
}
function getBackend() {
  const v = (process.env.RATE_LIMIT_BACKEND ?? "memory").toLowerCase();
  return v === "redis" ? "redis" : "memory";
}
async function checkAndBump(keyId, limitPerMinute) {
  if (limitPerMinute == null || limitPerMinute <= 0) return null;
  if (getBackend() === "redis") {
    const { hasFeature } = await import("./license/index.js");
    if (!hasFeature("redis_rate_limit")) {
      return memoryCheckAndBump(keyId, limitPerMinute);
    }
    const client = await loadRedis();
    if (client !== "unavailable") {
      return redisCheckAndBump(client, keyId, limitPerMinute);
    }
  }
  return memoryCheckAndBump(keyId, limitPerMinute);
}
function resetRateLimits() {
  buckets.clear();
}
function peekRateLimit(keyId) {
  return buckets.get(keyId);
}
async function validateBackend() {
  if (getBackend() !== "redis") return;
  await loadRedis();
}
function _setRedisClientForTesting(client) {
  g.__caRateLimitRedis = client;
  redisLoadPromise = null;
}

export {
  checkAndBump,
  resetRateLimits,
  peekRateLimit,
  validateBackend,
  _setRedisClientForTesting
};
