/**
 * Redis caching layer for The Order
 * Implements caching for database queries, cache invalidation, and cache monitoring
 */
import { createClient } from 'redis';
import { getEnv, createLogger } from '@the-order/shared';
const logger = createLogger('cache');
/**
 * Redis Cache Client
 */
export class CacheClient {
    client = null;
    config;
    stats = {
        hits: 0,
        misses: 0,
        sets: 0,
        deletes: 0,
        errors: 0,
    };
    constructor(config = {}) {
        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() {
        if (this.client) {
            return;
        }
        try {
            this.client = createClient({
                url: this.config.url,
            });
            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() {
        if (this.client) {
            await this.client.quit();
            this.client = null;
        }
    }
    /**
     * Get value from cache
     */
    async get(key) {
        if (!this.client) {
            await this.connect();
        }
        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(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, value, ttl) {
        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) {
        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) {
        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) {
        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() {
        return { ...this.stats };
    }
    /**
     * Reset cache statistics
     */
    resetStats() {
        this.stats = {
            hits: 0,
            misses: 0,
            sets: 0,
            deletes: 0,
            errors: 0,
        };
    }
    /**
     * Get full key with prefix
     */
    getFullKey(key) {
        return `${this.config.keyPrefix}${key}`;
    }
    /**
     * Serialize value
     */
    serialize(value) {
        return JSON.stringify(value);
    }
    /**
     * Deserialize value
     */
    deserialize(value) {
        return JSON.parse(value);
    }
}
/**
 * Get default cache client
 */
let defaultCacheClient = null;
export function getCacheClient(config) {
    if (!defaultCacheClient) {
        defaultCacheClient = new CacheClient(config);
    }
    return defaultCacheClient;
}
/**
 * Cache decorator for functions
 */
export function cached(fn, keyGenerator, ttl) {
    const cache = getCacheClient();
    return (async (...args) => {
        const key = keyGenerator ? keyGenerator(...args) : `fn:${fn.name}:${JSON.stringify(args)}`;
        const cachedValue = await cache.get(key);
        if (cachedValue !== null) {
            return cachedValue;
        }
        const result = await fn(...args);
        await cache.set(key, result, ttl);
        return result;
    });
}
//# sourceMappingURL=redis.js.map