import {
  BadRequestException,
  Body,
  Controller,
  Delete,
  Get,
  HttpCode,
  HttpStatus,
  InternalServerErrorException,
  NotFoundException,
  Param,
  Patch,
  Post,
  Put,
  Req,
} from '@nestjs/common';
import RequestWithUser from '../common/interfaces/requestWithUser.interface';
import { EUserClass, UsersService } from './users.service';
import { AuthService } from 'src/auth/auth.service';
import { UpdateUserDto } from './dto/update-user.dto';
import { JoinGroupDto } from './dto/join-group.dto';
import { Roles } from 'src/roles/roles.decorator';
import { ERole } from 'src/roles/roles.service';
import { SelectProfileDto } from './dto/select-profile.dto';
import { UserClasses } from './users.decorator';
import { CancelJoinGroupDto } from './dto/cancel-join-group.dto';
import { UsernameExistsDto } from './dto/username-exists.dto';
import { CreateUserDto } from './dto/create-user.dto';
// import { CreateUserDto } from './dto/create-user.dto';

// TODO: add applicable roles for members
// @Roles(ERole.SystemDeveloper, ERole.IPWAdministrator)
@Controller('user')
export class UsersController {
  constructor(
    private readonly usersService: UsersService,
    private readonly authService: AuthService
  ) {}

  @HttpCode(HttpStatus.OK)
  @Get('contact-details')
  // @Roles(
  //   // user_class_id = 1
  //   ERole.SystemDeveloper,
  //   ERole.IPWAdministrator,
  //   ERole.IPWAuditor,
  //   ERole.IPWDataCapturer,
  //   ERole.IPWContentManager,
  //   ERole.IPWLeadAuditor,
  //   ERole.MemberAdministrator, // 2
  //   ERole.EditEvaluationForms, // 2
  //   ERole.MemberReports,
  //   ERole.ViewEvaluationForms, // 2
  //   ERole.ViewStatusOnly, // 2
  // )
  async getContactDetails(@Req() req: RequestWithUser) {
    const user = await this.usersService.findById(req.user.user_id);
    if (!user) throw new NotFoundException();
    const metaUserIds = [user.created_by as number, user.updated_by as number];
    const metaUsers = await this.usersService.findByIds(metaUserIds);
    for (let i = 0, n = metaUsers.length; i < n; i++) {
      const metaUser = metaUsers[i];
      if (metaUser.user_id === user.created_by) user.created_by = metaUser;
      if (metaUser.user_id === user.updated_by) user.updated_by = metaUser;
    }
    return user;
  }

  /*
   * @description Groups that the user is assigned to along with their role for the group
   * @return A list of user groups containing the users role where applicable
   */
  @HttpCode(HttpStatus.OK)
  @Get('groups-and-roles')
  // @Roles(
  //   // user_class_id = 1
  //   ERole.SystemDeveloper,
  //   ERole.IPWAdministrator,
  //   ERole.IPWAuditor,
  //   ERole.IPWDataCapturer,
  //   ERole.IPWContentManager,
  //   ERole.IPWLeadAuditor,
  //   ERole.MemberAdministrator, // 2
  //   ERole.EditEvaluationForms, // 2
  //   ERole.MemberReports,
  //   ERole.ViewEvaluationForms, // 2
  //   ERole.ViewStatusOnly, // 2
  // )
  async getGroupsAndRoles(@Req() req: RequestWithUser) {
    const data = await this.usersService.groupsAndRoles(req.user.user_id);
    if (data.length > 0) {
      const groupIds = data.map((d: any) => d.users_has_group_id);
      const roleIds = await this.usersService.roleIdForGroups(groupIds);
      for (let i = 0, n = data.length; i < n; i++) {
        const group = data[i];
        if (roleIds.length === 0) continue;
        const role_id = roleIds.find((r: any) => r.roles_id).roles_id;
        group.account_administrator = role_id === BigInt(14);
        group.evaluation_form_manager = role_id === BigInt(10);
        group.view_only = role_id === BigInt(11);
        group.status_view_only = role_id === BigInt(16);
      }
    }
    return { data };
  }

  /*
   * @description Groups that the user has not already joined or awaiting approval to join
   * @return A list of user groups
   */
  @HttpCode(HttpStatus.OK)
  @Get('available-groups')
  // @Roles(
  //   // user_class_id = 1
  //   ERole.SystemDeveloper,
  //   ERole.IPWAdministrator,
  //   ERole.IPWAuditor,
  //   ERole.IPWDataCapturer,
  //   ERole.IPWContentManager,
  //   ERole.IPWLeadAuditor,
  //   ERole.MemberAdministrator, // 2
  //   ERole.EditEvaluationForms, // 2
  //   ERole.MemberReports,
  //   ERole.ViewEvaluationForms, // 2
  //   ERole.ViewStatusOnly, // 2
  // )
  async getAvailableGroups(@Req() req: RequestWithUser) {
    const data = await this.usersService.availableGroups(req.user.user_id);
    return { data };
  }

