import { DeleteResult, EntityManager, InsertResult, UpdateResult } from "typeorm";
import { dataSource } from "../data-source";
import { protocolPresence } from "../entity/protocolPresence";
import InternalException from "../exceptions/internalException";
import ProtocolPresenceService from "../service/protocolPrecenseService";
import { ProtocolPresenceCommand, SynchronizeProtocolPresenceCommand } from "./protocolPresenceCommand";

export default abstract class ProtocolPresenceCommandHandler {
  /**
   * @description sync protocolPresence
   * @param {SynchronizeProtocolPresenceCommand}
   * @returns {Promise<void>}
   */
  static async sync(syncProtocolPresences: SynchronizeProtocolPresenceCommand): Promise<void> {
    let currentPresence = await ProtocolPresenceService.getAll(syncProtocolPresences.protocolId);

    return await dataSource.manager
      .transaction(async (manager) => {
        let newMembers = syncProtocolPresences.members.filter(
          (r) => !currentPresence.some((cp) => cp.memberId == r.memberId)
        );
        let removeMembers = currentPresence.filter(
          (r) => !syncProtocolPresences.members.some((cp) => cp.memberId == r.memberId)
        );
        let keptMembers = syncProtocolPresences.members.filter(
          (m) =>
            currentPresence.some((cd) => cd.memberId == m.memberId) &&
            !removeMembers.some((cd) => cd.memberId == m.memberId)
        );

        if (newMembers.length != 0) {
          await this.syncPresenceAdd(manager, syncProtocolPresences.protocolId, newMembers);
        }

        if (removeMembers.length != 0) {
          await this.syncPresenceRemove(manager, syncProtocolPresences.protocolId, removeMembers);
        }

        for (const member of keptMembers) {
          await this.syncPresenceUpdate(manager, syncProtocolPresences.protocolId, member);
        }
      })
      .then(() => {})
      .catch((err) => {
        throw new InternalException("Failed saving protocol presence", err);
      });
  }

  private static async syncPresenceAdd(
    manager: EntityManager,
    protocolId: number,
    memberIds: Array<ProtocolPresenceCommand>
  ): Promise<InsertResult> {
    return await manager
      .createQueryBuilder()
      .insert()
      .into(protocolPresence)
      .values(
        memberIds.map((m) => ({
          ...m,
          protocolId,
        }))
      )
      .execute();
  }

  private static async syncPresenceUpdate(
    manager: EntityManager,
    protocolId: number,
    member: ProtocolPresenceCommand
  ): Promise<UpdateResult> {
    return await manager
      .createQueryBuilder()
      .update(protocolPresence)
      .set({
        absent: member.absent,
      })
      .where("memberId = :memberId", { memberId: member.memberId })
      .andWhere("protocolId = :protocolId", { protocolId })
      .execute();
  }

  private static async syncPresenceRemove(
    manager: EntityManager,
    protocolId: number,
    members: Array<ProtocolPresenceCommand>
  ): Promise<DeleteResult> {
    return await manager
      .createQueryBuilder()
      .delete()
      .from(protocolPresence)
      .where("memberId IN (:...ids)", { ids: members.map((m) => m.memberId) })
      .andWhere("protocolId = :protocolId", { protocolId })
      .execute();
  }
}