import type Redis from 'ioredis';

/**
 * Sabit pencereli hız sınırlayıcı. REDIS_URL tanımlıysa Redis (INCR + PEXPIRE, tüm replikalar ortak),
 * değilse süreç içi bellek. Redis erişilemezse bellek yedeğine düşer (fail-open değil, yerel sınır uygulanır).
 */
const buckets = new Map<string, { n: number; reset: number }>();
function memoryLimit(key: string, max: number, windowMs: number) {
  const now = Date.now(); const b = buckets.get(key);
  if (!b || b.reset < now) { buckets.set(key, { n: 1, reset: now + windowMs }); if (buckets.size > 50_000) buckets.clear(); return true; }
  if (b.n >= max) return false;
  b.n++; return true;
}
let redis: Redis | null | undefined;
async function getRedis() {
  if (redis !== undefined) return redis;
  if (!process.env.REDIS_URL) return (redis = null);
  const { default: R } = await import('ioredis');
  redis = new R(process.env.REDIS_URL, { maxRetriesPerRequest: 1, enableOfflineQueue: false, lazyConnect: false });
  redis.on('error', (e) => log('warn', 'redis_error', { err: e.message }));
  return redis;
}
export async function rateLimit(key: string, max: number, windowMs: number): Promise<boolean> {
  const r = await getRedis();
  if (r && r.status === 'ready') {
    try {
      const k = `rl:${key}`;
      const res = await r.multi().incr(k).pexpire(k, windowMs, 'NX').exec();
      const n = Number(res?.[0]?.[1] ?? 0);
      return n <= max;
    } catch (e) { log('warn', 'redis_ratelimit_fallback', { err: (e as Error).message }); }
  }
  return memoryLimit(key, max, windowMs);
}
export const __test = { memoryLimit, reset: () => { buckets.clear(); redis = undefined; } };
export function clientIp(h: Headers) { return (h.get('x-forwarded-for') || '').split(',')[0].trim() || h.get('x-real-ip') || 'local'; }
export function appUrl(req: Request) { return process.env.APP_URL || new URL(req.url).origin; }
export function log(level: 'info' | 'warn' | 'error', msg: string, extra: Record<string, unknown> = {}) {
  console[level === 'info' ? 'log' : level](JSON.stringify({ t: new Date().toISOString(), level, svc: 'web', msg, ...extra }));
}