  /*
   * @description Add the user to the pending groups for manager approval
   */
  @HttpCode(HttpStatus.CREATED)
  @Post('join-group')
  // @Roles(
  //   // user_class_id = 1
  //   ERole.SystemDeveloper,
  //   ERole.IPWAdministrator,
  //   ERole.IPWAuditor,
  //   ERole.IPWDataCapturer,
  //   ERole.IPWContentManager,
  //   ERole.IPWLeadAuditor,
  //   ERole.MemberAdministrator, // 2
  //   ERole.EditEvaluationForms, // 2
  //   ERole.MemberReports,
  //   ERole.ViewEvaluationForms, // 2
  //   ERole.ViewStatusOnly, // 2
  // )
  async postJoinGorup(
    @Req() req: RequestWithUser,
    @Body() body: JoinGroupDto
  ) {
    if (!body.user_group_id) throw new BadRequestException('INVALID_GROUP_ID');
    const groupIsPending = await this.usersService.groupIsExists(req.user.user_id, body.user_group_id);
    if (groupIsPending) throw new BadRequestException('EXISTS_GROUP_ID');
    // TODO: the signature for this function user_group_id: number, user_id: number, added_by_user_id: number
    // In the case of admins/managers calling the function then it needs to pass the users id that they are working with
    await this.usersService.joinGroup(body.user_group_id, req.user.user_id, req.user.user_id);
    return;
  }

  /*
   * @description Groups that the user is approval to join
   * @return A list of user groups
   */
  @Get('pending-groups')
  @HttpCode(HttpStatus.OK)
  // @Roles(
  //   // user_class_id = 1
  //   ERole.SystemDeveloper,
  //   ERole.IPWAdministrator,
  //   ERole.IPWAuditor,
  //   ERole.IPWDataCapturer,
  //   ERole.IPWContentManager,
  //   ERole.IPWLeadAuditor,
  //   ERole.MemberAdministrator, // 2
  //   ERole.EditEvaluationForms, // 2
  //   ERole.MemberReports,
  //   ERole.ViewEvaluationForms, // 2
  //   ERole.ViewStatusOnly, // 2
  // )
  async getPendingGroups(@Req() req: RequestWithUser) {
    const data = await this.usersService.pendingGroups(req.user.user_id);
    return { data };
  }

  /*
   * @description Remove the request to join a user group
   */
  @HttpCode(HttpStatus.NO_CONTENT)
  @Post('cancel-pending-group')
  async cancelPendingGroup(
    @Body() body: CancelJoinGroupDto
  ) {
    if (!body.users_has_group_pending_id) throw new BadRequestException('INVALID_PENDING_GROUP_ID');
    await this.usersService.cancelPendingGroup(body.users_has_group_pending_id);
    return;
  }

  @Get('available-profiles')
  @HttpCode(HttpStatus.OK)
  // @Roles(
  //   ERole.MemberAdministrator,
  //   ERole.EditEvaluationForms,
  //   ERole.ViewEvaluationForms,
  //   ERole.ViewStatusOnly,
  //   ERole.DocumentUploads
  // )
  @UserClasses(EUserClass.IPWMember)
  async availableProfiles(
    @Req() req: RequestWithUser
  ) {
    const data = await this.usersService.getAvailableProfiles(req.user.user_id);
    return { data };
  }

  // @Get('username-exsits')
  // async usernameExists(
  //   @Body() body: UsernameExistsDto
  // ) {
  //   const exists = await this.usersService.usernameExists(body.username);
  //   return { data: { exists } };
  // }

  // @Post('select-profile')
  // @HttpCode(HttpStatus.NO_CONTENT)
  // @Roles(
  //   ERole.MemberAdministrator,
  //   ERole.EditEvaluationForms,
  //   ERole.ViewEvaluationForms,
  //   ERole.ViewStatusOnly,
  //   ERole.DocumentUploads
  // )
  // async selectProfile(
  //   @Req() req: RequestWithUser,
  //   @Body() body: SelectProfileDto
  // ) {
  //   await this.usersService.selectProfile(req.user.user_id, body.user_group_id);
  //   return;
  // }

