/**
 * Redis caching layer for The Order
 * Implements caching for database queries, cache invalidation, and cache monitoring
 */

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

const logger = createLogger('cache');

export interface CacheConfig {
  url?: string;
  ttl?: number; // Default TTL in seconds
  keyPrefix?: string;
  enableCompression?: boolean;
}

export interface CacheStats {
  hits: number;
  misses: number;
  sets: number;
  deletes: number;
  errors: number;
}

/**
 * Redis Cache Client
 */
export class CacheClient {
  private client: RedisClientType | null = null;
  private config: Required<CacheConfig>;
  private stats: CacheStats = {
    hits: 0,
    misses: 0,
    sets: 0,
    deletes: 0,
    errors: 0,
  };

  constructor(config: CacheConfig = {}) {
    const env = getEnv();
    this.config = {
      url: config.url || env.REDIS_URL || 'redis://localhost:6379',
      ttl: config.ttl || 3600, // 1 hour default
      keyPrefix: config.keyPrefix || 'the-order:',
      enableCompression: config.enableCompression || false,
    };
  }

  /**
   * Initialize Redis client
   */
  async connect(): Promise<void> {
    if (this.client) {
      return;
    }

    try {
      this.client = createClient({
        url: this.config.url,
      }) as RedisClientType;

      this.client.on('error', (err) => {
        logger.error('Redis client error:', err);
        this.stats.errors++;
      });

      this.client.on('connect', () => {
        logger.info('Redis client connected');
      });

      this.client.on('disconnect', () => {
        logger.warn('Redis client disconnected');
      });

      await this.client.connect();
    } catch (error) {
      logger.error('Failed to connect to Redis:', error);
      throw error;
    }
  }

  /**
   * Disconnect Redis client
   */
  async disconnect(): Promise<void> {
    if (this.client) {
      await this.client.quit();
      this.client = null;
    }
  }

  /**
   * Get value from cache
   */
  async get<T>(key: string): Promise<T | null> {
    if (!this.client) {
      await this.connect();
    }

    if (!this.client) {
      this.stats.errors++;
      return null;
    }

    try {
      const fullKey = this.getFullKey(key);
      const value = await this.client.get(fullKey);

      if (value === null) {
        this.stats.misses++;
        return null;
      }

      this.stats.hits++;
      return this.deserialize<T>(value);
    } catch (error) {
      logger.error(`Cache get error for key ${key}:`, error);
      this.stats.errors++;
      this.stats.misses++;
      return null;
    }
  }

  /**
   * Set value in cache
   */
  async set(key: string, value: unknown, ttl?: number): Promise<void> {
    if (!this.client) {
      await this.connect();
    }

    if (!this.client) {
      this.stats.errors++;
      return;
    }

    try {
      const fullKey = this.getFullKey(key);
      const serialized = this.serialize(value);
      const expiresIn = ttl || this.config.ttl;

      await this.client.setEx(fullKey, expiresIn, serialized);
      this.stats.sets++;
    } catch (error) {
      logger.error(`Cache set error for key ${key}:`, error);
      this.stats.errors++;
    }
  }

  /**
   * Delete value from cache
   */
  async delete(key: string): Promise<void> {
    if (!this.client) {
      await this.connect();
    }

    if (!this.client) {
      this.stats.errors++;
      return;
    }

    try {
      const fullKey = this.getFullKey(key);
      await this.client.del(fullKey);
      this.stats.deletes++;
    } catch (error) {
      logger.error(`Cache delete error for key ${key}:`, error);
      this.stats.errors++;
    }
  }

  /**
   * Delete multiple keys by pattern
   */
  async invalidate(pattern: string): Promise<number> {
    if (!this.client) {
      await this.connect();
    }

    if (!this.client) {
      this.stats.errors++;
      return 0;
    }

    try {
      const fullPattern = this.getFullKey(pattern);
      const keys = await this.client.keys(fullPattern);

      if (keys.length === 0) {
        return 0;
      }

      const deleted = await this.client.del(keys);
      this.stats.deletes += deleted;
      return deleted;
    } catch (error) {
      logger.error(`Cache invalidate error for pattern ${pattern}:`, error);
      this.stats.errors++;
      return 0;
    }
  }

  /**
   * Check if key exists
   */
  async exists(key: string): Promise<boolean> {
    if (!this.client) {
      await this.connect();
    }

    if (!this.client) {
      this.stats.errors++;
      return false;
    }

    try {
      const fullKey = this.getFullKey(key);
      const result = await this.client.exists(fullKey);
      return result === 1;
    } catch (error) {
      logger.error(`Cache exists error for key ${key}:`, error);
      this.stats.errors++;
      return false;
    }
  }

  /**
   * Get cache statistics
   */
  getStats(): CacheStats {
    return { ...this.stats };
  }

  /**
   * Reset cache statistics
   */
  resetStats(): void {
    this.stats = {
      hits: 0,
      misses: 0,
      sets: 0,
      deletes: 0,
      errors: 0,
    };
  }

  /**
   * Get full key with prefix
   */
  private getFullKey(key: string): string {
    return `${this.config.keyPrefix}${key}`;
  }

  /**
   * Serialize value
   */
  private serialize(value: unknown): string {
    return JSON.stringify(value);
  }

  /**
   * Deserialize value
   */
  private deserialize<T>(value: string): T {
    return JSON.parse(value) as T;
  }
}

/**
 * Get default cache client
 */
let defaultCacheClient: CacheClient | null = null;

export function getCacheClient(config?: CacheConfig): CacheClient {
  if (!defaultCacheClient) {
    defaultCacheClient = new CacheClient(config);
  }
  return defaultCacheClient;
}

/**
 * Cache decorator for functions
 */
export function cached<T extends (...args: unknown[]) => Promise<unknown>>(
  fn: T,
  keyGenerator?: (...args: Parameters<T>) => string,
  ttl?: number
): T {
  const cache = getCacheClient();

  return (async (...args: Parameters<T>) => {
    const key = keyGenerator ? keyGenerator(...args) : `fn:${fn.name}:${JSON.stringify(args)}`;
    const cachedValue = await cache.get(key);

    if (cachedValue !== null) {
      return cachedValue as ReturnType<T>;
    }

    const result = await fn(...args);
    await cache.set(key, result, ttl);
    return result;
  }) as T;
}

