import { Request, Response, NextFunction } from "express";
import { isProKey } from "../services/keys.js";
import logger from "../services/logger.js";

interface RateLimitEntry {
  count: number;
  resetTime: number;
}

// Per-key rate limits (requests per minute)
const FREE_RATE_LIMIT = 10;
const PRO_RATE_LIMIT = 30;
const RATE_WINDOW_MS = 60_000; // 1 minute

// Concurrency limits
const MAX_CONCURRENT_PDFS = 3;
const MAX_QUEUE_SIZE = 10;

// Per-key queue fairness (Audit #15)
const MAX_QUEUED_PER_KEY = 3;

const rateLimitStore = new Map<string, RateLimitEntry>();
let activePdfCount = 0;
const pdfQueue: Array<{ resolve: () => void; reject: (error: Error) => void; apiKey: string }> = [];

function cleanupExpiredEntries(): void {
  const now = Date.now();
  for (const [key, entry] of rateLimitStore.entries()) {
    if (now >= entry.resetTime) {
      rateLimitStore.delete(key);
    }
  }
}

function getRateLimit(apiKey: string): number {
  return isProKey(apiKey) ? PRO_RATE_LIMIT : FREE_RATE_LIMIT;
}

function checkRateLimit(apiKey: string): boolean {
  cleanupExpiredEntries();
  
  const now = Date.now();
  const limit = getRateLimit(apiKey);
  const entry = rateLimitStore.get(apiKey);
  
  if (!entry || now >= entry.resetTime) {
    rateLimitStore.set(apiKey, {
      count: 1,
      resetTime: now + RATE_WINDOW_MS
    });
    return true;
  }
  
  if (entry.count >= limit) {
    return false;
  }
  
  entry.count++;
  return true;
}

function getQueuedCountForKey(apiKey: string): number {
  return pdfQueue.filter(w => w.apiKey === apiKey).length;
}

async function acquireConcurrencySlot(apiKey: string): Promise<void> {
  if (activePdfCount < MAX_CONCURRENT_PDFS) {
    activePdfCount++;
    return;
  }
  
  if (pdfQueue.length >= MAX_QUEUE_SIZE) {
    throw new Error("QUEUE_FULL");
  }
  
  // Audit #15: Per-key fairness — reject if this key already has too many queued
  if (getQueuedCountForKey(apiKey) >= MAX_QUEUED_PER_KEY) {
    logger.warn({ apiKey: apiKey.slice(0, 8) + "..." }, "Per-key queue limit reached");
    throw new Error("QUEUE_FULL");
  }
  
  return new Promise((resolve, reject) => {
    pdfQueue.push({ resolve, reject, apiKey });
  });
}

function releaseConcurrencySlot(): void {
  activePdfCount--;
  
  const waiter = pdfQueue.shift();
  if (waiter) {
    activePdfCount++;
    waiter.resolve();
  }
}

export function pdfRateLimitMiddleware(req: Request & { apiKeyInfo?: any }, res: Response, next: NextFunction): void {
  const keyInfo = req.apiKeyInfo;
  const apiKey = keyInfo?.key || "unknown";
  
  // Check rate limit first
  if (!checkRateLimit(apiKey)) {
    const limit = getRateLimit(apiKey);
    const tier = isProKey(apiKey) ? "pro" : "free";
    res.status(429).json({ error: `Rate limit exceeded: ${limit} PDFs/min allowed for ${tier} tier. Retry after 60s.` });
    return;
  }
  
  // Add concurrency control to the request (pass apiKey for fairness)
  (req as any).acquirePdfSlot = () => acquireConcurrencySlot(apiKey);
  (req as any).releasePdfSlot = releaseConcurrencySlot;
  
  next();
}

export function getConcurrencyStats() {
  return {
    activePdfCount,
    queueSize: pdfQueue.length,
    maxConcurrent: MAX_CONCURRENT_PDFS,
    maxQueue: MAX_QUEUE_SIZE
  };
}

// Proactive cleanup every 60s
setInterval(cleanupExpiredEntries, 60_000).unref();
