/**
 * Optimized Cache Client for SAPPS Backend
 * High-performance caching with Redis support and fallback to memory
 * Supports both Redis (for cluster) and in-memory (fallback)
 */

const config = require('./config.json');

// Try to use Redis if available, otherwise use optimized in-memory store
let redisClient = null;
let useRedis = false;

// Redis configuration from config
const redisConfig = config.redisConfig || {
    host: '127.0.0.1',
    port: 6379,
    enable: false // Disabled by default for backward compatibility
};

// Try to load ioredis (more performant than redis package)
let Redis;
try {
    Redis = require('ioredis');
} catch (e) {
    console.log('[Cache] ioredis not available, using memory cache');
}

/**
 * In-memory cache with TTL support (optimized version)
 */
class MemoryCache {
    constructor() {
        this.store = new Map();
        this.stats = {
            hits: 0,
            misses: 0,
            sets: 0,
            deletes: 0
        };
        
        // Periodic cleanup of expired entries (every 60 seconds)
        setInterval(() => this.cleanup(), 60000);
    }

    cleanup() {
        const now = Date.now();
        let cleaned = 0;
        
        for (const [key, entry] of this.store.entries()) {
            if (entry.expiresAt && entry.expiresAt <= now) {
                this.store.delete(key);
                cleaned++;
            }
        }
        
        if (cleaned > 0) {
            console.log(`[Cache] Cleaned ${cleaned} expired entries`);
        }
    }

    async setEx(key, ttlSeconds, value) {
        const expiresAt = Date.now() + (ttlSeconds * 1000);
        this.store.set(key, { value, expiresAt });
        this.stats.sets++;
        return 'OK';
    }

    async get(key) {
        const entry = this.store.get(key);
        if (!entry) {
            this.stats.misses++;
            return null;
        }
        
        if (entry.expiresAt && entry.expiresAt <= Date.now()) {
            this.store.delete(key);
            this.stats.misses++;
            return null;
        }
        
        this.stats.hits++;
        return entry.value;
    }

    async del(key) {
        const result = this.store.delete(key) ? 1 : 0;
        this.stats.deletes++;
        return result;
    }

    async delByPrefix(prefix) {
        let count = 0;
        for (const key of this.store.keys()) {
            if (key.startsWith(prefix)) {
                this.store.delete(key);
                count++;
            }
        }
        return count;
    }

    async getStats() {
        return {
            ...this.stats,
            size: this.store.size,
            type: 'memory'
        };
    }

    async flush() {
        this.store.clear();
        this.stats = { hits: 0, misses: 0, sets: 0, deletes: 0 };
        return 'OK';
    }
}

/**
 * Redis client wrapper with connection pooling
 */
class RedisCache {
    constructor(options) {
        this.client = null;
        this.options = {
            host: options.host || '127.0.0.1',
            port: options.port || 6379,
            password: options.password,
            db: options.db || 0,
            retryDelayOnFailover: 100,
            maxRetriesPerRequest: 3,
            enableReadyCheck: true,
            connectTimeout: 10000,
            // Performance optimizations
            family: 4,
            keepAlive: 30000,
            lazyConnect: true
        };
        
        this.stats = {
            hits: 0,
            misses: 0,
            sets: 0,
            deletes: 0
        };
        
        this.connected = false;
    }

    async connect() {
        if (this.client) return;
        
        try {
            this.client = new Redis(this.options);
            
            this.client.on('connect', () => {
                console.log('[Cache] Redis connected successfully');
                this.connected = true;
            });
            
            this.client.on('error', (err) => {
                console.error('[Cache] Redis error:', err.message);
                this.connected = false;
            });
            
            this.client.on('close', () => {
                this.connected = false;
            });
            
            await this.client.connect();
        } catch (error) {
            console.error('[Cache] Failed to connect to Redis:', error.message);
            throw error;
        }
    }

    async setEx(key, ttlSeconds, value) {
        if (!this.connected) {
            throw new Error('Redis not connected');
        }
        
        const serialized = JSON.stringify(value);
        await this.client.setex(key, ttlSeconds, serialized);
        this.stats.sets++;
        return 'OK';
    }

    async get(key) {
        if (!this.connected) {
            throw new Error('Redis not connected');
        }
        
        const value = await this.client.get(key);
        
        if (value === null) {
            this.stats.misses++;
            return null;
        }
        
        try {
            this.stats.hits++;
            return JSON.parse(value);
        } catch (e) {
            // Return raw value if not JSON
            return value;
        }
    }

    async del(key) {
        if (!this.connected) {
            throw new Error('Redis not connected');
        }
        
        const result = await this.client.del(key);
        this.stats.deletes++;
        return result;
    }

    async delByPrefix(prefix) {
        if (!this.connected) {
            throw new Error('Redis not connected');
        }
        
        // Use SCAN for safe prefix deletion
        let cursor = '0';
        let count = 0;
        
        do {
            const [newCursor, keys] = await this.client.scan(cursor, 'MATCH', prefix + '*', 'COUNT', 100);
            cursor = newCursor;
            
            if (keys.length > 0) {
                await this.client.del(...keys);
                count += keys.length;
            }
        } while (cursor !== '0');
        
        return count;
    }

    async getStats() {
        return {
            ...this.stats,
            connected: this.connected,
            type: 'redis'
        };
    }

    async flush() {
        if (!this.connected) {
            throw new Error('Redis not connected');
        }
        
        await this.client.flushdb();
        this.stats = { hits: 0, misses: 0, sets: 0, deletes: 0 };
        return 'OK';
    }
    
    async quit() {
        if (this.client) {
            await this.client.quit();
            this.client = null;
            this.connected = false;
        }
    }
}

// Initialize cache client
async function initCache() {
    // Check if Redis is enabled in config
    if (redisConfig.enable && Redis) {
        try {
            redisClient = new RedisCache(redisConfig);
            await redisClient.connect();
            useRedis = true;
            console.log('[Cache] Using Redis cache (cluster-ready)');
        } catch (error) {
            console.warn('[Cache] Redis connection failed, falling back to memory cache');
            redisClient = new MemoryCache();
            useRedis = false;
        }
    } else {
        // Use memory cache (backward compatible)
        redisClient = new MemoryCache();
        useRedis = false;
        
        if (redisConfig.enable) {
            console.log('[Cache] Redis not configured, using memory cache');
        } else {
            console.log('[Cache] Using optimized in-memory cache');
        }
    }
    
    return redisClient;
}

// Initialize on module load (non-blocking)
initCache().catch(err => {
    console.error('[Cache] Cache initialization error:', err.message);
    // Fallback to memory cache
    redisClient = new MemoryCache();
});

// Export cache interface (same as before for backward compatibility)
module.exports = {
    // Core methods (same interface as before)
    setEx: async (key, ttlSeconds, value) => {
        if (!redisClient) await initCache();
        return redisClient.setEx(key, ttlSeconds, value);
    },
    
    get: async (key) => {
        if (!redisClient) await initCache();
        return redisClient.get(key);
    },
    
    del: async (key) => {
        if (!redisClient) await initCache();
        return redisClient.del(key);
    },
    
    delByPrefix: async (prefix) => {
        if (!redisClient) await initCache();
        return redisClient.delByPrefix(prefix);
    },
    
    // New utility methods
    getStats: async () => {
        if (!redisClient) await initCache();
        return redisClient.getStats();
    },
    
    flush: async () => {
        if (!redisClient) await initCache();
        return redisClient.flush();
    },
    
    // Check cache type
    isUsingRedis: () => useRedis,
    
    // Re-initialize (for testing or config changes)
    reinitialize: initCache
};
