import jwt from 'jsonwebtoken';
import RedisService from '../services/RedisService.js';

// Validate JWT_SECRET at module load
if (!process.env.JWT_SECRET) {
  throw new Error('FATAL: JWT_SECRET environment variable is not defined');
}

export const authenticateToken = (req, res, next) => {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1];

  if (!token) {
    return res.status(401).json({
      status: 'error',
      code: 'UNAUTHORIZED',
      message: 'No token provided',
      data: null
    });
  }

  jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
    if (err) {
      return res.status(403).json({
        status: 'error',
        code: 'FORBIDDEN',
        message: 'Invalid or expired token',
        data: null
      });
    }
    req.user = user;
    next();
  });
};

// Optional authentication - does not fail if token is missing, but validates if present
// Supports both Authorization header and query parameter token (for EventSource compatibility)
export const optionalAuth = (req, res, next) => {
  const authHeader = req.headers['authorization'];
  const headerToken = authHeader && authHeader.split(' ')[1];

  // Check query parameter token (for EventSource which can't send headers)
  const queryToken = req.query.token;

  // Use header token if available, otherwise use query token
  const token = headerToken || queryToken;

  // If no token, continue without authentication (req.user will be undefined)
  if (!token) {
    return next();
  }

  // If token is present, validate it
  jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
    if (err) {
      // Invalid token - continue anyway but don't set req.user
      console.log('[Auth] Invalid token provided but continuing without auth');
      return next();
    }
    req.user = user;
    next();
  });
};

export const authorizeRole = (allowedRoles) => {
  return (req, res, next) => {
    if (!req.user) {
      return res.status(401).json({
        status: 'error',
        code: 'UNAUTHORIZED',
        message: 'User not authenticated',
        data: null
      });
    }

    if (!allowedRoles.includes(req.user.role)) {
      return res.status(403).json({
        status: 'error',
        code: 'FORBIDDEN',
        message: 'Insufficient permissions',
        data: null
      });
    }

    next();
  };
};

/**
 * Granular permission-based authorization middleware with Redis caching
 * Implements Look-Aside pattern: Cache -> DB -> Cache population
 */
export const requirePermission = (resource, action) => {
  return async (req, res, next) => {
    try {
      if (!req.user || !req.user.role) {
        return res.status(401).json({
          status: 'error',
          code: 'UNAUTHORIZED',
          message: 'No user context found',
          data: null
        });
      }

      const userRole = req.user.role;

      // Admin override - admins have implicit access to everything
      if (userRole === 'admin') {
        return next();
      }

      const permissionKey = `${resource}:${action}`;

      // ======== REDIS LAYER ========
      // Attempt cache read (non-blocking)
      const cachedPermissions = await RedisService.getRolePermissions(userRole);

      if (cachedPermissions !== null) {
        // Cache HIT
        const hasPermission = cachedPermissions.includes(permissionKey);
        console.debug(`[RBAC] Cache HIT for ${userRole} - ${permissionKey}: ${hasPermission}`);

        if (!hasPermission) {
          return res.status(403).json({
            status: 'error',
            code: 'FORBIDDEN',
            message: 'Missing required permission',
            data: { resource, action }
          });
        }

        return next();
      }

      // Cache MISS - fallback to database
      console.debug(`[RBAC] Cache MISS for ${userRole} - querying DB`);

      // Dynamically import TeamPermissions model to avoid circular dependency
      const { default: TeamPermissions } = await import('../models/TeamPermissions.js');

      // Fetch ALL permissions for this role (for cache population)
      const allPermissions = await TeamPermissions.findAll({
        where: { role: userRole },
        attributes: ['resource', 'action']
      });

      // Transform to "resource:action" format
      const permissionStrings = allPermissions.map(p => `${p.resource}:${p.action}`);

      // Fire-and-forget cache population (don't await to avoid blocking request)
      RedisService.setRolePermissions(userRole, permissionStrings).catch(err => {
        console.error('[RBAC] Failed to populate cache:', err.message);
      });

      // Check if requested permission exists
      const hasPermission = permissionStrings.includes(permissionKey);

      if (!hasPermission) {
        return res.status(403).json({
          status: 'error',
          code: 'FORBIDDEN',
          message: 'Missing required permission',
          data: { resource, action }
        });
      }

      next();
    } catch (error) {
      console.error('[RBAC] Permission check error:', error);
      res.status(500).json({
        status: 'error',
        code: 'SERVER_ERROR',
        message: 'Internal server error verifying permissions',
        data: null
      });
    }
  };
};
