import { EntityManager } from "typeorm";
import { dataSource } from "../data-source";
import { user } from "../entity/user";
import InternalException from "../exceptions/internalException";
import {
  CreateUserCommand,
  DeleteUserCommand,
  TransferUserOwnerCommand,
  UpdateUserCommand,
  UpdateUserRolesCommand,
  UpdateUserSecretCommand,
} from "./userCommand";
import UserService from "../service/userService";

export default abstract class UserCommandHandler {
  /**
   * @description create user
   * @param CreateUserCommand
   * @returns {Promise<number>}
   */
  static async create(createUser: CreateUserCommand): Promise<number> {
    return await dataSource
      .createQueryBuilder()
      .insert()
      .into(user)
      .values({
        username: createUser.username,
        mail: createUser.mail,
        firstname: createUser.firstname,
        lastname: createUser.lastname,
        secret: createUser.secret,
        isOwner: createUser.isOwner,
      })
      .execute()
      .then((result) => {
        return result.identifiers[0].id;
      })
      .catch((err) => {
        throw new InternalException("Failed saving user", err);
      });
  }

  /**
   * @description update user
   * @param UpdateUserCommand
   * @returns {Promise<void>}
   */
  static async update(updateUser: UpdateUserCommand): Promise<void> {
    return await dataSource
      .createQueryBuilder()
      .update(user)
      .set({
        mail: updateUser.mail,
        firstname: updateUser.firstname,
        lastname: updateUser.lastname,
        username: updateUser.username,
      })
      .where("id = :id", { id: updateUser.id })
      .execute()
      .then(() => {})
      .catch((err) => {
        throw new InternalException("Failed updating user", err);
      });
  }

  /**
   * @description update user
   * @param UpdateUserSecretCommand
   * @returns {Promise<void>}
   */
  static async updateSecret(updateUser: UpdateUserSecretCommand): Promise<void> {
    return await dataSource
      .createQueryBuilder()
      .update(user)
      .set({
        secret: updateUser.secret,
      })
      .where("id = :id", { id: updateUser.id })
      .execute()
      .then(() => {})
      .catch((err) => {
        throw new InternalException("Failed updating user secret", err);
      });
  }

  /**
   * @description update user roles
   * @param UpdateUserRolesCommand
   * @returns {Promise<void>}
   */
  static async updateRoles(updateUserRoles: UpdateUserRolesCommand): Promise<void> {
    let currentRoles = (await UserService.getAssignedRolesByUserId(updateUserRoles.id)).map((r) => r.id);
    return await dataSource.manager
      .transaction(async (manager) => {
        let newRoles = updateUserRoles.roleIds.filter((r) => !currentRoles.includes(r));
        let removeRoles = currentRoles.filter((r) => !updateUserRoles.roleIds.includes(r));

        for (let role of newRoles) {
          await this.updateRolesAdd(manager, updateUserRoles.id, role);
        }

        for (let role of removeRoles) {
          await this.updateRolesRemove(manager, updateUserRoles.id, role);
        }
      })
      .then(() => {})
      .catch((err) => {
        throw new InternalException("Failed saving user roles", err);
      });
  }

  private static async updateRolesAdd(manager: EntityManager, userId: number, roleId: number): Promise<void> {
    return await manager.createQueryBuilder().relation(user, "roles").of(userId).add(roleId);
  }

  private static async updateRolesRemove(manager: EntityManager, userId: number, roleId: number): Promise<void> {
    return await manager.createQueryBuilder().relation(user, "roles").of(userId).remove(roleId);
  }

  /**
   * @description transfer ownership
   * @param TransferUserOwnerCommand
   * @returns {Promise<void>}
   */
  static async transferOwnership(transferOwnership: TransferUserOwnerCommand): Promise<void> {
    return await dataSource.manager
      .transaction(async (manager) => {
        manager
          .createQueryBuilder()
          .update(user)
          .set({
            isOwner: false,
          })
          .where("id = :id", { id: transferOwnership.fromId })
          .execute();

        manager
          .createQueryBuilder()
          .update(user)
          .set({
            isOwner: true,
          })
          .where("id = :id", { id: transferOwnership.toId })
          .execute();
      })
      .then(() => {})
      .catch((err) => {
        throw new InternalException("Failed transfering ownership", err);
      });
  }

  /**
   * @description delete user
   * @param DeleteUserCommand
   * @returns {Promise<void>}
   */
  static async delete(deleteUser: DeleteUserCommand): Promise<void> {
    return await dataSource
      .createQueryBuilder()
      .delete()
      .from(user)
      .where("id = :id", { id: deleteUser.id })
      .execute()
      .then(() => {})
      .catch((err) => {
        throw new InternalException("Failed deleting user", err);
      });
  }
}