/**
 * In-Memory Cache Middleware
 * Caches API responses to reduce database load
 */
const logger = require("../config/logger");

class CacheManager {
  constructor(defaultTTL = 300000, maxSize = 2000) {
    // 5 minutes default, max 2000 entries (optimized for performance)
    this.cache = new Map();
    this.defaultTTL = defaultTTL;
    this.maxSize = maxSize;
    this.stats = { hits: 0, misses: 0, evictions: 0 };
    // Use Map for O(1) LRU tracking instead of array indexOf/splice
    this.lruHead = null; // Most recently used
    this.lruTail = null; // Least recently used
    this.lruNodes = new Map(); // key -> {prev, next, key}
  }

  set(key, value, ttl = this.defaultTTL) {
    const expiresAt = Date.now() + ttl;

    // If key exists, remove from LRU list first
    if (this.cache.has(key)) {
      this._removeLRUNode(key);
    } else if (this.cache.size >= this.maxSize) {
      // Evict least recently used
      if (this.lruTail) {
        const evictKey = this.lruTail.key;
        this.cache.delete(evictKey);
        this._removeLRUNode(evictKey);
        this.stats.evictions++;
        logger.debug(`Cache LRU eviction: ${evictKey}`);
      }
    }

    this.cache.set(key, { value, expiresAt });
    this._addLRUNode(key); // Add to head (most recent)
    logger.debug(`Cache set: ${key} (TTL: ${ttl}ms)`);
  }

  get(key) {
    const cached = this.cache.get(key);

    if (!cached) {
      this.stats.misses++;
      logger.debug(`Cache miss: ${key}`);
      return null;
    }

    const now = Date.now();
    if (now > cached.expiresAt) {
      this.cache.delete(key);
      this._removeLRUNode(key);
      this.stats.misses++;
      logger.debug(`Cache expired: ${key}`);
      return null;
    }

    // Move to head (most recently used) - O(1)
    this._removeLRUNode(key);
    this._addLRUNode(key);

    this.stats.hits++;
    logger.debug(`Cache hit: ${key}`);
    return cached.value;
  }

  delete(key) {
    const deleted = this.cache.delete(key);
    if (deleted) logger.debug(`Cache invalidated: ${key}`);
    return deleted;
  }

  deletePattern(pattern) {
    let count = 0;
    for (const key of this.cache.keys()) {
      if (key.includes(pattern)) {
        this.cache.delete(key);
        count++;
      }
    }
    if (count > 0)
      logger.debug(`Cache pattern invalidated: ${pattern} (${count} keys)`);
    return count;
  }

  clear() {
    const size = this.cache.size;
    this.cache.clear();
    this.lruNodes.clear();
    this.lruHead = null;
    this.lruTail = null;
    logger.info(`Cache cleared (${size} keys)`);
  }

  size() {
    return this.cache.size;
  }

  // Get cache statistics
  getStats() {
    const hitRate =
      this.stats.hits + this.stats.misses > 0
        ? (
            (this.stats.hits / (this.stats.hits + this.stats.misses)) *
            100
          ).toFixed(2)
        : 0;
    return {
      ...this.stats,
      hitRate: `${hitRate}%`,
      size: this.cache.size,
      maxSize: this.maxSize,
    };
  }

  // Reset statistics
  resetStats() {
    this.stats = { hits: 0, misses: 0, evictions: 0 };
  }

  // O(1) LRU operations using doubly-linked list pattern
  _addLRUNode(key) {
    const node = { key, prev: null, next: this.lruHead };

    if (this.lruHead) {
      this.lruHead.prev = node;
    }
    this.lruHead = node;

    if (!this.lruTail) {
      this.lruTail = node;
    }

    this.lruNodes.set(key, node);
  }

  _removeLRUNode(key) {
    const node = this.lruNodes.get(key);
    if (!node) return;

    if (node.prev) {
      node.prev.next = node.next;
    } else {
      this.lruHead = node.next;
    }

    if (node.next) {
      node.next.prev = node.prev;
    } else {
      this.lruTail = node.prev;
    }

    this.lruNodes.delete(key);
  }

  // Clean up expired entries
  cleanup() {
    const now = Date.now();
    let cleaned = 0;
    for (const [key, { expiresAt }] of this.cache.entries()) {
      if (now > expiresAt) {
        this.cache.delete(key);
        cleaned++;
      }
    }
    if (cleaned > 0)
      logger.debug(`Cache cleanup: removed ${cleaned} expired entries`);
    return cleaned;
  }
}

// Global cache instance
const cache = new CacheManager();

// Cleanup interval reference (for graceful shutdown)
let cleanupInterval = null;

// Start automatic cleanup (optional, call from server startup)
const startCleanup = () => {
  if (!cleanupInterval) {
    cleanupInterval = setInterval(() => cache.cleanup(), 300000); // 5 minutes
    logger.debug("Cache cleanup scheduler started");
  }
};

// Stop automatic cleanup (for graceful shutdown)
const stopCleanup = () => {
  if (cleanupInterval) {
    clearInterval(cleanupInterval);
    cleanupInterval = null;
    logger.debug("Cache cleanup scheduler stopped");
  }
};

/**
 * Cache middleware factory
 * @param {number} ttl - Time to live in milliseconds
 * @param {function} keyGenerator - Function to generate cache key from req
 */
const cacheMiddleware = (ttl = 300000, keyGenerator = null) => {
  return (req, res, next) => {
    // Skip cache for authenticated requests
    if (req.session && req.session.userId) {
      return next();
    }

    const key = keyGenerator
      ? keyGenerator(req)
      : `${req.method}:${req.originalUrl}`;

    const cachedResponse = cache.get(key);
    if (cachedResponse) {
      res.setHeader("X-Cache", "HIT");
      return res.json(cachedResponse);
    }

    // Store original json method
    const originalJson = res.json.bind(res);

    // Override json method to cache response
    res.json = function (data) {
      cache.set(key, data, ttl);
      res.setHeader("X-Cache", "MISS");
      return originalJson(data);
    };

    next();
  };
};

module.exports = {
  cache,
  cacheMiddleware,
  startCleanup,
  stopCleanup,
};
