/**
 * Per-product rate limiting for extraction service.
 *
 * Tracks request rates per productId to prevent abuse and ensure
 * fair resource allocation across products.
 *
 * Limits:
 *   - Default: 100 requests/min per product
 *   - Configurable via env vars per tier
 */

// ── Configuration ───────────────────────────────────────────────

/** Configuration for product rate limiting */
export interface ProductRateLimitConfig {
  productId: string;
  windowMs: number;
  maxRequests: number;
}

// Default limits (can be overridden via env)
const DEFAULT_PRODUCT_RPM = parseInt(process.env.PRODUCT_RATE_LIMIT_RPM || '100', 10);
const WINDOW_MS = 60_000; // 1 minute

// ── In-memory rate limit store ──────────────────────────────────

interface RateLimitEntry {
  count: number;
  windowStart: number;
  resetAt: number;
}

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

function getStoreKey(productId: string): string {
  const windowIndex = Math.floor(Date.now() / WINDOW_MS);
  return `${productId}:${windowIndex}`;
}

// ── Rate limiting functions ─────────────────────────────────────

export interface RateLimitResult {
  allowed: boolean;
  limit: number;
  remaining: number;
  resetAt: number;
  retryAfter?: number;
}

/**
 * Check if request is within product rate limit.
 */
export function checkProductRateLimit(productId: string): RateLimitResult {
  const key = getStoreKey(productId);
  const now = Date.now();
  const resetAt = (Math.floor(now / WINDOW_MS) + 1) * WINDOW_MS;

  const entry = rateLimitStore.get(key);

  if (!entry || now >= entry.resetAt) {
    // New window
    rateLimitStore.set(key, {
      count: 1,
      windowStart: now,
      resetAt,
    });
    return {
      allowed: true,
      limit: DEFAULT_PRODUCT_RPM,
      remaining: DEFAULT_PRODUCT_RPM - 1,
      resetAt,
    };
  }

  // Existing window
  if (entry.count >= DEFAULT_PRODUCT_RPM) {
    return {
      allowed: false,
      limit: DEFAULT_PRODUCT_RPM,
      remaining: 0,
      resetAt: entry.resetAt,
      retryAfter: Math.ceil((entry.resetAt - now) / 1000),
    };
  }

  entry.count++;
  return {
    allowed: true,
    limit: DEFAULT_PRODUCT_RPM,
    remaining: DEFAULT_PRODUCT_RPM - entry.count,
    resetAt: entry.resetAt,
  };
}

/**
 * Get current rate limit status for a product (without incrementing).
 */
export function getProductRateLimitStatus(productId: string): RateLimitResult {
  const key = getStoreKey(productId);
  const now = Date.now();
  const resetAt = (Math.floor(now / WINDOW_MS) + 1) * WINDOW_MS;

  const entry = rateLimitStore.get(key);

  if (!entry || now >= entry.resetAt) {
    return {
      allowed: true,
      limit: DEFAULT_PRODUCT_RPM,
      remaining: DEFAULT_PRODUCT_RPM,
      resetAt,
    };
  }

  const remaining = Math.max(0, DEFAULT_PRODUCT_RPM - entry.count);
  return {
    allowed: remaining > 0,
    limit: DEFAULT_PRODUCT_RPM,
    remaining,
    resetAt: entry.resetAt,
    retryAfter: remaining === 0 ? Math.ceil((entry.resetAt - now) / 1000) : undefined,
  };
}

/**
 * Get rate limit summary across all products.
 */
export function getRateLimitSummary(): {
  products: Array<{
    productId: string;
    currentWindow: number;
    requests: number;
    limit: number;
    resetAt: number;
  }>;
  totalProducts: number;
} {
  const now = Date.now();
  const products = new Map<string, { count: number; resetAt: number }>();

  for (const [key, entry] of rateLimitStore.entries()) {
    if (now < entry.resetAt) {
      const productId = key.split(':')[0];
      const existing = products.get(productId);
      if (existing) {
        existing.count += entry.count;
      } else {
        products.set(productId, { count: entry.count, resetAt: entry.resetAt });
      }
    }
  }

  return {
    products: [...products.entries()].map(([productId, data]) => ({
      productId,
      currentWindow: Math.floor(now / WINDOW_MS),
      requests: data.count,
      limit: DEFAULT_PRODUCT_RPM,
      resetAt: data.resetAt,
    })),
    totalProducts: products.size,
  };
}

/**
 * Reset rate limit for a product (admin operation).
 */
export function resetProductRateLimit(productId: string): void {
  const now = Date.now();
  const currentWindow = Math.floor(now / WINDOW_MS);

  // Clear current and next window entries
  for (let i = -1; i <= 1; i++) {
    rateLimitStore.delete(`${productId}:${currentWindow + i}`);
  }
}

/**
 * Cleanup old entries from rate limit store.
 * Called periodically to prevent memory growth.
 */
export function cleanupRateLimitStore(): number {
  const now = Date.now();
  let cleaned = 0;

  for (const [key, entry] of rateLimitStore.entries()) {
    if (now >= entry.resetAt) {
      rateLimitStore.delete(key);
      cleaned++;
    }
  }

  return cleaned;
}

// Cleanup interval handle (exported for testing/shutdown)
let cleanupInterval: ReturnType<typeof setInterval> | null = null;

/**
 * Start automatic cleanup of expired rate limit entries.
 * Called during server startup.
 */
export function startRateLimitCleanup(): void {
  if (cleanupInterval) {
    clearInterval(cleanupInterval);
  }
  cleanupInterval = setInterval(
    () => {
      const cleaned = cleanupRateLimitStore();
      if (cleaned > 0 && process.env.NODE_ENV === 'development') {
        // eslint-disable-next-line no-console
        console.log(`[product-rate-limit] Cleaned up ${cleaned} expired entries`);
      }
    },
    5 * 60 * 1000
  );
}

/**
 * Stop automatic cleanup (for graceful shutdown/testing).
 */
export function stopRateLimitCleanup(): void {
  if (cleanupInterval) {
    clearInterval(cleanupInterval);
    cleanupInterval = null;
  }
}

// Auto-start in production/development (not test)
if (process.env.NODE_ENV !== 'test') {
  startRateLimitCleanup();
}
