import { Request, Response, NextFunction } from 'express';
import { PgAccessRepository } from '../../infrastructure/repositories/postgresql/access.repository';
import { PgUserRepository } from '../../infrastructure/repositories/postgresql/user.repository';
import { PgRestaurantGroupRepository, PgRestaurantBrandRepository, PgRestaurantOutletRepository } from '../../infrastructure/repositories/postgresql/restaurant.repository';
import { ApiError } from '../../utils/errors/api.error';
import logger from '../../config/logger';
import { CreateAccessDto, UpdateAccessDto } from '../../domain/models/access.model';

// Initialize repositories
const accessRepository = new PgAccessRepository();
const userRepository = new PgUserRepository();
const groupRepository = new PgRestaurantGroupRepository();
const brandRepository = new PgRestaurantBrandRepository();
const outletRepository = new PgRestaurantOutletRepository();

/**
 * Access Controller
 * Handles user access management for restaurants
 */
export class AccessController {
  /**
   * Get all access entries with pagination
   * @route GET /api/access
   */
  async getAllAccess(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const page = parseInt(req.query.page as string) || 1;
      const limit = parseInt(req.query.limit as string) || 10;

      // Extract filter parameters
      const filters: Record<string, any> = {};
      if (req.query.user_id) {
        filters.user_id = req.query.user_id;
      }
      if (req.query.restaurant_id) {
        filters.restaurant_id = req.query.restaurant_id;
      }
      if (req.query.restaurant_level) {
        filters.restaurant_level = req.query.restaurant_level;
      }
      if (req.query.permission) {
        filters.permission = req.query.permission;
      }

      // Get data with pagination
      const accessEntries = await accessRepository.findAll(page, limit, filters);
      const totalCount = await accessRepository.count(filters);

      // Calculate pagination metadata
      const totalPages = Math.ceil(totalCount / limit);
      const hasNext = page < totalPages;
      const hasPrev = page > 1;

      res.status(200).json({
        status: 'success',
        data: {
          access: accessEntries,
          pagination: {
            page,
            limit,
            totalCount,
            totalPages,
            hasNext,
            hasPrev
          }
        }
      });
    } catch (error) {
      next(error);
    }
  }

  /**
   * Get a specific access entry by ID
   * @route GET /api/access/:id
   */
  async getAccessById(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const accessId = req.params.id;
      const access = await accessRepository.findById(accessId);

      if (!access) {
        throw ApiError.notFound(`Access entry with ID ${accessId} not found`);
      }

      res.status(200).json({
        status: 'success',
        data: { access }
      });
    } catch (error) {
      next(error);
    }
  }

  /**
   * Create a new access entry
   * @route POST /api/access
   */
  async createAccess(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const accessData: CreateAccessDto = req.body;

      // Check if user exists
      const user = await userRepository.findById(accessData.user_id);
      if (!user) {
        throw ApiError.badRequest(`User with ID ${accessData.user_id} not found`);
      }

      // Check if restaurant entity exists based on level
      await this.validateRestaurantEntity(accessData.restaurant_id, accessData.restaurant_level);

      // Check if access already exists
      const existingAccess = await accessRepository.findSpecificAccess(
        accessData.user_id,
        accessData.restaurant_id,
        accessData.restaurant_level
      );

      if (existingAccess) {
        throw ApiError.conflict('User already has access to this restaurant entity');
      }

      // Create access entry
      const newAccess = await accessRepository.create(accessData);

      res.status(201).json({
        status: 'success',
        data: { access: newAccess }
      });
    } catch (error) {
      next(error);
    }
  }

  /**
   * Update an access entry
   * @route PUT /api/access/:id
   */
  async updateAccess(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const accessId = req.params.id;
      const accessData: UpdateAccessDto = req.body;

      // Check if access entry exists
      const existingAccess = await accessRepository.findById(accessId);
      if (!existingAccess) {
        throw ApiError.notFound(`Access entry with ID ${accessId} not found`);
      }

      // Update access entry permissions
      const updatedAccess = await accessRepository.updatePermissions(
        accessId,
        accessData.permissions || []
      );

      if (!updatedAccess) {
        throw ApiError.internal('Failed to update access entry');
      }

      res.status(200).json({
        status: 'success',
        data: { access: updatedAccess }
      });
    } catch (error) {
      next(error);
    }
  }

  /**
   * Delete an access entry
   * @route DELETE /api/access/:id
   */
  async deleteAccess(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const accessId = req.params.id;

      // Check if access entry exists
      const existingAccess = await accessRepository.findById(accessId);
      if (!existingAccess) {
        throw ApiError.notFound(`Access entry with ID ${accessId} not found`);
      }

      // Delete access entry
      const deleted = await accessRepository.delete(accessId);

      if (!deleted) {
        throw ApiError.internal('Failed to delete access entry');
      }

      res.status(200).json({
        status: 'success',
        message: `Access entry with ID ${accessId} has been deleted`
      });
    } catch (error) {
      next(error);
    }
  }

  /**
   * Get user's access to restaurants
   * @route GET /api/access/user/:userId
   */
  async getUserAccess(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const userId = req.params.userId;

      // Check if user exists
      const user = await userRepository.findById(userId);
      if (!user) {
        throw ApiError.notFound(`User with ID ${userId} not found`);
      }

      // Get access summary
      const accessSummary = await accessRepository.getUserAccessSummary(userId);

      res.status(200).json({
        status: 'success',
        data: { accessSummary }
      });
    } catch (error) {
      next(error);
    }
  }

  /**
   * Get users with access to a restaurant
   * @route GET /api/access/restaurant/:restaurantId
   */
  async getRestaurantUsers(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const restaurantId = req.params.restaurantId;
      const restaurantLevel = req.query.level as 'group' | 'brand' | 'outlet';

      // Validate restaurant level
      if (!['group', 'brand', 'outlet'].includes(restaurantLevel)) {
        throw ApiError.badRequest('Invalid restaurant level. Must be one of: group, brand, outlet');
      }

      // Check if restaurant entity exists
      await this.validateRestaurantEntity(restaurantId, restaurantLevel);

      // Get access entries for this restaurant
      const accessEntries = await accessRepository.findByRestaurantId(
        restaurantId,
        restaurantLevel
      );

      // Get user details for each access entry
      const userIds = accessEntries.map(entry => entry.user_id);
      const usersWithAccess = [];

      for (const userId of userIds) {
        const user = await userRepository.findById(userId);
        if (user) {
          const { password, ...userData } = user;

          // Find the access entry for this user
          const access = accessEntries.find(entry => entry.user_id === userId);

          usersWithAccess.push({
            user: userData,
            access
          });
        }
      }

      res.status(200).json({
        status: 'success',
        data: { usersWithAccess }
      });
    } catch (error) {
      next(error);
    }
  }

  /**
   * Add permissions to an access entry
   * @route PATCH /api/access/:id/permissions/add
   */
  async addPermissions(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const accessId = req.params.id;
      const { permissions } = req.body;

      if (!Array.isArray(permissions)) {
        throw ApiError.badRequest('Permissions must be an array');
      }

      // Check if access entry exists
      const existingAccess = await accessRepository.findById(accessId);
      if (!existingAccess) {
        throw ApiError.notFound(`Access entry with ID ${accessId} not found`);
      }

      // Add permissions
      const updatedAccess = await accessRepository.addPermissions(accessId, permissions);

      if (!updatedAccess) {
        throw ApiError.internal('Failed to add permissions');
      }

      res.status(200).json({
        status: 'success',
        data: { access: updatedAccess }
      });
    } catch (error) {
      next(error);
    }
  }

  /**
   * Remove permissions from an access entry
   * @route PATCH /api/access/:id/permissions/remove
   */
  async removePermissions(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const accessId = req.params.id;
      const { permissions } = req.body;

      if (!Array.isArray(permissions)) {
        throw ApiError.badRequest('Permissions must be an array');
      }

      // Check if access entry exists
      const existingAccess = await accessRepository.findById(accessId);
      if (!existingAccess) {
        throw ApiError.notFound(`Access entry with ID ${accessId} not found`);
      }

      // Remove permissions
      const updatedAccess = await accessRepository.removePermissions(accessId, permissions);

      if (!updatedAccess) {
        throw ApiError.internal('Failed to remove permissions');
      }

      res.status(200).json({
        status: 'success',
        data: { access: updatedAccess }
      });
    } catch (error) {
      next(error);
    }
  }

  /**
   * Bulk assign access to multiple users
   * @route POST /api/access/bulk-assign
   */
  async bulkAssignAccess(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const { restaurant_id, restaurant_level, user_ids, permissions } = req.body;

      if (!restaurant_id || !restaurant_level || !user_ids || !Array.isArray(user_ids)) {
        throw ApiError.badRequest('Missing required fields');
      }

      // Validate restaurant level
      if (!['group', 'brand', 'outlet'].includes(restaurant_level)) {
        throw ApiError.badRequest('Invalid restaurant level. Must be one of: group, brand, outlet');
      }

      // Check if restaurant entity exists
      await this.validateRestaurantEntity(restaurant_id, restaurant_level);

      // Check if all users exist
      for (const userId of user_ids) {
        const user = await userRepository.findById(userId);
        if (!user) {
          throw ApiError.badRequest(`User with ID ${userId} not found`);
        }
      }

      // Bulk assign access
      const result = await accessRepository.bulkAssignAccess(
        restaurant_id,
        restaurant_level,
        user_ids,
        permissions || []
      );

      res.status(200).json({
        status: 'success',
        data: {
          accessEntries: result,
          count: result.length
        }
      });
    } catch (error) {
      next(error);
    }
  }

  /**
   * Revoke all access for a user
   * @route DELETE /api/access/user/:userId/revoke-all
   */
  async revokeAllAccessForUser(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const userId = req.params.userId;

      // Check if user exists
      const user = await userRepository.findById(userId);
      if (!user) {
        throw ApiError.notFound(`User with ID ${userId} not found`);
      }

      // Revoke all access
      const result = await accessRepository.revokeAllAccessForUser(userId);

      res.status(200).json({
        status: 'success',
        message: `All access for user with ID ${userId} has been revoked`,
        success: result
      });
    } catch (error) {
      next(error);
    }
  }

  /**
   * Check if user has access to a restaurant
   * @route GET /api/access/check
   */
  async checkAccess(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const { user_id, restaurant_id, level, permission } = req.query as {
        user_id: string;
        restaurant_id: string;
        level: 'group' | 'brand' | 'outlet';
        permission?: string;
      };

      if (!user_id || !restaurant_id || !level) {
        throw ApiError.badRequest('Missing required parameters');
      }

      // Validate restaurant level
      if (!['group', 'brand', 'outlet'].includes(level)) {
        throw ApiError.badRequest('Invalid restaurant level. Must be one of: group, brand, outlet');
      }

      // Check if user exists
      const user = await userRepository.findById(user_id);
      if (!user) {
        throw ApiError.notFound(`User with ID ${user_id} not found`);
      }

      // Check if restaurant entity exists
      await this.validateRestaurantEntity(restaurant_id, level);

      let hasAccess = false;

      if (permission) {
        // Check for specific permission
        hasAccess = await accessRepository.hasPermission(user_id, restaurant_id, permission);
      } else {
        // Check for any access
        hasAccess = await accessRepository.hasAccess(user_id, restaurant_id, level);
      }

      res.status(200).json({
        status: 'success',
        data: {
          user_id,
          restaurant_id,
          level,
          permission: permission || null,
          hasAccess
        }
      });
    } catch (error) {
      next(error);
    }
  }

  /**
   * Validate that a restaurant entity exists
   * @param id Restaurant entity ID
   * @param level Restaurant level (group, brand, outlet)
   */
  private async validateRestaurantEntity(
    id: string,
    level: 'group' | 'brand' | 'outlet'
  ): Promise<void> {
    let exists = false;

    switch (level) {
      case 'group':
        const group = await groupRepository.findById(id);
        exists = !!group;
        break;
      case 'brand':
        const brand = await brandRepository.findById(id);
        exists = !!brand;
        break;
      case 'outlet':
        const outlet = await outletRepository.findById(id);
        exists = !!outlet;
        break;
    }

    if (!exists) {
      throw ApiError.badRequest(`Restaurant ${level} with ID ${id} not found`);
    }
  }
}

// Export controller instance
export const accessController = new AccessController();
