/**
 * Rate limiter — Upstash Redis (production) + in-memory fallback (dev only).
 *
 * ⚠️  PERINGATAN PRODUCTION:
 * In-memory fallback TIDAK AMAN untuk production karena:
 *   1. Vercel/serverless: setiap invocation bisa spawn instance baru → Map selalu kosong
 *   2. Multi-instance: state tidak di-share antar server → rate limit bypassable
 *
 * ✅ Untuk production, WAJIB set env vars Upstash Redis:
 *   UPSTASH_REDIS_REST_URL=https://xxxxxxxx.upstash.io
 *   UPSTASH_REDIS_REST_TOKEN=AXxxxxxxxxxx
 *
 *   Daftar gratis di: https://upstash.com (free tier: 10.000 req/hari)
 *
 * In-memory fallback HANYA aman untuk:
 *   - Development lokal (npm run dev)
 *   - CI/CD testing
 *   - Single-instance self-hosted (non-serverless)
 *
 * Batas: 10 request/menit per user_id
 */

const WINDOW_MS = 60 * 1000; // 1 minute
const MAX_REQUESTS = 10;

// ── In-memory fallback ────────────────────────────────────────────────────────
const rateStore = new Map<string, { count: number; resetAt: number }>();

function checkInMemory(identifier: string): {
  allowed: boolean;
  remaining: number;
  resetAt: number;
} {
  const now = Date.now();
  const record = rateStore.get(identifier);

  if (!record || now > record.resetAt) {
    rateStore.set(identifier, { count: 1, resetAt: now + WINDOW_MS });
    return { allowed: true, remaining: MAX_REQUESTS - 1, resetAt: now + WINDOW_MS };
  }
  if (record.count >= MAX_REQUESTS) {
    return { allowed: false, remaining: 0, resetAt: record.resetAt };
  }
  record.count++;
  return { allowed: true, remaining: MAX_REQUESTS - record.count, resetAt: record.resetAt };
}

// Cleanup in-memory store — hanya relevan di long-running server (dev lokal / self-hosted).
// Di Vercel/serverless, instance di-recycle sebelum interval ini sempat fire.
// Tidak masalah: Map akan di-GC bersama instance saat recycle.
if (typeof setInterval !== "undefined" && process.env.NODE_ENV !== "production") {
  setInterval(() => {
    const now = Date.now();
    Array.from(rateStore.entries()).forEach(([key, value]) => {
      if (now > value.resetAt) rateStore.delete(key);
    });
  }, WINDOW_MS);
}

// ── Upstash Redis (production) ────────────────────────────────────────────────
// Fix: Gunakan Lua EVAL untuk atomic INCR+EXPIRE — mencegah race condition
// di mana dua request concurrent sama-sama melihat count=0 dan tidak set TTL.
async function checkUpstashRedis(identifier: string): Promise<{
  allowed: boolean;
  remaining: number;
  resetAt: number;
}> {
  const url = process.env.UPSTASH_REDIS_REST_URL;
  const token = process.env.UPSTASH_REDIS_REST_TOKEN;
  if (!url || !token) throw new Error("Upstash not configured");

  const key = `rate:${identifier}`;
  const windowSec = Math.floor(WINDOW_MS / 1000);

  // Lua script: atomically INCR and set EXPIRE only on first increment.
  // Returns [count, ttl] in a single roundtrip — no race condition.
  const luaScript = `
    local count = redis.call('INCR', KEYS[1])
    if count == 1 then
      redis.call('EXPIRE', KEYS[1], ARGV[1])
    end
    local ttl = redis.call('TTL', KEYS[1])
    return {count, ttl}
  `.trim();

  const res = await fetch(`${url}/eval`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${token}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify([luaScript, 1, key, String(windowSec)]),
  });

  if (!res.ok) throw new Error(`Upstash EVAL failed: ${res.status}`);

  const { result } = await res.json() as { result: [number, number] };
  const count = result[0];
  const ttl   = result[1];

  const now = Date.now();
  const resetAt = now + (ttl > 0 ? ttl * 1000 : WINDOW_MS);
  const remaining = Math.max(0, MAX_REQUESTS - count);
  return { allowed: count <= MAX_REQUESTS, remaining, resetAt };
}

// ── Public API (async) ────────────────────────────────────────────────────────
export async function checkRateLimit(identifier: string): Promise<{
  allowed: boolean;
  remaining: number;
  resetAt: number;
}> {
  if (process.env.UPSTASH_REDIS_REST_URL && process.env.UPSTASH_REDIS_REST_TOKEN) {
    try {
      return await checkUpstashRedis(identifier);
    } catch (e) {
      console.warn("[rate-limit] Upstash failed, falling back to in-memory:", e);
    }
  }
  return checkInMemory(identifier);
}
