import Redis from "ioredis";
import { env, isProductionRuntime } from "@/server/env";
import { AppError } from "@/server/errors";

type WindowEntry = {
  resetAt: number;
  count: number;
};

const memoryBuckets = new Map<string, WindowEntry>();
let redis: Redis | undefined;

function getRedis() {
  const config = env();
  if (!config.REDIS_URL) {
    return undefined;
  }

  if (!redis) {
    redis = new Redis(config.REDIS_URL, {
      maxRetriesPerRequest: 1,
      lazyConnect: true
    });
  }

  return redis;
}

export async function enforceRateLimit(key: string, limit: number, windowSeconds: number) {
  const config = env();
  const now = Date.now();
  const resetAt = now + windowSeconds * 1000;
  const client = getRedis();

  if (client) {
    try {
      if (client.status === "wait") {
        await client.connect();
      }
      const count = await client.incr(key);
      if (count === 1) {
        await client.expire(key, windowSeconds);
      }
      if (count > limit) {
        throw new AppError("Too many requests", 429, "RATE_LIMITED");
      }
      return;
    } catch (error) {
      if (error instanceof AppError) {
        throw error;
      }
      if (isProductionRuntime(config)) {
        throw new AppError("Rate limiter unavailable", 503, "RATE_LIMITER_UNAVAILABLE");
      }
    }
  }

  const entry = memoryBuckets.get(key);
  if (!entry || entry.resetAt <= now) {
    memoryBuckets.set(key, { count: 1, resetAt });
    return;
  }

  entry.count += 1;
  if (entry.count > limit) {
    throw new AppError("Too many requests", 429, "RATE_LIMITED");
  }
}
