/**
 * Redis Cache Helper
 * 
 * Provides caching layer for site/page lookups to achieve <5ms routing.
 * Gracefully degrades if Redis is unavailable.
 */
import Redis from 'ioredis';

let redis: Redis | null = null;

export function getRedisClient(): Redis {
    if (!redis) {
        const redisUrl = process.env.REDIS_URL || 'redis://localhost:6379';
        redis = new Redis(redisUrl, {
            maxRetriesPerRequest: 3,
            retryStrategy(times) {
                const delay = Math.min(times * 50, 2000);
                return delay;
            },
            lazyConnect: true,
            enableReadyCheck: true,
            // Timeout to prevent hanging
            connectTimeout: 1000,
            commandTimeout: 2000
        });

        // Handle errors gracefully
        redis.on('error', (err) => {
            console.warn('Redis error:', err.message);
        });
    }
    return redis;
}

/**
 * Cache wrapper for site/page lookups
 * Falls back to direct DB query if Redis unavailable
 * 
 * @param key - Cache key
 * @param fetchFn - Function to fetch data if cache miss
 * @param ttl - Time to live in seconds (default: 60s)
 */
export async function getCached<T>(
    key: string,
    fetchFn: () => Promise<T>,
    ttl: number = 60
): Promise<T> {
    try {
        const redis = getRedisClient();

        // Ensure connection
        if (redis.status !== 'ready') {
            await redis.connect().catch(() => null);
        }

        // Try cache first
        const cached = await redis.get(key);
        if (cached) {
            return JSON.parse(cached);
        }
    } catch (err) {
        // Redis unavailable - fall through to DB
        if (process.env.NODE_ENV === 'development') {
            console.warn('Redis cache miss, using DB:', key);
        }
    }

    // Cache miss or Redis unavailable - fetch from DB
    const data = await fetchFn();

    // Store in cache (fire and forget - don't block on cache writes)
    if (data !== null && data !== undefined) {
        try {
            const redis = getRedisClient();
            redis.setex(key, ttl, JSON.stringify(data)).catch(() => {
                // Ignore cache write failures
            });
        } catch (err) {
            // Cache write failure is non-fatal
        }
    }

    return data;
}

/**
 * Invalidate all cache entries for a site
 * Use this when pages are updated/deleted
 * 
 * @param domain - Site domain to invalidate
 */
export async function invalidateSite(domain: string) {
    try {
        const redis = getRedisClient();
        const pattern = `site:${domain}:*`;

        // Find all keys matching pattern
        const keys = await redis.keys(pattern);

        if (keys.length > 0) {
            await redis.del(...keys);
            console.log(`Cache invalidated: ${keys.length} keys for ${domain}`);
        }
    } catch (err) {
        console.warn('Cache invalidation failed:', err);
        // Non-fatal - cache will expire naturally
    }
}

/**
 * Invalidate a specific page cache
 * 
 * @param domain - Site domain
 * @param route - Page route (e.g., "/about")
 */
export async function invalidatePage(domain: string, route: string) {
    try {
        const redis = getRedisClient();
        const key = `site:${domain}:page:${route}`;
        await redis.del(key);
    } catch (err) {
        console.warn('Page cache invalidation failed:', err);
    }
}

/**
 * Get cache statistics
 */
export async function getCacheStats() {
    try {
        const redis = getRedisClient();
        const info = await redis.info('stats');
        const lines = info.split('\r\n');
        const stats: Record<string, string> = {};

        lines.forEach(line => {
            const [key, value] = line.split(':');
            if (key && value) {
                stats[key] = value;
            }
        });

        return {
            hits: parseInt(stats.keyspace_hits || '0'),
            misses: parseInt(stats.keyspace_misses || '0'),
            hitRate: stats.keyspace_hits && stats.keyspace_misses
                ? (parseInt(stats.keyspace_hits) / (parseInt(stats.keyspace_hits) + parseInt(stats.keyspace_misses)) * 100).toFixed(2) + '%'
                : 'N/A'
        };
    } catch (err) {
        return { hits: 0, misses: 0, hitRate: 'Redis unavailable' };
    }
}
