import { DeleteResult, EntityManager, InsertResult } from "typeorm";
import { dataSource } from "../data-source";
import { user } from "../entity/user";
import { userPermission } from "../entity/user_permission";
import InternalException from "../exceptions/internalException";
import {
  CreateUserPermissionCommand,
  DeleteUserPermissionCommand,
  UpdateUserPermissionsCommand,
} from "./userPermissionCommand";
import UserPermissionService from "../service/userPermissionService";
import PermissionHelper from "../helpers/permissionHelper";
import { PermissionString } from "../type/permissionTypes";

export default abstract class UserPermissionCommandHandler {
  /**
   * @description update user permissions
   * @param UpdateUserPermissionsCommand
   * @returns {Promise<void>}
   */
  static async updatePermissions(updateUserPermissions: UpdateUserPermissionsCommand): Promise<void> {
    let currentPermissions = (await UserPermissionService.getByUser(updateUserPermissions.userId)).map(
      (r) => r.permission
    );
    return await dataSource.manager
      .transaction(async (manager) => {
        let newPermissions = PermissionHelper.getWhatToAdd(currentPermissions, updateUserPermissions.permissions);
        let removePermissions = PermissionHelper.getWhatToRemove(currentPermissions, updateUserPermissions.permissions);

        if (newPermissions.length != 0) {
          await this.updatePermissionsAdd(manager, updateUserPermissions.userId, newPermissions);
        }
        if (removePermissions.length != 0) {
          await this.updatePermissionsRemove(manager, updateUserPermissions.userId, removePermissions);
        }
      })
      .then(() => {})
      .catch((err) => {
        throw new InternalException("Failed saving user permissions", err);
      });
  }

  private static async updatePermissionsAdd(
    manager: EntityManager,
    userId: number,
    permissions: Array<PermissionString>
  ): Promise<InsertResult> {
    return await manager
      .createQueryBuilder()
      .insert()
      .into(userPermission)
      .values(
        permissions.map((p) => ({
          permission: p,
          userId: userId,
        }))
      )
      .orIgnore()
      .execute();
  }

  private static async updatePermissionsRemove(
    manager: EntityManager,
    userId: number,
    permissions: Array<PermissionString>
  ): Promise<DeleteResult> {
    return await manager
      .createQueryBuilder()
      .delete()
      .from(userPermission)
      .where("userId = :id", { id: userId })
      .andWhere("permission IN (:...permission)", { permission: permissions })
      .execute();
  }

  /**
   * @description grant permission to user
   * @param CreateUserPermissionCommand
   * @returns {Promise<number>}
   */
  static async create(createPermission: CreateUserPermissionCommand): Promise<number> {
    return await dataSource
      .createQueryBuilder()
      .insert()
      .into(userPermission)
      .values({
        permission: createPermission.permission,
        userId: createPermission.userId,
      })
      .execute()
      .then((result) => {
        return result.identifiers[0].id;
      })
      .catch((err) => {
        throw new InternalException("Failed saving user permission", err);
      });
  }

  /**
   * @description remove permission to user
   * @param DeleteUserPermissionCommand
   * @returns {Promise<any>}
   */
  static async delete(deletePermission: DeleteUserPermissionCommand): Promise<any> {
    return await dataSource
      .createQueryBuilder()
      .delete()
      .from(userPermission)
      .where("userId = :id", { id: deletePermission.userId })
      .andWhere("permission = :permission", { permission: deletePermission.permission })
      .execute()
      .then((res) => {})
      .catch((err) => {
        throw new InternalException("failed user permission removal", err);
      });
  }
}