/**
 * Redis Service
 * Specialized service layer for RBAC caching operations.
 * Implements Look-Aside caching pattern for role permissions.
 */

import redisClient from '../config/redis.js';

// Default TTL: 1 hour (can be overridden by env)
const CACHE_TTL = parseInt(process.env.REDIS_TTL || '3600', 10);
const IS_ENABLED = process.env.ENABLE_REDIS_CACHE === 'true';

class RedisService {
  /**
   * Check if Redis caching is currently enabled and client is ready
   * @returns {boolean}
   */
  isEnabled() {
    return IS_ENABLED && redisClient && redisClient.status === 'ready';
  }

  /**
   * Get all permissions for a specific role
   * @param {string} role - The user role
   * @returns {Promise<string[]|null>} Array of "resource:action" strings or null on miss/error
   */
  async getRolePermissions(role) {
    if (!this.isEnabled()) return null;

    const key = `rbac:permissions:${role}`;

    try {
      const members = await redisClient.smembers(key);

      // If empty, genuine cache miss (Redis returns [] for missing keys)
      if (members.length === 0) {
        return null;
      }

      // Filter out sentinel value used to prevent cache penetration
      const filtered = members.filter(p => p !== '__EMPTY__');

      // If only sentinel existed, return empty array
      return filtered.length === 0 ? [] : filtered;
    } catch (error) {
      console.error(`[Redis] Error fetching permissions for role ${role}:`, error.message);
      // Fail open: return null to fallback to DB
      return null;
    }
  }

  /**
   * Cache permissions for a role
   * @param {string} role - The user role
   * @param {string[]} permissions - Array of "resource:action" strings
   * @returns {Promise<void>}
   */
  async setRolePermissions(role, permissions) {
    if (!this.isEnabled()) return;

    const key = `rbac:permissions:${role}`;

    try {
      // Use multi() for atomic transaction to prevent race conditions
      const multi = redisClient.multi();

      multi.del(key);

      // Cache empty sets with sentinel value to prevent cache penetration
      if (!permissions || permissions.length === 0) {
        multi.sadd(key, '__EMPTY__');
      } else {
        multi.sadd(key, ...permissions);
      }

      // Set Expiration
      multi.expire(key, CACHE_TTL);

      const results = await multi.exec();

      // Validate transaction success
      if (!results || results.some(([err]) => err)) {
        throw new Error('Redis transaction failed');
      }

      const permCount = permissions?.length || 0;
      console.debug(`[RBAC] Cached ${permCount} permissions for role: ${role}`);
    } catch (error) {
      console.error(`[Redis] Error setting permissions for role ${role}:`, error.message);
    }
  }

  /**
   * Invalidate cache for a specific role
   * Called when permissions are modified via API
   * @param {string} role
   * @returns {Promise<void>}
   */
  async invalidateRole(role) {
    if (!this.isEnabled()) return;

    const key = `rbac:permissions:${role}`;

    try {
      await redisClient.del(key);
      console.log(`[RBAC] Invalidated cache for role: ${role}`);
    } catch (error) {
      console.error(`[Redis] Error invalidating role ${role}:`, error.message);
    }
  }

  /**
   * Simple health check for monitoring
   * @returns {Promise<object>}
   */
  async healthCheck() {
    if (!IS_ENABLED) return { enabled: false, status: 'disabled' };
    if (!redisClient) return { enabled: true, status: 'client_not_initialized' };

    try {
      const start = Date.now();
      await redisClient.ping();
      return {
        enabled: true,
        status: redisClient.status,
        latency: `${Date.now() - start}ms`
      };
    } catch (error) {
      return { enabled: true, status: 'error', error: error.message };
    }
  }
}

export default new RedisService();
