import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';

// Per-class limiter configuration. Distinct Redis prefix per class is
// REQUIRED — without it, a search-class hit would consume the auth-class
// budget for the same identifier. `slidingWindow` chosen across all
// classes to match Brief 4's existing algorithm; switching to
// `tokenBucket` per-class would be its own convoy.
const LIMITER_CONFIG = {
  auth:     { limit: 5,  window: '15 m', prefix: 'deckhearth:auth' },
  search:   { limit: 60, window: '1 m',  prefix: 'deckhearth:search' },
  upload:   { limit: 10, window: '1 h',  prefix: 'deckhearth:upload' },
  generate: { limit: 5,  window: '1 h',  prefix: 'deckhearth:generate' },
  import:   { limit: 5,  window: '1 h',  prefix: 'deckhearth:import' },
  scan:     { limit: 5,  window: '1 m',  prefix: 'deckhearth:scan' },
};

// Lazy singleton. Module-load init would throw in environments without
// Upstash env vars (local dev pre-onboarding, tests that transitively
// import the auth handlers, Vercel build-time bundling). Defer
// construction until the first request actually arrives.
let cached = null;

function init() {
  // Env-var names match Vercel's Upstash Marketplace integration, which
  // auto-provisions KV_REST_API_URL and KV_REST_API_TOKEN. See
  // https://upstash.com/docs/redis/howto/vercelintegration. Single-source-
  // of-truth — do NOT alias to UPSTASH_REDIS_REST_*.
  const url = process.env.KV_REST_API_URL;
  const token = process.env.KV_REST_API_TOKEN;

  if (url && token) {
    const redis = new Redis({ url, token });
    const instances = new Map();
    for (const [name, cfg] of Object.entries(LIMITER_CONFIG)) {
      instances.set(
        name,
        new Ratelimit({
          redis,
          limiter: Ratelimit.slidingWindow(cfg.limit, cfg.window),
          prefix: cfg.prefix,
        })
      );
    }
    return { mode: 'live', instances };
  }

  if (process.env.NODE_ENV === 'production') {
    // Fail-closed in production. A single failed login is a better outcome
    // than silently disabling brute-force protection on the live site.
    throw new Error(
      '[rate-limit] Upstash not configured. Set KV_REST_API_URL and KV_REST_API_TOKEN in the deployment environment (auto-provisioned by the Vercel Upstash Marketplace integration) before serving auth traffic.'
    );
  }

  console.warn(
    '[rate-limit] KV_REST_API_URL / KV_REST_API_TOKEN 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';
}

// THROWS on missing userId. Per-user limiters MUST sit AFTER the auth
// check in the handler body — silently falling back to IP here would
// convert a per-user limit into a per-IP limit, locking out other
// household members for one user's behavior. The throw surfaces the
// misordering immediately during development rather than at first
// production incident.
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 { success, remaining, reset } = await limiter.limit(identifier);
    return { allowed: success, remaining, reset };
  } catch (err) {
    // Fail-open on Upstash outage. A hard outage at the rate-limit backend
    // should not lock the entire user base out. Brute-force protection
    // lives behind defense-in-depth (Vercel firewall, etc.).
    console.error('[rate-limit]', err);
    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));
}
