import { logger } from '@goodgo/logger';
import NodeCache from 'node-cache';

import { getRedisClient } from '../../config/redis.config';

/**
 * EN: Multi-layer cache implementation (L1: Memory, L2: Redis)
 * VI: Triển khai cache đa tầng (L1: Bộ nhớ, L2: Redis)
 */
export class MultiLayerCache {
  private l1Cache: NodeCache; // In-memory cache
  private redis: ReturnType<typeof getRedisClient> | null = null; // Disabled for testing

  constructor() {
    // L1: In-memory cache (10MB default, 60s default TTL)
    this.l1Cache = new NodeCache({
      stdTTL: 60, // Default TTL 60 seconds
      maxKeys: 10000, // Max 10k keys
      useClones: false, // Better performance
    });

    // EN: Temporarily disable L2 Redis cache to test performance
    // VI: Tạm thời tắt L2 Redis cache để test hiệu suất
    // TODO: Re-enable after fixing Redis EPIPE errors
    // this.redis = getRedisClient();
  }

  /**
   * EN: Get value from cache (L1 -> L2)
   * VI: Lấy giá trị từ cache (L1 -> L2)
   */
  async get<T>(key: string): Promise<T | null> {
    try {
      // L1: Check in-memory cache first (< 1ms)
      const l1Value = this.l1Cache.get<T>(key);
      if (l1Value !== undefined) {
        return l1Value;
      }

      // L2: Check Redis cache (< 5ms) - only if Redis is enabled
      if (this.redis) {
        const l2Value = await this.redis.get(key);
        if (l2Value) {
          const parsed = JSON.parse(l2Value) as T;
          // Warm up L1 cache
          this.l1Cache.set(key, parsed, 60); // Cache 1 minute in L1
          return parsed;
        }
      }

      return null;
    } catch (error) {
      logger.error('Multi-layer cache get error', { key, error });
      return null;
    }
  }

  /**
   * EN: Set value in cache (both L1 and L2)
   * VI: Lưu giá trị vào cache (cả L1 và L2)
   */
  async set(key: string, value: any, ttlSeconds?: number): Promise<void> {
    try {
      // L1: Set in-memory cache
      const l1Ttl = ttlSeconds ? Math.min(ttlSeconds, 300) : 60; // Max 5 min in L1
      this.l1Cache.set(key, value, l1Ttl);

      // EN: Temporarily disable L2 Redis cache to test performance
      // VI: Tạm thời tắt L2 Redis cache để test hiệu suất
      // TODO: Re-enable after fixing Redis EPIPE errors
      /*
      // L2: Set Redis cache
      if (this.redis) {
        const stringValue = JSON.stringify(value);
        if (ttlSeconds) {
          await this.redis.setex(key, ttlSeconds, stringValue);
        } else {
          await this.redis.set(key, stringValue);
        }
      }
      */
    } catch (error) {
      logger.error('Multi-layer cache set error', { key, error });
      // Continue even if Redis fails (L1 still works)
    }
  }

  /**
   * EN: Get from cache or fetch from source if missing
   * VI: Lấy từ cache hoặc lấy từ nguồn nếu không có
   */
  async getOrSet<T>(
    key: string,
    fetchFn: () => Promise<T>,
    ttlSeconds: number = 300
  ): Promise<T> {
    const cached = await this.get<T>(key);
    if (cached !== null) {
      return cached;
    }

    const data = await fetchFn();
    await this.set(key, data, ttlSeconds);
    return data;
  }

  /**
   * EN: Delete from cache (both L1 and L2)
   * VI: Xóa khỏi cache (cả L1 và L2)
   */
  async del(key: string): Promise<void> {
    try {
      // L1: Delete from memory
      this.l1Cache.del(key);

      // L2: Delete from Redis
      if (this.redis) {
        await this.redis.del(key);
      }
    } catch (error) {
      logger.error('Multi-layer cache del error', { key, error });
    }
  }

  /**
   * EN: Delete multiple keys from cache
   * VI: Xóa nhiều keys khỏi cache
   */
  async delMany(keys: string[]): Promise<void> {
    try {
      // L1: Delete from memory
      keys.forEach(key => this.l1Cache.del(key));

      // L2: Delete from Redis
      if (this.redis && keys.length > 0) {
        await this.redis.del(...keys);
      }
    } catch (error) {
      logger.error('Multi-layer cache delMany error', { keys, error });
    }
  }

  /**
   * EN: Invalidate cache by pattern (for cache invalidation)
   * VI: Làm mất hiệu lực cache theo pattern
   */
  async invalidatePattern(pattern: string): Promise<void> {
    try {
      // L1: Clear all (pattern matching is expensive, so clear all)
      // In production, you might want to implement pattern matching
      this.l1Cache.flushAll();

      // L2: Scan and delete matching keys in Redis
      if (this.redis) {
        const stream = this.redis.scanStream({
          match: pattern,
          count: 100,
        });

        const keys: string[] = [];
        stream.on('data', (resultKeys: string[]) => {
          keys.push(...resultKeys);
        });

        const redis = this.redis; // Capture for closure
        stream.on('end', async () => {
          if (keys.length > 0) {
            await redis.del(...keys);
          }
        });
      }
    } catch (error) {
      logger.error('Multi-layer cache invalidatePattern error', { pattern, error });
    }
  }

  /**
   * EN: Get cache statistics
   * VI: Lấy thống kê cache
   */
  getStats() {
    const l1Stats = this.l1Cache.getStats();
    return {
      l1: {
        keys: l1Stats.keys,
        hits: l1Stats.hits,
        misses: l1Stats.misses,
        hitRate: l1Stats.hits / (l1Stats.hits + l1Stats.misses) || 0,
      },
    };
  }
}

export const multiLayerCache = new MultiLayerCache();
