/**
 * Global rate limiting implementation
 * Supports per-user, per-IP, and global rate limits
 */

import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
import { createLogger } from './logger';

const logger = createLogger('rate-limiting');

export interface RateLimitOptions {
  maxRequests?: number;
  windowMs?: number;
  keyGenerator?: (request: FastifyRequest) => string;
  skipOnError?: boolean;
}

interface RateLimitStore {
  [key: string]: {
    count: number;
    resetTime: number;
  };
}

const store: RateLimitStore = {};

/**
 * Create a rate limit key from request
 */
function generateKey(request: FastifyRequest, options: RateLimitOptions): string {
  if (options.keyGenerator) {
    return options.keyGenerator(request);
  }

  // Default: use IP address
  const ip = request.ip || request.socket.remoteAddress || 'unknown';
  return `rate-limit:${ip}`;
}

/**
 * Check if request should be rate limited
 */
function checkRateLimit(
  key: string,
  maxRequests: number,
  windowMs: number
): { allowed: boolean; remaining: number; resetTime: number } {
  const now = Date.now();
  const entry = store[key];

  // Clean expired entries periodically
  if (Math.random() < 0.01) {
    // 1% chance to clean up
    Object.keys(store).forEach((k) => {
      if (store[k]!.resetTime < now) {
        delete store[k];
      }
    });
  }

  if (!entry || entry.resetTime < now) {
    // Create new window
    store[key] = {
      count: 1,
      resetTime: now + windowMs,
    };
    return {
      allowed: true,
      remaining: maxRequests - 1,
      resetTime: now + windowMs,
    };
  }

  if (entry.count >= maxRequests) {
    return {
      allowed: false,
      remaining: 0,
      resetTime: entry.resetTime,
    };
  }

  entry.count++;
  return {
    allowed: true,
    remaining: maxRequests - entry.count,
    resetTime: entry.resetTime,
  };
}

/**
 * Rate limiting middleware
 */
export async function rateLimit(
  request: FastifyRequest,
  reply: FastifyReply,
  options: RateLimitOptions = {}
): Promise<void> {
  const {
    maxRequests = 100,
    windowMs = 60000, // 1 minute default
    skipOnError = true,
  } = options;

  try {
    const key = generateKey(request, options);
    const result = checkRateLimit(key, maxRequests, windowMs);

    // Set rate limit headers
    reply.header('X-RateLimit-Limit', maxRequests.toString());
    reply.header('X-RateLimit-Remaining', result.remaining.toString());
    reply.header('X-RateLimit-Reset', new Date(result.resetTime).toISOString());

    if (!result.allowed) {
      logger.warn({ key, ip: request.ip }, 'Rate limit exceeded');
      reply.code(429).send({
        error: 'Too Many Requests',
        message: 'Rate limit exceeded. Please try again later.',
        retryAfter: Math.ceil((result.resetTime - Date.now()) / 1000),
      });
      return;
    }
  } catch (err) {
    if (skipOnError) {
      logger.warn({ err }, 'Rate limit check failed, allowing request');
      return;
    }
    throw err;
  }
}

/**
 * Register global rate limiting plugin
 */
export async function registerGlobalRateLimit(
  server: FastifyInstance,
  options: RateLimitOptions = {}
): Promise<void> {
  server.addHook('onRequest', async (request, reply) => {
    await rateLimit(request, reply, options);
  });
}

/**
 * Per-user rate limiting
 */
export function createUserRateLimit(maxRequests: number, windowMs: number) {
  return async (request: FastifyRequest, reply: FastifyReply): Promise<void> => {
    const userId = (request as any).user?.id || (request as any).user?.sub;
    if (!userId) {
      return; // No user, skip rate limiting
    }

    await rateLimit(request, reply, {
      maxRequests,
      windowMs,
      keyGenerator: () => `rate-limit:user:${userId}`,
    });
  };
}

/**
 * Per-IP rate limiting
 */
export function createIPRateLimit(maxRequests: number, windowMs: number) {
  return async (request: FastifyRequest, reply: FastifyReply): Promise<void> => {
    await rateLimit(request, reply, {
      maxRequests,
      windowMs,
      keyGenerator: (req) => `rate-limit:ip:${req.ip || req.socket.remoteAddress || 'unknown'}`,
    });
  };
}

