/**
 * Redis caching implementation
 * Provides cache strategy, invalidation, and warming
 */

import { createLogger } from '@the-order/shared';
import type { RedisClientType } from 'redis';

const logger = createLogger('redis-cache');

export interface CacheOptions {
  ttl?: number; // Time to live in seconds
  prefix?: string; // Key prefix
  serialize?: (value: unknown) => string;
  deserialize?: (value: string) => unknown;
}

export class RedisCache {
  private client: RedisClientType;
  private defaultTTL: number;
  private prefix: string;

  constructor(client: RedisClientType, options: CacheOptions = {}) {
    this.client = client;
    this.defaultTTL = options.ttl || 3600; // 1 hour default
    this.prefix = options.prefix || 'the-order:cache:';
  }

  /**
   * Get value from cache
   */
  async get<T>(key: string): Promise<T | null> {
    try {
      const fullKey = `${this.prefix}${key}`;
      const value = await this.client.get(fullKey);
      if (!value) {
        return null;
      }
      return JSON.parse(value) as T;
    } catch (err) {
      logger.warn({ err, key }, 'Cache get failed');
      return null;
    }
  }

  /**
   * Set value in cache
   */
  async set(key: string, value: unknown, ttl?: number): Promise<void> {
    try {
      const fullKey = `${this.prefix}${key}`;
      const serialized = JSON.stringify(value);
      const cacheTTL = ttl || this.defaultTTL;
      await this.client.setEx(fullKey, cacheTTL, serialized);
    } catch (err) {
      logger.warn({ err, key }, 'Cache set failed');
    }
  }

  /**
   * Delete value from cache
   */
  async delete(key: string): Promise<void> {
    try {
      const fullKey = `${this.prefix}${key}`;
      await this.client.del(fullKey);
    } catch (err) {
      logger.warn({ err, key }, 'Cache delete failed');
    }
  }

  /**
   * Delete multiple keys matching pattern
   */
  async invalidatePattern(pattern: string): Promise<void> {
    try {
      const fullPattern = `${this.prefix}${pattern}`;
      const keys = await this.client.keys(fullPattern);
      if (keys.length > 0) {
        await this.client.del(keys);
      }
    } catch (err) {
      logger.warn({ err, pattern }, 'Cache invalidation failed');
    }
  }

  /**
   * Clear all cache entries with prefix
   */
  async clear(): Promise<void> {
    try {
      await this.invalidatePattern('*');
    } catch (err) {
      logger.warn({ err }, 'Cache clear failed');
    }
  }

  /**
   * Warm cache with data
   */
  async warm(entries: Array<{ key: string; value: unknown; ttl?: number }>): Promise<void> {
    try {
      await Promise.all(
        entries.map((entry) => this.set(entry.key, entry.value, entry.ttl))
      );
      logger.info({ count: entries.length }, 'Cache warmed');
    } catch (err) {
      logger.warn({ err }, 'Cache warming failed');
    }
  }

  /**
   * Get or set pattern (cache-aside)
   */
  async getOrSet<T>(
    key: string,
    fetcher: () => Promise<T>,
    ttl?: number
  ): Promise<T> {
    const cached = await this.get<T>(key);
    if (cached !== null) {
      return cached;
    }

    const value = await fetcher();
    await this.set(key, value, ttl);
    return value;
  }
}

