import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
import { InjectModel } from "@nestjs/sequelize";
import { Op } from "sequelize";
import * as _ from "lodash";
import { CreateRoleDto } from "./dto/create-role.dto";
import { RoleQueryDto } from "./dto/role.query.dto";
import { UsersService } from "./users.service";
import { Role } from "src/database/models/role.model";
import { Permission } from "src/database/models/permission.model";
import { UpdateRoleDto } from "./dto/update-role.dto";
import { UpdateRolePermissionDto } from "./dto/update-role-permission.dto";
import { assignPermissionDto } from "./dto/assign-permission.dto";
import { ROLE_ENUM } from "./../../common/constant";
import { error_message } from "src/common/error_message";

@Injectable()
export class RolesService {
  constructor(
    @InjectModel(Role)
    private readonly roleModel: typeof Role,
    @InjectModel(Permission)
    private readonly permissionModel: typeof Permission,
    private readonly usersService: UsersService
  ) { }

  async create(payload: CreateRoleDto): Promise<Role> {
    if (payload.displayName) {
      await this.validateRole(payload)
    }
    return this.roleModel.create({
      name: payload.name,
      displayName: payload.displayName,
      active: payload.active,
    });
  }

  async count(query?: RoleQueryDto): Promise<number> {
    return this.roleModel.unscoped().count({ where: this.createWhere(query) });
  }

  findAll(query?: RoleQueryDto): Promise<Role[]> {
    const { page, limit, order, orderBy } = query;
    return this.roleModel.findAll({
      where: this.createWhere(query),
      limit,
      offset: (page - 1) * limit,
      order: [[orderBy, order]],
    });
  }

  async findOne(id: number): Promise<Role> {
    const data = await this.roleModel.findOne({
      where: {
        id,
      },
    });
    if (!data) throw new NotFoundException(error_message.not_found);
    return data;
  }

  private async validateRole(payload) {
    const whereRole = [];
    if (payload.role) {
      whereRole[0] = {
        id: {[Op.ne]: payload.role.id}
      }
      whereRole[1] = {
        displayName: {[Op.eq]: payload.displayName}
      }
    } else {
      whereRole[0] = {
        displayName: {[Op.eq]: payload.displayName}
      }
    }

    const existRoleByName = await this.roleModel.count({
      where: {
        [Op.and]: whereRole,
      }
    });
    if (existRoleByName) {
      throw new BadRequestException('Nama display role tidak boleh sama.');
    }
  }

  async update(id: number, payload: UpdateRoleDto): Promise<Role> {
    const role = await this.findOne(id);
    if (!role) throw new NotFoundException("Role not found");

    if (payload.displayName != role.displayName) {
      Object.assign(payload, {role: role})
      await this.validateRole(payload)
    }
    await role.update(payload);
    return role;
  }

  async assignPermission(id: number, payload: assignPermissionDto): Promise<Role> {    
    const permissionData = await this.permissionModel.findAll({
      where: {
        id: payload.permissionIds,
        superadmin: false
      }
    }).then((permissions) => {
      const result = [];
      permissions.forEach(async permission => {
        
        result.push({
          permissionId: permission.id,
          roleId: id
        })
      });
      
      return result;
    });

    return await this.findOne(id);
  }

  async findSuperAdmin(): Promise<Role> {
    const superAdmin = await this.roleModel.findOne({where: {name: ROLE_ENUM.SUPERADMIN}})
    return superAdmin;
  }

  async delete(id: number): Promise<void> {
    // user
    const users = await this.usersService.findNoScope({
      where: {
        roleId: id,
      },
    });
    if (users && users.length) {
      throw new NotFoundException("Role telah digunakan");
    }

    const role = await this.findOne(id);
    if (!role) throw new NotFoundException("Role not found");
    await role.destroy();
  }

  private createWhere(conditions) {
    const { page, limit, order, orderBy, ...rest } = conditions; // eslint-disable-line
    const where = _.isEmpty(rest) ? {} : { [Op.and]: { ...rest } };
    return where;
  }
}
