import { Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
import { config } from '../../config/index.js';
import { AuthenticatedRequest, JwtPayload, UnauthorizedError, ForbiddenError } from '../types/index.js';
import { logger } from '../utils/logger.js';
import { permissionsService } from '../../modules/roles/permissions.service.js';
import {
  checkCachedPermission,
  getCachedPermissions,
  setCachedPermissions,
} from '../services/permission-cache.service.js';

// Re-export AuthenticatedRequest for convenience
export { AuthenticatedRequest } from '../types/index.js';

export function authenticate(
  req: AuthenticatedRequest,
  _res: Response,
  next: NextFunction
): void {
  try {
    const authHeader = req.headers.authorization;

    if (!authHeader || !authHeader.startsWith('Bearer ')) {
      throw new UnauthorizedError('Token de acceso requerido');
    }

    const token = authHeader.substring(7);

    try {
      const payload = jwt.verify(token, config.jwt.secret) as JwtPayload;
      req.user = payload;
      req.tenantId = payload.tenantId;
      next();
    } catch (error) {
      if (error instanceof jwt.TokenExpiredError) {
        throw new UnauthorizedError('Token expirado');
      }
      throw new UnauthorizedError('Token inválido');
    }
  } catch (error) {
    next(error);
  }
}

export function requireRoles(...roles: string[]) {
  return (req: AuthenticatedRequest, _res: Response, next: NextFunction): void => {
    try {
      if (!req.user) {
        throw new UnauthorizedError('Usuario no autenticado');
      }

      // Superusers bypass role checks
      if (req.user.roles.includes('super_admin')) {
        return next();
      }

      const hasRole = roles.some(role => req.user!.roles.includes(role));
      if (!hasRole) {
        logger.warn('Access denied - insufficient roles', {
          userId: req.user.userId,
          requiredRoles: roles,
          userRoles: req.user.roles,
        });
        throw new ForbiddenError('No tiene permisos para esta acción');
      }

      next();
    } catch (error) {
      next(error);
    }
  };
}

export function requirePermission(resource: string, action: string) {
  return async (req: AuthenticatedRequest, _res: Response, next: NextFunction): Promise<void> => {
    try {
      if (!req.user) {
        throw new UnauthorizedError('Usuario no autenticado');
      }

      const { userId, tenantId, roles } = req.user;

      // Superusers bypass permission checks
      if (roles.includes('super_admin')) {
        logger.debug('Permission bypassed (super_admin)', { userId, resource, action });
        return next();
      }

      // Check cache first
      const cachedResult = await checkCachedPermission(tenantId, userId, resource, action);

      if (cachedResult !== null) {
        if (cachedResult) {
          return next();
        }
        logger.warn('Access denied (cached)', {
          userId,
          tenantId,
          resource,
          action,
          roles,
        });
        throw new ForbiddenError(`No tiene permiso para ${action} en ${resource}`);
      }

      // Cache miss - check database
      const hasPermission = await permissionsService.hasPermission(tenantId, userId, resource, action);

      if (hasPermission) {
        // Warm up cache with all user permissions for future requests
        const effectivePermissions = await permissionsService.getEffectivePermissions(tenantId, userId);
        await setCachedPermissions(tenantId, userId, effectivePermissions);
        return next();
      }

      // Permission denied - log and throw
      logger.warn('Access denied', {
        userId,
        tenantId,
        resource,
        action,
        roles,
      });
      throw new ForbiddenError(`No tiene permiso para ${action} en ${resource}`);
    } catch (error) {
      next(error);
    }
  };
}

/**
 * Preload user permissions into cache (call after login)
 */
export async function preloadUserPermissions(tenantId: string, userId: string): Promise<void> {
  try {
    const cached = await getCachedPermissions(tenantId, userId);
    if (cached) {
      return; // Already cached
    }

    const permissions = await permissionsService.getEffectivePermissions(tenantId, userId);
    await setCachedPermissions(tenantId, userId, permissions);
    logger.debug('User permissions preloaded', { tenantId, userId, count: permissions.length });
  } catch (error) {
    logger.error('Error preloading user permissions', {
      error: (error as Error).message,
      tenantId,
      userId,
    });
  }
}

export function optionalAuth(
  req: AuthenticatedRequest,
  _res: Response,
  next: NextFunction
): void {
  try {
    const authHeader = req.headers.authorization;

    if (authHeader && authHeader.startsWith('Bearer ')) {
      const token = authHeader.substring(7);
      try {
        const payload = jwt.verify(token, config.jwt.secret) as JwtPayload;
        req.user = payload;
        req.tenantId = payload.tenantId;
      } catch {
        // Token invalid, but that's okay for optional auth
      }
    }

    next();
  } catch (error) {
    next(error);
  }
}