  // @Get('selected-profile')
  // @HttpCode(HttpStatus.OK)
  // @Roles(
  //   ERole.MemberAdministrator,
  //   ERole.EditEvaluationForms,
  //   ERole.ViewEvaluationForms,
  //   ERole.ViewStatusOnly,
  //   ERole.DocumentUploads
  // )
  // async selectedProfile(
  //   @Req() req: RequestWithUser
  // ) {
  //   const selectedProfile = await this.usersService.selectedProfile(req.user.user_id);
  //   return { user_group_id: selectedProfile };
  // }

  @Put('member')
  @UserClasses(EUserClass.IPWStaff, EUserClass.IPWMember)
  @HttpCode(HttpStatus.CREATED)
  async createMember(
    @Req() req: RequestWithUser,
    @Body() body: CreateUserDto
  ) {
    // TODO: Throw forbidden if the creating user is not a member admin or IPW administrator
    // if user admin then the selected group id must be passed
    const foundByUsername = await this.usersService.findByUsername(body.username);
    if (foundByUsername) throw new BadRequestException('EXISTS_USERNAME');
    return this.usersService.create(req.user.user_id, body, false); // false = trigger user_class_id = 2 / ipw member
  }

  @Delete(':id')
  @Roles(ERole.SystemDeveloper, ERole.IPWAdministrator)
  remove(
    @Param('id') user_id: number
  ) {
    return this.usersService.remove(user_id);
  }

  @Patch(':id')
  // @Roles(
  //   // user_class_id = 1
  //   ERole.SystemDeveloper,
  //   ERole.IPWAdministrator,
  //   ERole.IPWAuditor,
  //   ERole.IPWDataCapturer,
  //   ERole.IPWContentManager,
  //   ERole.IPWLeadAuditor,
  //   ERole.MemberAdministrator, // 2
  //   ERole.EditEvaluationForms, // 2
  //   ERole.MemberReports,
  //   ERole.ViewEvaluationForms, // 2
  //   ERole.ViewStatusOnly, // 2
  // )
  // TODO: this is admin capable, refine it to match user only, check if the user id matches the user that is being edited
  async update(
    @Param('id') user_id: string,
    @Body() body: UpdateUserDto
  ) {
    const userId = typeof user_id !== 'number' ? parseInt(user_id) || null : user_id;
    if (!userId) throw new NotFoundException();
    // Get the user body
    let user = await this.usersService.findById(userId);
    if (!user) throw new NotFoundException();
    // Update the user
    const payload: any = {};
    if (user.title !== body.title) payload.title = body.title;
    if (user.firstname !== body.firstname) payload.firstname = body.firstname;
    if (user.lastname !== body.lastname) payload.lastname = body.lastname;
    if (user.tell !== body.tell) payload.tell = body.tell;
    if (user.email !== body.email) payload.email = body.email;
    if (user.username !== body.username) payload.username = body.username;
    if (Object.keys(payload).length == 0) throw new BadRequestException('NO_CHANGE');
    if (typeof payload.username !== 'undefined') {
      const foundByUsername = await this.usersService.findByUsername(payload.username as string);
      if (foundByUsername) throw new BadRequestException('EXISTS_USERNAME');
    }
    if (typeof payload.email !== 'undefined') {
      const foundByEmail = await this.usersService.findByEmail(payload.email as string);
      if (foundByEmail) throw new BadRequestException('EXISTS_EMAIL');
    }
    // Do the update
    user = await this.usersService.update(userId, payload);
    if (!user) throw new NotFoundException();
    // Update the password
    if (body.password && (body.password as string).length > 0) await this.authService.updatePassword(userId, body.password as string);
    return user || {};
  }

  @Get()
  @Roles(ERole.SystemDeveloper, ERole.IPWAdministrator)
  findAll() {
    return this.usersService.findAll();
  }

  @Get(':id')
  @Roles(ERole.SystemDeveloper, ERole.IPWAdministrator)
  findOne(
    @Param('id') user_id: string
  ) {
    return this.usersService.findById(parseInt(user_id));
  }

  // @Post()
  // @Roles(ERole.MemberAdministrator)
  // async createUser(
  //   @Req() req: RequestWithUser,
  //   @Body() body: CreateUserDto
  // ) {
  //   const foundByUsername = await this.usersService.findByUsername(body.username);
  //   if (foundByUsername) throw new BadRequestException('EXISTS_USERNAME');
  //   const foundByEmail = await this.usersService.findByEmail(body.email);
  //   if (foundByEmail) throw new BadRequestException('EXISTS_EMAIL');
  //   return this.usersService.create(req.user.user_id, body, true); // true = trigger user_class_id = 1 / ipw staff, auditor
  // }
}
