import type { Context, MiddlewareHandler } from 'hono';
import * as errors from '../custom-errors';
import { IS_DEV_BUILD } from '../build-env';

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

type RateLimiterOptions = {
  windowMs: number;
  max: number;
  keyGenerator?: (ctx: Context) => string;
  message?: string;
};

const stores = new Map<string, Map<string, RateLimitEntry>>();

function cleanupStore(store: Map<string, RateLimitEntry>) {
  const now = Date.now();
  for (const [key, entry] of store) {
    if (entry.resetAt <= now) {
      store.delete(key);
    }
  }
}

export function rateLimiter(options: RateLimiterOptions): MiddlewareHandler {
  const {
    windowMs,
    max,
    keyGenerator = (ctx) => ctx.req.header('x-forwarded-for') || 'unknown',
    message = 'Too many requests, please try again later',
  } = options;

  const storeId = `${windowMs}-${max}-${Math.random()}`;
  const store = new Map<string, RateLimitEntry>();
  stores.set(storeId, store);

  // Cleanup every minute
  setInterval(() => cleanupStore(store), 60_000);

  return async (ctx, next) => {
    if (IS_DEV_BUILD) return next();

    const key = keyGenerator(ctx);
    const now = Date.now();
    const entry = store.get(key);

    if (entry && entry.resetAt > now && entry.count >= max) {
      const retryAfter = Math.ceil((entry.resetAt - now) / 1000);
      throw errors.TOO_MANY_REQUESTS(message, retryAfter);
    }

    await next();

    // Only count failed attempts (4xx status codes)
    const status = ctx.res.status;
    if (status >= 400 && status < 500) {
      if (entry && entry.resetAt > now) {
        entry.count++;
      } else {
        store.set(key, { count: 1, resetAt: now + windowMs });
      }
    }
  };
}

// Pre-configured rate limiters for common use cases
export const signinRateLimiter = rateLimiter({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 5,
  keyGenerator: (ctx) => {
    const body = ctx.get('body');
    return body?.email || ctx.req.header('x-forwarded-for') || 'unknown';
  },
  message: 'Too many sign in attempts, please try again in 15 minutes',
});

export const forgotPasswordRateLimiter = rateLimiter({
  windowMs: 60 * 60 * 1000, // 1 hour
  max: 3,
  keyGenerator: (ctx) => {
    const body = ctx.get('body');
    return body?.email || ctx.req.header('x-forwarded-for') || 'unknown';
  },
  message: 'Too many password reset requests, please try again in 1 hour',
});

export const signupRateLimiter = rateLimiter({
  windowMs: 60 * 60 * 1000, // 1 hour
  max: 5,
  keyGenerator: (ctx) => ctx.req.header('x-forwarded-for') || 'unknown',
  message: 'Too many sign up attempts, please try again in 1 hour',
});

export const passkeyRateLimiter = rateLimiter({
  windowMs: 60 * 1000, // 1 minute
  max: 10,
  keyGenerator: (ctx) => {
    const email = ctx.req.param('email');
    return email || ctx.req.header('x-forwarded-for') || 'unknown';
  },
  message: 'Too many passkey requests, please try again in 1 minute',
});
