import { Request, Response, NextFunction } from 'express';
import { AuthService } from '../../domain/services/auth.service';
import { PgUserRepository } from '../../infrastructure/repositories/postgresql/user.repository';
import logger from '../../config/logger';
import { AuthError } from '../../utils/errors/auth.error';

// Initialize repositories and services
const userRepository = new PgUserRepository();
const authService = new AuthService(userRepository);

// Extend Express Request to include user and token payload
declare global {
  namespace Express {
    interface Request {
      user?: any;
      tokenPayload?: any;
    }
  }
}

/**
 * Authentication middleware
 * Verifies JWT tokens and attaches user/client info to the request
 */
export const authenticate = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
  try {
    // Get the Authorization header
    const authHeader = req.headers.authorization;

    if (!authHeader) {
      throw AuthError.unauthorized('No authorization token provided');
    }

    // Extract the token (Bearer token)
    const parts = authHeader.split(' ');

    if (parts.length !== 2 || parts[0] !== 'Bearer') {
      throw AuthError.unauthorized('Invalid authorization format. Use: Bearer [token]');
    }

    const token = parts[1];

    // Validate the token
    const tokenPayload = authService.validateToken(token);

    // Attach the token payload to the request
    req.tokenPayload = tokenPayload;

    // For user tokens, attach the user info
    if (tokenPayload.type === 'user') {
      req.user = {
        id: tokenPayload.sub,
        email: tokenPayload.email,
        roles: tokenPayload.roles || [],
        permissions: tokenPayload.permissions || []
      };
    }

    // For client tokens, attach client info
    if (tokenPayload.type === 'client') {
      req.user = {
        clientId: tokenPayload.client_id,
        scopes: tokenPayload.scopes || []
      };
    }

    next();
  } catch (error) {
    if (error instanceof AuthError) {
      return res.status(error.statusCode).json({
        status: 'error',
        message: error.message,
        code: error.code
      });
    }

    logger.error('Authentication error:', error);
    res.status(401).json({
      status: 'error',
      message: 'Authentication failed',
      code: 'UNAUTHORIZED'
    });
  }
};

/**
 * Role-based access control middleware
 * Checks if the authenticated user has any of the required roles
 * @param roles Array of required roles
 */
export const hasRole = (roles: string[]) => {
  return (req: Request, res: Response, next: NextFunction): void => {
    try {
      // Check if user is authenticated
      if (!req.user) {
        throw AuthError.unauthorized('User is not authenticated');
      }

      // Only applies to user tokens, not client tokens
      if (req.tokenPayload?.type !== 'user') {
        throw AuthError.forbidden('This endpoint requires user authentication');
      }

      // Check if user has any of the required roles
      const userRoles = req.user.roles || [];
      const hasRequiredRole = roles.some(role => userRoles.includes(role));

      if (!hasRequiredRole) {
        throw AuthError.forbidden(`Access denied. Required roles: ${roles.join(', ')}`);
      }

      next();
    } catch (error) {
      if (error instanceof AuthError) {
        return res.status(error.statusCode).json({
          status: 'error',
          message: error.message,
          code: error.code
        });
      }

      logger.error('Role verification error:', error);
      res.status(403).json({
        status: 'error',
        message: 'Access denied',
        code: 'FORBIDDEN'
      });
    }
  };
};

/**
 * Permission-based access control middleware
 * Checks if the authenticated user has all required permissions
 * @param permissions Array of required permissions
 */
