import Redis from 'ioredis';
import { RateLimiterRedis } from 'rate-limiter-flexible';

const LIMITER_CONFIG = {
  auth: { limit: 5, durationSec: 15 * 60, prefix: 'deckhearth:auth' },
  search: { limit: 60, durationSec: 60, prefix: 'deckhearth:search' },
  upload: { limit: 10, durationSec: 60 * 60, prefix: 'deckhearth:upload' },
  generate: { limit: 5, durationSec: 60 * 60, prefix: 'deckhearth:generate' },
  import: { limit: 5, durationSec: 60 * 60, prefix: 'deckhearth:import' },
  // One camera verify may escalate L0→L2; vision path is the expensive step.
  scan: { limit: 15, durationSec: 60, prefix: 'deckhearth:scan' },
};

let cached = null;

function init() {
  const url = process.env.REDIS_URL;
  if (url) {
    const storeClient = new Redis(url, {
      maxRetriesPerRequest: 1,
      enableOfflineQueue: false,
    });
    const instances = new Map();
    for (const [name, cfg] of Object.entries(LIMITER_CONFIG)) {
      instances.set(
        name,
        new RateLimiterRedis({
          storeClient,
          keyPrefix: cfg.prefix,
          points: cfg.limit,
          duration: cfg.durationSec,
        })
      );
    }
    return { mode: 'live', instances };
  }

  if (process.env.NODE_ENV === 'production') {
    throw new Error(
      '[rate-limit] REDIS_URL is not configured. Set REDIS_URL to the homelab Redis URL (CT 102) before serving traffic.'
    );
  }

  console.warn('[rate-limit] REDIS_URL not set — rate limiting disabled (dev/test only)');
  return { mode: 'noop' };
}

function extractIpIdentifier(req) {
  const xff = req.headers?.['x-forwarded-for'];
  const firstHop = Array.isArray(xff) ? xff[0] : xff?.split(',')[0]?.trim();
  return firstHop || req.socket?.remoteAddress || 'anonymous';
}

function extractUserIdentifier(userId) {
  if (
    userId === null ||
    userId === undefined ||
    userId === '' ||
    (typeof userId === 'number' && Number.isNaN(userId))
  ) {
    throw new Error(
      '[rate-limit] extractUserIdentifier called without an authenticated userId. Place the rate-limit gate AFTER the auth check, never before.'
    );
  }
  return `user:${userId}`;
}

async function check(className, identifier) {
  if (!cached) {
    cached = init();
  }

  if (cached.mode === 'noop') {
    return { allowed: true, remaining: Infinity, reset: 0 };
  }

  const limiter = cached.instances.get(className);
  if (!limiter) {
    throw new Error(`[rate-limit] Unknown limiter class: ${className}`);
  }

  try {
    const result = await limiter.consume(identifier);
    return {
      allowed: true,
      remaining: result.remainingPoints,
      reset: Date.now() + result.msBeforeNext,
    };
  } catch (rej) {
    if (rej && typeof rej.msBeforeNext === 'number') {
      return {
        allowed: false,
        remaining: 0,
        reset: Date.now() + rej.msBeforeNext,
      };
    }
    console.error('[rate-limit]', rej);
    return { allowed: true, remaining: Infinity, reset: 0 };
  }
}

export async function checkAuthRateLimit(req) {
  return check('auth', extractIpIdentifier(req));
}

export async function checkSearchRateLimit(req) {
  return check('search', extractIpIdentifier(req));
}

export async function checkUploadRateLimit(req, userId) {
  return check('upload', extractUserIdentifier(userId));
}

export async function checkGenerateRateLimit(req, userId) {
  return check('generate', extractUserIdentifier(userId));
}

export async function checkImportRateLimit(req, userId) {
  return check('import', extractUserIdentifier(userId));
}

export async function checkScanRateLimit(req, userId) {
  return check('scan', extractUserIdentifier(userId));
}
