import { BadRequestException, Body, Controller,
         Delete, Get, HttpCode, HttpStatus, NotFoundException, Param, Patch, Post, Put, Req, Res } from '@nestjs/common';
import { Roles } from 'src/roles/roles.decorator';
import { ERole, RolesService } from 'src/roles/roles.service';
import { CreateUserDto } from 'src/users/dto/create-user.dto';
import { EUserClass, UsersService } from 'src/users/users.service';
import { AddUserDto } from 'src/roles/dto/add-user.dto';
import { RemoveUserDto } from 'src/roles/dto/remove-user.dto';
import RequestWithUser from 'src/common/interfaces/requestWithUser.interface';
import { AdminService } from './admin.service';
import { UserClasses } from 'src/users/users.decorator';
import { AllMembersDto } from './dto/all-members.dto';
import { UpdateUserDto } from 'src/users/dto/update-user.dto';
import { AuthService } from 'src/auth/auth.service';
import { GroupsAndRolesDto } from './dto/groups-and-role.dto';
import { AvailableGroupsDto } from './dto/available-groups.dto';
import { AddMemberRoleDto } from './dto/add-member-role.dto';
import { RemoveGroupMembershipDto } from './dto/remove-group-membership.dto';
import { AddGroupMembershipDto } from './dto/add-group-membership.dto';
import { AllGroupsDto } from './dto/all-groups.dto';
import { GroupService } from 'src/group/group.service';
import { DeleteGroupDto } from './dto/delete-group-dto';

@Roles(ERole.IPWAdministrator)
@UserClasses(EUserClass.IPWStaff)
@Controller('admin')
export class AdminController {
  constructor(
    private readonly authService: AuthService,
    private readonly adminService: AdminService,
    private readonly rolesService: RolesService,
    private readonly usersService: UsersService,
    private readonly groupService: GroupService
  ) {}

  /*
   * @description List all administrative users of IPW
   * @return A list of administrative users
   */
  @Get('all-users')
  @HttpCode(HttpStatus.OK)
  async getAllAffiliates() {
    const data = await this.adminService.getAllUsers();
    return { data };
  }

  /*
   * @description Get profile data for an administrative user
   * @return An object containing the users profile data
   */
  @Get('get-user/:id')
  @HttpCode(HttpStatus.OK)
  findOne(
    @Param('id') user_id: string
  ) {
    return this.usersService.findById(parseInt(user_id));
  }

  /*
   * @description Create a new administrative user
   * @return An object containing the users profile data
   */
  @Put('create-user')
  @HttpCode(HttpStatus.CREATED)
  async createAffiliate(
    @Req() req: RequestWithUser,
    @Body() body: CreateUserDto
  ) {
    const foundByUsername = await this.usersService.findByUsername(body.username);
    if (foundByUsername) throw new BadRequestException('EXISTS_USERNAME');
    return this.usersService.create(req.user.user_id, body, true); // true = trigger user_class_id = 1 / ipw staff, auditor
  }

  /*
   * @description Update an administrative user
   * @return An object containing the users profile data
   */
  @Patch('update-user/:id')
  @HttpCode(HttpStatus.OK)
  async update(
    @Param('id') user_id: string,
    @Body() body: UpdateUserDto
  ) {

    const userId = parseInt(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');
    }
    // Handle un/suspension

    // 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('member-activity')
  @HttpCode(HttpStatus.OK)
  async getMemberActivity() {
    const data = await this.adminService.getMemberActivity();
    return { data };
  }

  @Get('live-users')
  @HttpCode(HttpStatus.OK)
  async getLiveUsers() {
    const data = await this.adminService.getLiveUsers();
    return { data };
  }

  @Post('role')
  @HttpCode(HttpStatus.CREATED)
  async addUserRole(
    @Body() body: AddUserDto
  ) {
    if (!body.role_id || !body.user_id) throw new BadRequestException();
    await this.rolesService.addRoleToGroupMember(body.role_id, this.usersService.ipwGroupID, body.user_id);
    return;
  }

  @Delete('role')
  @HttpCode(HttpStatus.NO_CONTENT)
  async removeUserRole(
    @Body() body: RemoveUserDto
  ) {
    if (!body.role_id || !body.user_id) throw new BadRequestException();
    await this.rolesService.removeRoleFromGroupMember(body.role_id, this.usersService.ipwGroupID, body.user_id);
    return;
  }

  /*
   * @description List all (non-administrative) users who hold IPW membership
   * @return A list of all available IPW members
   */
  @Post('all-members')
  @HttpCode(HttpStatus.OK)
  async getAllMembers(
    @Body() body: AllMembersDto
  ) {
    const data = await this.adminService.getAllMembers(body.filter, body.search);
    return { data };
  }

  /*
   * @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
   */
  @Post('groups-and-roles')
  @HttpCode(HttpStatus.OK)
  async getGroupsAndRoles(
    @Body() body: GroupsAndRolesDto
  ) {
    const data = await this.usersService.groupsAndRoles(body.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) {
          group.role_id = null;
          continue;
        }
        const associatedRole = roleIds.find((r: any) => r.users_has_group_id === group.users_has_group_id);
        if (!associatedRole) {
          group.role_id = null;
          continue;
        }
        group.role_id = associatedRole.roles_id;
      }
    }
    return { data };
  }

  /*
   * @description Groups that the user has not already joined or awaiting approval to join
   * @return A list of user groups
   */
  @Post('available-groups')
  @HttpCode(HttpStatus.OK)
  async getAvailableGroups(
    @Body() body: AvailableGroupsDto
  ) {
    const data = await this.usersService.availableGroups(body.user_id);
    return { data };
  }

  /*
   * @description Add or update the members role assignment for a specific group
   */
  @Post('add-member-role')
  @HttpCode(HttpStatus.NO_CONTENT)
  async addMemberRole(
    @Body() body: AddMemberRoleDto
  ) {
    // Validate roles
    if (![14,10,11,16].includes(body.role_id)) throw new BadRequestException();
    await this.rolesService.updateRoleForGroupMember(body.role_id, body.user_group_id, body.user_id)
    return;
  }

  /*
   * @description Remove a member from a group
   */
  @Post('remove-group-membership')
  @HttpCode(HttpStatus.NO_CONTENT)
  async removeGroupMembership(
    @Body() body: RemoveGroupMembershipDto
  ) {
    await this.adminService.removeGroupMembership(body.user_has_group_id);
    return;
  }

  /*
   * @description Add a member to a group
   */
  @Post('add-group-membership')
  @HttpCode(HttpStatus.NO_CONTENT)
  async addGroupMembership(
    @Body() body: AddGroupMembershipDto
  ) {
    await this.adminService.addGroupMembership(body.user_id, body.user_group_id);
    return;
  }

  /*
   * @description List all member groups
   * @return A list of all available groups
   */
  @Post('all-groups')
  @HttpCode(HttpStatus.OK)
  async getAllGroups(
    @Body() body: AllGroupsDto
  ) {
    const data = await this.groupService.findByQuery(body.filter, body.search);
    return { data };
  }

  /*
   * @description List all member groups
   * @return A list of all available groups
   */
  @Delete('group')
  @HttpCode(HttpStatus.NO_CONTENT)
  async deleteGroup(
    @Body() body: DeleteGroupDto
  ) {
    const countOfExistingEvalFormEntries = await this.groupService.countEvailFormEntries(body.user_group_id);
    if (countOfExistingEvalFormEntries > 0) throw new BadRequestException('EXISTS_EVAL_FORM_ENTRIES');
    await this.groupService.remove(body.user_group_id);
    return;
  }

}