export const hasPermission = (permissions: string[]) => {
  return (req: Request, res: Response, next: NextFunction): void => {
    try {
      // Check if user is authenticated
      if (!req.user) {
        throw AuthError.unauthorized('User is not authenticated');
      }

      // Handle user tokens
      if (req.tokenPayload?.type === 'user') {
        const userPermissions = req.user.permissions || [];
        const hasAllPermissions = permissions.every(permission =>
          userPermissions.includes(permission)
        );

        if (!hasAllPermissions) {
          throw AuthError.forbidden(`Access denied. Required permissions: ${permissions.join(', ')}`);
        }
      }
      // Handle client tokens
      else if (req.tokenPayload?.type === 'client') {
        const clientScopes = req.user.scopes || [];

        // Map permissions to corresponding scopes
        // This is a simplistic mapping approach - you might need a more complex mapping
        const requiredScopes = permissions.map(permission => {
          // Example mapping: 'read:restaurant' permission maps to 'read:restaurant' scope
          return permission;
        });

        const hasAllScopes = requiredScopes.every(scope =>
          clientScopes.includes(scope)
        );

        if (!hasAllScopes) {
          throw AuthError.forbidden(`Access denied. Required scopes: ${requiredScopes.join(', ')}`);
        }
      } else {
        throw AuthError.unauthorized('Invalid token type');
      }

      next();
    } catch (error) {
      if (error instanceof AuthError) {
        return res.status(error.statusCode).json({
          status: 'error',
          message: error.message,
          code: error.code
        });
      }

      logger.error('Permission verification error:', error);
      res.status(403).json({
        status: 'error',
        message: 'Access denied',
        code: 'FORBIDDEN'
      });
    }
  };
};

/**
 * Scope-based access control middleware
 * Checks if the authenticated client has all required scopes
 * @param scopes Array of required scopes
 */
export const hasScope = (scopes: string[]) => {
  return (req: Request, res: Response, next: NextFunction): void => {
    try {
      // Check if client is authenticated
      if (!req.user) {
        throw AuthError.unauthorized('Client is not authenticated');
      }

      // This middleware is only for client tokens
      if (req.tokenPayload?.type !== 'client') {
        throw AuthError.forbidden('This endpoint requires client authentication');
      }

      const clientScopes = req.user.scopes || [];
      const hasAllScopes = scopes.every(scope =>
        clientScopes.includes(scope)
      );

      if (!hasAllScopes) {
        throw AuthError.forbidden(`Access denied. Required scopes: ${scopes.join(', ')}`);
      }

      next();
    } catch (error) {
      if (error instanceof AuthError) {
        return res.status(error.statusCode).json({
          status: 'error',
          message: error.message,
          code: error.code
        });
      }

      logger.error('Scope verification error:', error);
      res.status(403).json({
        status: 'error',
        message: 'Access denied',
        code: 'FORBIDDEN'
      });
    }
  };
};

/**
 * Restaurant access middleware
 * Checks if the user has access to the specified restaurant entity
 * @param paramName The parameter name that contains the restaurant ID
 */
export const hasRestaurantAccess = (paramName: string = 'restaurantId') => {
  return async (req: Request, res: Response, next: NextFunction): Promise<void> => {
    try {
      // This middleware requires authentication
      if (!req.user) {
        throw AuthError.unauthorized('User is not authenticated');
      }

      // Get the restaurant ID from the request
      const restaurantId = req.params[paramName] || req.body[paramName];

      if (!restaurantId) {
        throw AuthError.badRequest(`Restaurant ID not found in parameter ${paramName}`);
      }

      // For user tokens - check access through repository
      if (req.tokenPayload?.type === 'user') {
        // Check if user has admin role (admins can access all restaurants)
        const isAdmin = req.user.roles.includes('admin');

        if (isAdmin) {
          return next();
        }

        // For non-admin users, check specific restaurant access
        // This would typically query the access repository
        // For now, we'll use a placeholder implementation
        const hasAccess = true; // Replace with actual access check

        if (!hasAccess) {
          throw AuthError.forbidden(`Access denied for restaurant ${restaurantId}`);
        }
      }
      // For client tokens - check scopes
      else if (req.tokenPayload?.type === 'client') {
        // Client scopes can include specific restaurant IDs like 'restaurant:12345'
        // or wildcard access like 'restaurant:*'
        const clientScopes = req.user.scopes || [];
        const hasAccess = clientScopes.some(scope =>
          scope === 'restaurant:*' ||
          scope === `restaurant:${restaurantId}`
        );

        if (!hasAccess) {
          throw AuthError.forbidden(`Client does not have access to restaurant ${restaurantId}`);
        }
      }

      next();
    } catch (error) {
      if (error instanceof AuthError) {
        return res.status(error.statusCode).json({
          status: 'error',
          message: error.message,
          code: error.code
        });
      }

      logger.error('Restaurant access verification error:', error);
      res.status(403).json({
        status: 'error',
        message: 'Access denied',
        code: 'FORBIDDEN'
      });
    }
  };
};
