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 { permissionCacheService } from '../../modules/auth/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')) {
        return next();
      }

      // Build permission key for cache lookup
      const permissionKey = `${resource}:${action}`;

      // Try cache first for fast permission check
      const cachedHasPermission = await permissionCacheService.hasPermission(userId, permissionKey);

      // If cache hit and user has permission, allow access
      if (cachedHasPermission) {
        logger.debug('Permission granted (cache hit)', {
          userId,
          resource,
          action,
        });
        return next();
      }

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

      if (!hasPermission) {
        logger.warn('Access denied - insufficient permissions', {
          userId,
          tenantId,
          resource,
          action,
        });
        throw new ForbiddenError(`No tiene permisos para ${action} en ${resource}`);
      }

      // Permission granted - update cache with user's effective permissions
      // This ensures future checks are faster
      try {
        const effectivePermissions = await permissionsService.getEffectivePermissions(tenantId, userId);
        const permissionKeys = effectivePermissions.map(p => `${p.resource}:${p.action}`);
        await permissionCacheService.setUserPermissions(userId, permissionKeys);
      } catch (cacheError) {
        // Don't fail the request if cache update fails
        logger.warn('Failed to update permission cache', {
          userId,
          error: (cacheError as Error).message,
        });
      }

      logger.debug('Permission granted (database check)', {
        userId,
        resource,
        action,
      });

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

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);
  }
}
