/**
 * Per-tier daily scan limit middleware.
 *
 * Reads `req.user.tier` (set by requireAuth) and counts scans in a
 * rolling 24h window. Anonymous callers fall through to a 'free'
 * bucket keyed by IP so the open demo can't be DOS'd via shell.
 *
 * The middleware is separate from the IP rate limiter (SEC-1, Session
 * 7d) on /api/analyze: that one caps RAW request rate (10/min) per IP
 * regardless of who's authenticated. This one caps PAID-OPERATION
 * counts per user per day per tier.
 *
 * Counts live in-memory (Map<userOrIpKey, Array<timestamp>>) — same
 * trade-off as rateLimit.js. Survives a Coolify redeploy by resetting
 * to zero, which is the user-friendly fallback.
 */

const { getScanLimit } = require('../config/tiers');

const WINDOW_MS = 24 * 60 * 60 * 1000;
const MAX_TRACKED = 50_000;

const hits = new Map();

function clientKey(req) {
  if (req.user?.id) return `u:${req.user.id}`;
  return `ip:${req.ip || req.socket?.remoteAddress || 'unknown'}`;
}

function evictIfFull() {
  if (hits.size <= MAX_TRACKED) return;
  const oldest = hits.keys().next().value;
  if (oldest !== undefined) hits.delete(oldest);
}

function pruneOlderThan(arr, cutoff) {
  let w = 0;
  for (let i = 0; i < arr.length; i += 1) {
    if (arr[i] > cutoff) {
      arr[w] = arr[i];
      w += 1;
    }
  }
  arr.length = w;
  return w;
}

function scanLimit() {
  return function scanLimitMiddleware(req, res, next) {
    const tier = req.user?.tier || 'free';
    const limit = getScanLimit(tier);
    // Infinity → never block; skip the bookkeeping entirely so the
    // hot Desk path doesn't allocate Map entries it'll never read.
    if (limit === Infinity) return next();

    const key = clientKey(req);
    const now = Date.now();
    const cutoff = now - WINDOW_MS;

    let ts = hits.get(key);
    if (!ts) {
      ts = [];
      hits.set(key, ts);
      evictIfFull();
    }

    const used = pruneOlderThan(ts, cutoff);
    if (used >= limit) {
      const oldest = ts[0];
      const retryAfterSec = Math.max(1, Math.ceil((oldest + WINDOW_MS - now) / 1000));
      res.set('Retry-After', String(retryAfterSec));
      res.set('X-Scans-Used', String(used));
      res.set('X-Scans-Limit', String(limit));
      return res.status(429).json({
        error: 'Daily scan limit reached. Upgrade for more scans.',
        scans_used: used,
        scans_limit: limit,
        retry_after_seconds: retryAfterSec,
        tier,
      });
    }

    ts.push(now);
    res.set('X-Scans-Used', String(used + 1));
    res.set('X-Scans-Limit', String(limit));
    return next();
  };
}

// Test helper — drop all tracked counts. Not exported in module.exports
// to keep prod surface clean; reachable via the __internals bag.
function resetForTests() {
  hits.clear();
}

/**
 * Session 60 (night2/F, 5.4) — read-only usage for the Settings meter.
 * Reports the SAME rolling-24h window the limiter enforces, from the same
 * store — the meter can never disagree with the gate. Infinity limit →
 * { unlimited: true }.
 */
function scanUsage(req) {
  const tier = req.user?.tier || 'free';
  const limit = getScanLimit(tier);
  if (limit === Infinity) return { tier, unlimited: true, used: null, limit: null };
  const ts = hits.get(clientKey(req));
  const used = ts ? pruneOlderThan(ts, Date.now() - WINDOW_MS) : 0;
  return { tier, unlimited: false, used, limit, remaining: Math.max(0, limit - used) };
}

module.exports = {
  scanLimit,
  scanUsage,
  __internals: { hits, clientKey, resetForTests, WINDOW_MS, MAX_TRACKED },
};
