import { Injectable, HttpException, HttpStatus, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Like, Repository } from 'typeorm';
import { hash } from 'bcrypt';
import { UserEntity } from './entity/users.entity';
import { UpdateUserDto } from './dto/update-user.dto';
import { mapUsersToResponseDtos, UserResponseDto } from './dto/user-response.dto';
import { CreateAdminDto } from './dto/create-admin.dto';
import { UserRole, UserStatus } from 'common/enum/UserRole.enum';
import { BaseUserAbstract } from './abstracts/user.abstract';
import { UserFilterDto } from './dto/user-filter.dto';
import { mapUsersToDto, mapUserToDto } from './dto/map-user.dto';
import { ClientEntity } from 'modules/client/entities/client.entity';
import { PaginatedResponseDto } from 'common/utils/paginated-response.dto';

@Injectable()
export class UserService extends BaseUserAbstract {
  private readonly logger = new Logger(UserService.name);

  constructor(
    @InjectRepository(UserEntity)
    private readonly userRepository: Repository<UserEntity>,
  ) {
    super();
  }

  public async countUsers(): Promise<number> {
    try {
      return await this.userRepository.count();
    } catch (error) {
      this.logger.error('Error counting users', error);
      throw new HttpException(error.message, error.status || HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }

  public async findAllUsers(page: number = 1, limit: number = 10): Promise<PaginatedResponseDto<UserResponseDto>> {
    try {
      const skip = (page - 1) * limit;
      const findUser = await this.userRepository.find({
        skip,
        take: limit,
        order: {
          createdAt: 'ASC',
        },
      });
      const total = await this.userRepository.count();
      const totalPages = Math.ceil(total / limit);

      return {
        items: mapUsersToDto(findUser),
        currentPage: page,
        itemsPerPage: limit,
        totalItems: total,
        totalPages: totalPages,
      };
    } catch (error) {
      this.logger.error('Error fetching all users', error);
      throw new HttpException(error.message, error.status || HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }

  public async findUserById(userId: number): Promise<UserResponseDto> {
    try {
      const findUser = await this.userRepository.findOne({
        where: { id: userId },
        // relations: ['roles'],
      });

      if (!findUser) {
        throw new HttpException("User doesn't exist", HttpStatus.NOT_FOUND);
      }

      const userResponse = mapUserToDto(findUser);
      return userResponse;
    } catch (error) {
      this.logger.error(`Error fetching user by ID: ${userId}`, error);
      throw new HttpException(error.message, error.status || HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }

  // NOTE: This method is for internal use only, it returns the UserEntity directly
  async findUserByUserIdWithEntity(userId: number): Promise<UserEntity> {
    try {
      const findUser = await this.userRepository.findOne({
        where: { id: userId },
      });

      if (!findUser) {
        throw new HttpException("User doesn't exist", HttpStatus.NOT_FOUND);
      }

      return findUser;
    } catch (error) {
      this.logger.error(`Error fetching user by ID with entity: ${userId}`, error);
      throw new HttpException(error.message, error.status || HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }

  //  Note: This method fetches a user by ID and includes their roles in the response.
  //  This is only for internal use where roles are needed. Not for public api
  public async findUserByIdWithRoles(userId: number): Promise<UserResponseDto> {
    try {
      const findUser = await this.userRepository.findOne({
        where: { id: userId },
        relations: ['roles'],
      });
      if (!findUser) {
        throw new HttpException("User doesn't exist", HttpStatus.NOT_FOUND);
      }
      const userResponse = mapUserToDto(findUser);
      this.logger.debug(`Fetched user by ID: ${userId}`, { userResponse });
      return userResponse;
    } catch (error) {
      this.logger.error(`Error fetching user by ID with roles: ${userId}`, error);
      throw new HttpException(error.message, error.status || HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }

  public async findUserByEmailWithRoles(email: string): Promise<UserResponseDto> {
    try {
      const findUser = await this.userRepository.findOne({
        where: { email },
        relations: ['roles'],
      });
      if (!findUser) {
        throw new HttpException("User doesn't exist", HttpStatus.NOT_FOUND);
      }
      const userResponse = mapUserToDto(findUser);
      this.logger.debug(`Fetched user by email with roles: ${email}`, { userResponse });
      return userResponse;
    } catch (error) {
      this.logger.error(`Error fetching user by email with roles: ${email}`, error);
      throw new HttpException(error.message, error.status || HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }

  public async updateUserRoles(userId: number, roles: any[]): Promise<UserResponseDto> {
    this.logger.log(`Updating roles for user ID: ${userId}`, { roles });
    try {
      const findUser = await this.userRepository.findOne({
        where: { id: userId },
        relations: ['roles'],
      });
      if (!findUser) {
        throw new HttpException("User doesn't exist", HttpStatus.NOT_FOUND);
      }
      this.logger.log(`Updating roles for user ID: ${userId}`, { roles });
      //  add new roles more to existing roles
      const existingRoles = findUser.roles.map(role => role.id);
      const newRoles = roles.filter(role => !existingRoles.includes(role.id));
      findUser.roles = [...findUser.roles, ...newRoles];

      await this.userRepository.save(findUser);
      this.logger.debug('Updated user roles successfully', { userId, roles: findUser.roles });
      return mapUserToDto(findUser);
    } catch (error) {
      this.logger.error(`Error updating roles for user ID: ${userId}`, error);
      throw new HttpException(error.message, error.status || HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }

  public async removeUserRole(userId: number, roleId: string): Promise<UserResponseDto> {
    try {
      const findUser = await this.userRepository.findOne({
        where: { id: userId },
        relations: ['roles'],
      });

      if (!findUser) {
        throw new HttpException("User doesn't exist", HttpStatus.NOT_FOUND);
      }

      const roleIndex = findUser.roles.findIndex(role => role.id === roleId);
      if (roleIndex === -1) {
        throw new HttpException(`Role with ID ${roleId} not found for user`, HttpStatus.NOT_FOUND);
      }

      findUser.roles.splice(roleIndex, 1);
      await this.userRepository.save(findUser);

      return mapUserToDto(findUser);
    } catch (error) {
      this.logger.error(`Error removing role ID: ${roleId} from user ID: ${userId}`, error);
      throw new HttpException(error.message, error.status || HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }

  public async findUserByEmail(email: string): Promise<UserResponseDto> {
    try {
      const findUser = await this.userRepository.findOne({ where: { email } });

      if (!findUser) {
        throw new HttpException("User doesn't exist", HttpStatus.NOT_FOUND);
      }

      const userResponse = mapUserToDto(findUser);

      return userResponse;
    } catch (error) {
      this.logger.error(`Error fetching user by email: ${email}`, error);
      throw new HttpException(error.message, error.status || HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }

  public async createAdmin(userData: CreateAdminDto): Promise<UserResponseDto> {
    this.logger.log('Admin registration attempt', { email: userData.email });

    try {
      if (userData.role !== UserRole.ADMIN) {
        throw new HttpException('Invalid role for admin creation', HttpStatus.BAD_REQUEST);
      }

      const existingUser = await this.userRepository.findOne({
        where: { email: userData.email },
      });

      if (existingUser) {
        throw new HttpException(`This email ${userData.email} already exists`, HttpStatus.CONFLICT);
      }

      const hashedPassword = await hash(userData.password, 10);
      const createUserData = this.userRepository.create({
        ...userData,
        password: hashedPassword,
      });

      const createdUser = await this.userRepository.save(createUserData);
      return mapUserToDto(createdUser);
    } catch (error) {
      this.logger.error('Error creating admin', error);
      throw new HttpException(error.message, error.status || HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }

  public async updateUser(userId: number, userData: UpdateUserDto): Promise<UserResponseDto> {
    this.logger.log(`Updating user ID: ${userId}`, { userData });
    try {
      const findUser = await this.userRepository.findOne({
        where: { id: userId },
      });

      if (!findUser) {
        throw new HttpException("User doesn't exist", HttpStatus.NOT_FOUND);
      }

      if (userData.email && userData.email !== findUser.email) {
        const emailExists = await this.userRepository.findOne({
          where: { email: userData.email },
        });

        if (emailExists) {
          throw new HttpException(`This email ${userData.email} already exists`, HttpStatus.CONFLICT);
        }
      }

      if (userData.password) {
        userData.password = await hash(userData.password, 10);
      }

      await this.userRepository.update(userId, userData);
      const updatedUser = await this.userRepository.findOne({
        where: { id: userId },
      });

      return mapUserToDto(updatedUser);
    } catch (error) {
      this.logger.error(`Error updating user ID: ${userId}`, error);
      throw new HttpException(error.message, error.status || HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }

  async upgradeUserToClient(userId: number, client: ClientEntity): Promise<UserResponseDto> {
    this.logger.log(`Upgrading user ID: ${userId} to client`, { client });
    try {
      const findUser = await this.userRepository.findOne({
        where: { id: userId },
      });
      if (!findUser) {
        throw new HttpException("User doesn't exist", HttpStatus.NOT_FOUND);
      }
      if (findUser.role === UserRole.CLIENT) {
        throw new HttpException('User is already a client', HttpStatus.CONFLICT);
      }
      if (findUser.role !== UserRole.USER) {
        throw new HttpException('Only users can be upgraded to clients', HttpStatus.BAD_REQUEST);
      }
      // Update user role to CLIENT
      findUser.role = UserRole.CLIENT;
      // Associate the user with the client
      findUser.planClient = client;
      const updatedUser = await this.userRepository.save(findUser);
      this.logger.debug('User upgraded to client successfully', { userId, clientId: client.id });
      return mapUserToDto(updatedUser);
    } catch (error) {
      this.logger.error(`Error upgrading user ID: ${userId} to client`, error);
      throw new HttpException(error.message, error.status || HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }

  async linkUserToClient(userId: number, clientId: string): Promise<UserResponseDto> {
    this.logger.log(`Linking user ID: ${userId} to client ID: ${clientId}`);
    try {
      const findUser = await this.userRepository.findOne({
        where: { id: userId },
        relations: ['planClient'],
      });
      if (!findUser) {
        throw new HttpException("User doesn't exist", HttpStatus.NOT_FOUND);
      }

      if (findUser.role !== UserRole.USER) {
        throw new HttpException('Only users can be linked to clients', HttpStatus.BAD_REQUEST);
      }

      // Check is user is verified
      if (!findUser.isEmailVerified) {
        throw new HttpException('User email is not verified', HttpStatus.BAD_REQUEST);
      }

      // Link the user to the client
      findUser.linkedClientId = clientId; // Set the linked client ID

      const updatedUser = await this.userRepository.save(findUser);
      this.logger.debug('User linked to client successfully', { userId, clientId });
      return mapUserToDto(updatedUser);
    } catch (error) {
      this.logger.error(`Error linking user ID: ${userId} to client ID: ${clientId}`, error);
      throw new HttpException(error.message, error.status || HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }

  async unlinkUserFromClient(userId: number, clientId: string): Promise<UserResponseDto> {
    this.logger.log(`Unlinking user ID: ${userId} from client`);
    try {
      const findUser = await this.userRepository.findOne({
        where: { id: userId },
        relations: ['planClient'],
      });

      // Check if the user exists
      if (!findUser) {
        throw new HttpException("User doesn't exist", HttpStatus.NOT_FOUND);
      }

      // Check if the user is linked to the specified client
      if (findUser.linkedClientId !== clientId) {
        throw new HttpException(`User is not linked to client ID: ${clientId}`, HttpStatus.BAD_REQUEST);
      }

      if (findUser.role !== UserRole.USER) {
        throw new HttpException('Only users can be unlinked from clients', HttpStatus.BAD_REQUEST);
      }

      // Unlink the user from the client
      findUser.linkedClientId = null; // Clear linked client ID
      findUser.clientId = null; // Clear client ID
      findUser.planClient = null; // Clear planClient association
      findUser.linkedClient = null; // Clear linkedClient association
      const updatedUser = await this.userRepository.save(findUser);
      this.logger.debug('User unlinked from client successfully', { userId });
      return mapUserToDto(updatedUser);
    } catch (error) {
      this.logger.error(`Error unlinking user ID: ${userId} from client`, error);
      throw new HttpException(error.message, error.status || HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }

  public async deleteUser(userId: number): Promise<void> {
    try {
      const findUser = await this.userRepository.findOne({
        where: { id: userId },
      });
      if (!findUser) {
        throw new HttpException("User doesn't exist", HttpStatus.NOT_FOUND);
      }

      await this.userRepository.delete(userId);
    } catch (error) {
      this.logger.error(`Error deleting user ID: ${userId}`, error);
      throw new HttpException(error.message, error.status || HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }

  public async findUsersByFilter(filter: UserFilterDto): Promise<PaginatedResponseDto<UserResponseDto>> {
    this.logger.log('Finding users by filter', { filter });
    try {
      const { page = 1, limit = 10, role, status, clientId, search } = filter;
      const skip = (page - 1) * limit;

      const whereConditions: any = {};

      if (role) {
        whereConditions.role = role;
      }

      if (status) {
        whereConditions.status = status;
      }

      if (clientId) {
        whereConditions.clientId = clientId;
      }

      if (search) {
        whereConditions.email = Like(`%${search}%`);
      }

      const [users, total] = await this.userRepository.findAndCount({
        where: whereConditions,
        skip,
        take: limit,
        order: {
          createdAt: 'ASC',
        },
      });

      const totalPages = Math.ceil(total / limit);
      return {
        items: mapUsersToDto(users),
        currentPage: page,
        itemsPerPage: limit,
        totalItems: total,
        totalPages: totalPages,
      };
    } catch (error) {
      this.logger.error('Error finding users by filter', error);
      throw new HttpException(error.message, error.status || HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }

  public async findUserByRole(role: UserRole, page: number, limit: number): Promise<PaginatedResponseDto<UserResponseDto>> {
    this.logger.log(`Finding users by role: ${role}`, { page, limit });
    // Sanitize page and limit
    if (page < 1 || limit < 1) {
      throw new HttpException('Page and limit must be greater than 0', HttpStatus.BAD_REQUEST);
    }
    try {
      const skip = (page - 1) * limit;

      const [users, total] = await this.userRepository.findAndCount({
        where: { role },
        skip,
        take: limit,
        order: {
          createdAt: 'ASC',
        },
      });

      const totalPages = Math.ceil(total / limit);
      return {
        items: mapUsersToResponseDtos(users),
        currentPage: page,
        itemsPerPage: limit,
        totalItems: total,
        totalPages: totalPages,
      };
    } catch (error) {
      this.logger.error(`Error finding users by role: ${role}`, error);
      throw new HttpException(error.message, error.status || HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }

  public async findUserByClientId(clientId: string, page = 1, limit = 10): Promise<PaginatedResponseDto<UserResponseDto>> {
    this.logger.log(`Finding users by client ID: ${clientId}`, { page, limit });
    // Sanitize page and limit
    try {
      const safePage = Math.max(1, Number(page) || 1);
      const safeLimit = Math.max(1, Number(limit) || 10);
      const skip = (safePage - 1) * safeLimit;

      const [users, total] = await this.userRepository.findAndCount({
        where: {
          linkedClientId: clientId, // Use linkedClientId for users linked to a client
        },
        skip,
        take: safeLimit,
        order: {
          createdAt: 'ASC',
        },
      });

      const totalPages = Math.ceil(total / safeLimit);

      return {
        items: mapUsersToResponseDtos(users),
        currentPage: safePage,
        itemsPerPage: safeLimit,
        totalItems: total,
        totalPages: totalPages,
      };
    } catch (error) {
      this.logger.error(`Error finding users by client ID: ${clientId}`, error);
      throw new HttpException(error.message, error.status || HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }

  public async findUserByStatus(status: UserStatus, page: number, limit: number): Promise<PaginatedResponseDto<UserResponseDto>> {
    this.logger.log(`Finding users by status: ${status}`, { page, limit });
    try {
      const skip = (page - 1) * limit;

      const [users, total] = await this.userRepository.findAndCount({
        where: { status },
        skip,
        take: limit,
        order: {
          createdAt: 'ASC',
        },
      });

      const totalPages = Math.ceil(total / limit);
      return {
        items: mapUsersToResponseDtos(users),
        currentPage: page,
        itemsPerPage: limit,
        totalItems: total,
        totalPages: totalPages,
      };
    } catch (error) {
      this.logger.error(`Error finding users by status: ${status}`, error);
      throw new HttpException(error.message, error.status || HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }

  public async getUsersLinkedToClient(clientId: string, page: number, limit: number): Promise<PaginatedResponseDto<UserResponseDto>> {
    // this.logger.log(`Fetching users linked to client ID: ${clientId}`, { page, limit });
    try {
      const skip = (page - 1) * limit;

      const [users, total] = await this.userRepository.findAndCount({
        where: { clientId },
        skip,
        take: limit,
        order: {
          createdAt: 'ASC',
        },
      });

      const totalPages = Math.ceil(total / limit);
      return {
        items: mapUsersToResponseDtos(users),
        currentPage: page,
        itemsPerPage: limit,
        totalItems: total,
        totalPages: totalPages,
      };
    } catch (error) {
      this.logger.error(`Error fetching users linked to client ID: ${clientId}`, error);
      throw new HttpException(error.message, error.status || HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }

  public async getUserCountByRole(role: UserRole): Promise<number> {
    try {
      return await this.userRepository.count({
        where: { role },
      });
    } catch (error) {
      this.logger.error(`Error counting users by role: ${role}`, error);
      throw new HttpException(error.message, error.status || HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }
}
