import { ClassError, ObjectBase } from '@tomei/general';
import { RentalHirerChangeRequestRepository } from './rental-hirer-change-request.repository';
import { ApplicationConfig, ComponentConfig } from '@tomei/config';
import { LoginUser } from '@tomei/sso';
import { ActionEnum, Activity } from '@tomei/activity-history';
import { HirerChangeRequestTypeEnum } from '../../enum/rental-hirer-change-request-type';
import { HirerChangeRequestStatusEnum } from '../../enum/rental-hirer-change-request-status';
import {
  IRentalHirerChangeRequestAttr,
  IRentalHirerChangeRequestUpdate,
} from '../../interfaces';
import { Rental } from '../rental/rental';
import {
  HirerChangeRequestHirerRoleEnum,
  RentalAccountTypeEnum,
} from '../../enum';
import { Op } from 'sequelize';
import { JointHirerRepository } from '../../components/joint-hirer/joint-hirer.repository';
import { HirerChangeRequestSignatureRepository } from '../hirer-change-request-signature/hirer-change-request-signature.repository';
import { HirerChangeRequestSignature } from '../hirer-change-request-signature/hirer-change-request-signature';
import { HirerChangeRequestNewHirer } from '../hirer-change-request-new-hirer/hirer-change-request-new-hirer';
import { HirerChangeRequestRemoveHirer } from '../hirer-change-request-remove-hirer/hirer-change-request-remove-hirer';
import { JointHirer } from '../joint-hirer/joint-hirer';

export class RentalHirerChangeRequest
  extends ObjectBase
  implements IRentalHirerChangeRequestAttr
{
  ObjectId: string;
  ObjectName: string;
  ObjectType: string = 'RentalHirerChangeRequest';
  TableName: string = 'rental_HirerChangeRequest';

  RentalId: string;
  Type: HirerChangeRequestTypeEnum;
  Status: HirerChangeRequestStatusEnum;
  RequestedAt: Date;
  RequestedById: string;
  RequestingHirerId: string;
  RequestingHirerType: HirerChangeRequestHirerRoleEnum;
  CancelRemarks: string;
  UpdatedAt: Date;
  UpdatedById: string;

  Signatures: HirerChangeRequestSignature[] = [];
  Rental: Rental | null = null;

  get RequestId(): string {
    return this.ObjectId;
  }

  set RequestId(value: string) {
    this.ObjectId = value;
  }

  protected static _Repository = new RentalHirerChangeRequestRepository();
  protected static _HirerSignatureRepository =
    new HirerChangeRequestSignatureRepository();
  protected static _JointHirerRepository = new JointHirerRepository();

  protected constructor(
    hirerChangeRequestAttr?: IRentalHirerChangeRequestAttr,
  ) {
    super();
    if (hirerChangeRequestAttr) {
      this.RequestId = hirerChangeRequestAttr.RequestId;
      this.RentalId = hirerChangeRequestAttr.RentalId;
      this.Type = hirerChangeRequestAttr.Type;
      this.Status = hirerChangeRequestAttr.Status;
      this.RequestedAt = hirerChangeRequestAttr.RequestedAt;
      this.RequestedById = hirerChangeRequestAttr.RequestedById;
      this.RequestingHirerId = hirerChangeRequestAttr.RequestingHirerId;
      this.RequestingHirerType = hirerChangeRequestAttr.RequestingHirerType;
      this.CancelRemarks = hirerChangeRequestAttr.CancelRemarks;
      this.UpdatedAt = hirerChangeRequestAttr.UpdatedAt;
      this.UpdatedById = hirerChangeRequestAttr.UpdatedById;
    }
  }

  toJSON(): IRentalHirerChangeRequestAttr {
    return {
      RequestId: this.RequestId,
      RentalId: this.RentalId,
      Type: this.Type,
      Status: this.Status,
      RequestedAt: this.RequestedAt,
      RequestedById: this.RequestedById,
      RequestingHirerId: this.RequestingHirerId,
      RequestingHirerType: this.RequestingHirerType,
      CancelRemarks: this.CancelRemarks,
      UpdatedAt: this.UpdatedAt,
      UpdatedById: this.UpdatedById,
    };
  }

  public static async init(requestId?: string, dbTransaction?: any) {
    try {
      if (requestId) {
        const hirerChangeReq =
          await RentalHirerChangeRequest._Repository.findByPk(
            requestId,
            dbTransaction,
          );
        if (hirerChangeReq) {
          return new RentalHirerChangeRequest(
            hirerChangeReq.get({ plain: true }),
          );
        } else {
          throw new ClassError(
            'HirerChangeRequest',
            'HirerChangeRequestErrMsg00',
            'HirerChangeRequest not found',
          );
        }
      }
      return new RentalHirerChangeRequest();
    } catch (error) {
      console.error('Error initializing RentalHirerChangeRequest:', error);
      throw error;
    }
  }

  private async checkForDuplicate(dbTransaction: any) {
    try {
      if (!this.RentalId) {
        throw new ClassError(
          'HirerChangeRequest',
          'HirerChangeRequestErrMsg08',
          'RentalId is required to check for duplicate requests.',
        );
      }

      const isDuplicate = await RentalHirerChangeRequest._Repository.findAll({
        where: {
          RentalId: this.RentalId,
          Type: this.Type,
          Status: {
            [Op.notIn]: [
              HirerChangeRequestStatusEnum.COMPLETED,
              HirerChangeRequestStatusEnum.CANCELLED,
            ],
          },
        },
        transaction: dbTransaction,
      });

      if (isDuplicate.length > 0) {
        throw new ClassError(
          'HirerChangeRequest',
          'HirerChangeRequestErrMsg04',
          'Duplicate record detected.',
        );
      }
    } catch (error) {
      throw error;
    }
  }

  public async getSignatures(dbTransaction: any) {
    try {
      if (this.Signatures.length > 0) {
        return this.Signatures;
      }

      const hirers =
        await RentalHirerChangeRequest._HirerSignatureRepository.findAll({
          where: {
            RequestId: this.RequestId,
          },
          transaction: dbTransaction,
        });

      this.Signatures = await Promise.all(
        hirers.map(async (hirer) => {
          return await HirerChangeRequestSignature.init(
            hirer.SignatureId,
            dbTransaction,
          );
        }),
      );
      return this.Signatures;
    } catch (error) {
      throw error;
    }
  }

  private async checkIsAllSigned(dbTransaction) {
    try {
      const listUpdatedHirer = await this.getSignatures(dbTransaction);
      if (listUpdatedHirer.length > 0) {
        return listUpdatedHirer.every((record) => record.SignedAt !== null);
      } else {
        return false;
      }
    } catch (error) {
      throw error;
    }
  }

  private async create(loginUser: LoginUser, dbTransaction?: any) {
    try {
      // Part 1: Check Privilege
      // 1.1 Make sure user got "HIRER_CHANGE_REQUEST" privilege
      const systemCode =
        ApplicationConfig.getComponentConfigValue('system-code');
      const isPrivileged = await loginUser.checkPrivileges(
        systemCode,
        'HIRER_CHANGE_REQUEST',
      );

      if (!isPrivileged) {
        throw new ClassError(
          'HirerChangeRequest',
          'HirerChangeRequestErrMsg01',
          "You do not have 'HIRER_CHANGE_REQUEST' privilege.",
        );
      }

      // Part 2: Validation Rental
      // 2.1 Make sure this.RentalId and this.RequestingHirerId  got value.
      if (!this.RentalId || !this.RequestingHirerId) {
        throw new ClassError(
          'HirerChangeRequest',
          'HirerChangeRequestErrMsg02',
          'RentalId and RequestingHirerId are required.',
        );
      }
      const rental = await Rental.init(dbTransaction, this.RentalId);

      // 2.2 Only allow for joint account
      if (rental.AccountType !== RentalAccountTypeEnum.JOINT) {
        throw new ClassError(
          'HirerChangeRequest',
          'HirerChangeRequestErrMsg03',
          'Only Rental with Joint Account Type Allowed.',
        );
      }

      // 2.3 only allow for rental account where its joint hirer not maximum yet (at this step you've retrieve the number of joint hirer)
      const jointHirers = await rental.getJointHirers(dbTransaction);

      if (this.Type === HirerChangeRequestTypeEnum.ADD) {
        const maxJointHirer = ComponentConfig.getComponentConfigValue(
          '@tomei/rental',
          'maxJointHirerLength',
        );

        if (jointHirers?.length >= maxJointHirer) {
          throw new ClassError(
            'HirerChangeRequest',
            'HirerChangeRequestErrMsg05',
            'Joint Hirer exceed max joint hirer length.',
          );
        }
      }
      // For Remove type, we don't need to check the max joint hirer length
      // but we need to ensure that there is at least one joint hirer left after removal
      if (
        this.Type == HirerChangeRequestTypeEnum.REMOVE &&
        jointHirers?.length <= 1
      ) {
        throw new ClassError(
          'HirerChangeRequest',
          'HirerChangeRequestErrMsg05',
          'Cannot remove the last joint hirer.',
        );
      }

      // 2.4 avoid duplicate request, call this.checkForDuplicate() private method, this method will ensure no record with same RentalId and same Type and Status is not "Completed" or "Cancelled"
      await this.checkForDuplicate(dbTransaction);

      // Part 3: Create Hirer Change Request and Hirer Change Request Hirer Records
      // 3.1 Set other attributes
      this.ObjectId = this.createId();
      this.RequestedById = loginUser.ObjectId;
      this.RequestedAt = new Date();
      this.UpdatedById = loginUser.ObjectId;
      this.UpdatedAt = new Date();
      this.Status = HirerChangeRequestStatusEnum.PENDINGSIGNATURES;
      this.RequestingHirerType =
        rental.CustomerId === this.RequestingHirerId
          ? HirerChangeRequestHirerRoleEnum.MAIN
          : HirerChangeRequestHirerRoleEnum.JOINT;

      // 3.2 Set Entity value after
      const entityValueAfter: IRentalHirerChangeRequestAttr = {
        RequestId: this.RequestId,
        RentalId: this.RentalId,
        Type: this.Type,
        Status: this.Status,
        RequestedById: this.RequestedById,
        RequestedAt: this.RequestedAt,
        RequestingHirerId: this.RequestingHirerId,
        RequestingHirerType: this.RequestingHirerType,
        CancelRemarks: this.CancelRemarks,
        UpdatedById: this.UpdatedById,
        UpdatedAt: this.UpdatedAt,
      };

      // 3.3 Call repo class create method by passing the class attributes and db transaction.
      await RentalHirerChangeRequest._Repository.create(entityValueAfter, {
        transaction: dbTransaction,
      });

      // Generate signatures, call this.generateSignature by passing:
      await this.generateSignatures(loginUser, dbTransaction);

      //Part 4: Record Create Hirer Change Request Activity
      const activity = new Activity();
      activity.ObjectId = this._createId();
      activity.Action = ActionEnum.CREATE;
      activity.Description =
        this.Type === HirerChangeRequestTypeEnum.ADD
          ? 'Add New Hirer'
          : 'Remove Hirer';
      activity.EntityId = this.ObjectId;
      activity.EntityType = this.ObjectType;
      activity.EntityValueBefore = JSON.stringify({});
      activity.EntityValueAfter = JSON.stringify(entityValueAfter);

      await activity.create(loginUser.ObjectId, dbTransaction);

      return this;
    } catch (error) {
      throw error;
    }
  }

  public async update(
    loginUser: LoginUser,
    dbTransaction: any,
    params: IRentalHirerChangeRequestUpdate,
  ) {
    try {
      // Part 1: Validation
      // 1.1 Check if RequestId is not null if null return error
      if (!this.RequestId) {
        throw new ClassError(
          'HirerChangeRequest',
          'HirerChangeRequestErrMsg06',
          'RequestId are required.',
        );
      }

      // Part 2: Update Hirer Change Request Hirer Records
      // 2.1 Set entityValueBefore
      const entityValueBefore: IRentalHirerChangeRequestAttr = {
        RequestId: this.RequestId,
        RentalId: this.RentalId,
        Type: this.Type,
        Status: this.Status,
        RequestedById: this.RequestedById,
        RequestedAt: this.RequestedAt,
        RequestingHirerId: this.RequestingHirerId,
        RequestingHirerType: this.RequestingHirerType,
        CancelRemarks: this.CancelRemarks,
        UpdatedById: this.UpdatedById,
        UpdatedAt: this.UpdatedAt,
      };

      // 2.2 Check if jointHirerId, customerId is null or not
      let { jointHirerId, customerId } = params;
      if (!customerId && !jointHirerId) {
        throw new ClassError(
          'HirerChangeRequest',
          'HirerChangeRequestErrMsg07',
          'At least one of customerId or jointHirerId must be provided.',
        );
      }

      // 2.3 Get list hirer related to request then find hirer with same CustomerId, if found update the hirer record (make it signed)
      const listHirer = await this.getSignatures(dbTransaction);

      if (listHirer?.length > 0) {
        let foundHirer = listHirer?.find((record) => {
          record.CustomerId === customerId;
        });

        let hirer = await HirerChangeRequestSignature.init(
          foundHirer.SignatureId,
          dbTransaction,
        );

        await hirer.update(loginUser, dbTransaction);
      }

      // 2.4 Check if all hirer in request is signed, if yes check request Type:
      //     Type = "Add" = update Request status to “AwaitingNewHirer”
      //     Type = "Remove" = flag the joint hirer record status to "Inactive" & update Request status to "Completed"
      const isSigned = await this.checkIsAllSigned(dbTransaction);

      if (isSigned) {
        if (this.Type === HirerChangeRequestTypeEnum.ADD) {
          this.Status = HirerChangeRequestStatusEnum.AWAITINGNEWHIRER;
        } else if (this.Type === HirerChangeRequestTypeEnum.REMOVE) {
          // TODO: Currently JointHirer dont have status props

          await RentalHirerChangeRequest._JointHirerRepository.update(
            { Status: 'Inactive' },
            {
              where: {
                HirerId: this.RequestId,
              },
              transaction: dbTransaction,
            },
          );
          this.Status = HirerChangeRequestStatusEnum.COMPLETED;
        }
      }

      // 2.5 Set entityValueAfter
      const entityValueAfter: IRentalHirerChangeRequestAttr = {
        RequestId: this.RequestId,
        RentalId: this.RentalId,
        Type: this.Type,
        Status: this.Status,
        RequestedById: this.RequestedById,
        RequestedAt: this.RequestedAt,
        RequestingHirerId: this.RequestingHirerId,
        RequestingHirerType: this.RequestingHirerType,
        CancelRemarks: this.CancelRemarks,
        UpdatedById: this.UpdatedById,
        UpdatedAt: this.UpdatedAt,
      };

      // 2.6 Call repo class update method by passing the class attributes and db transaction.
      await RentalHirerChangeRequest._Repository.update(entityValueAfter, {
        where: {
          RequestId: this.RequestId,
        },
        transaction: dbTransaction,
      });

      //Part 3: Record Create Hirer Change Request Activity
      const activity = new Activity();
      activity.ObjectId = this._createId();
      activity.Action = ActionEnum.UPDATE;
      activity.Description = 'Update Hirer Change Request';
      activity.EntityId = this.ObjectId;
      activity.EntityType = this.ObjectType;
      activity.EntityValueBefore = JSON.stringify(entityValueBefore);
      activity.EntityValueAfter = JSON.stringify(entityValueAfter);

      await activity.create(loginUser.ObjectId, dbTransaction);

      return this;
    } catch (error) {
      throw error;
    }
  }

  public async generateSignatures(loginUser: LoginUser, dbTransaction?: any) {
    try {
      if (!this.RentalId) {
        throw new ClassError(
          'HirerChangeRequest',
          'HirerChangeRequestErrMsg09',
          'RentalId is required to create signatures.',
        );
      }

      // 1. If this.Rental is null, set it to Rental instantiation using this.RentalId
      if (!this.Rental) {
        this.Rental = await Rental.init(dbTransaction, this.RentalId);
      }

      // 2. Prepare all signature data (main + joint) in one go
      const jointHirers = await this.Rental.getJointHirers(dbTransaction);

      // Main hirer signature
      const mainSignature = await HirerChangeRequestSignature.init();
      mainSignature.RequestId = this.RequestId;
      mainSignature.CustomerId = this.Rental.CustomerId;
      mainSignature.HirerType = HirerChangeRequestHirerRoleEnum.MAIN;
      mainSignature.Method = 'Upload';

      // Joint hirer signatures
      const jointSignaturePromises = jointHirers.map(async (jointHirer) => {
        const signature = await HirerChangeRequestSignature.init();
        signature.RequestId = this.RequestId;
        signature.JointHirerId = jointHirer.HirerId;
        signature.HirerType = HirerChangeRequestHirerRoleEnum.JOINT;
        signature.Method = 'Upload';
        return signature;
      });

      const jointSignatures = await Promise.all(jointSignaturePromises);

      // 3. Create all signatures in parallel
      await Promise.all([
        mainSignature.create(loginUser, dbTransaction),
        ...jointSignatures.map((sig) => sig.create(loginUser, dbTransaction)),
      ]);

      this.Signatures = [mainSignature, ...jointSignatures];
    } catch (error) {
      console.error('Error creating signatures:', error);
      throw error;
    }
  }

  public async createAddRequest(
    newHirer: {
      FullName: string;
      IdNo: string;
      IdType: string;
      Email?: string;
      ContactNo: string;
      Relationship: string;
      Address: string;
      City: string;
      State: string;
      Postcode: string;
      Country: string;
    },
    loginUser: LoginUser,
    dbTransaction?: any,
  ): Promise<{
    hirerChangeRequest: RentalHirerChangeRequest;
    NewHirer: HirerChangeRequestNewHirer;
  }> {
    try {
      // Validations
      // 1. Make sure newHirer required fields cannot be null.
      if (
        !newHirer.FullName ||
        !newHirer.IdNo ||
        !newHirer.IdType ||
        !newHirer.ContactNo ||
        !newHirer.Address ||
        !newHirer.City ||
        !newHirer.State ||
        !newHirer.Postcode ||
        !newHirer.Country
      ) {
        throw new ClassError(
          'HirerChangeRequest',
          'HirerChangeRequestErrMsg10',
          'FullName, IdNo, IdType, ContactNo, and Address are required.',
        );
      }

      // 2. Set this.Type to "Add".
      this.Type = HirerChangeRequestTypeEnum.ADD;

      // 3. Call this.create by passing:
      // loginUser, dbTransaction
      await this.create(loginUser, dbTransaction);

      // 4. Insert into hirerChangeRequest_NewHirer
      const nh = await HirerChangeRequestNewHirer.init();
      nh.RequestId = this.RequestId;
      nh.FullName = newHirer.FullName;
      nh.Email = newHirer.Email;
      nh.IdType = newHirer.IdType;
      nh.IdNo = newHirer.IdNo;
      nh.ContactNo = newHirer.ContactNo;
      nh.Relationship = newHirer.Relationship;
      nh.Address = newHirer.Address;
      nh.City = newHirer.City;
      nh.State = newHirer.State;
      nh.Postcode = newHirer.Postcode;
      nh.Country = newHirer.Country;

      await nh.create(loginUser, dbTransaction);

      // 5. Return HirerChangeRequest instance and NewHirer instance.
      return {
        hirerChangeRequest: this,
        NewHirer: nh,
      };
    } catch (error) {
      console.error('Error creating add request:', error);
      throw error;
    }
  }

  public async createRemoveRequest(
    loginUser: LoginUser,
    dbTransaction: any,
    targetHirerId: string,
  ): Promise<{
    hirerChangeRequest: RentalHirerChangeRequest;
    RemoveHirer: HirerChangeRequestRemoveHirer;
  }> {
    try {
      // 1. Make sure targetHirerId can be instantiated into JointHirer class (existing joint hirer)
      await JointHirer.init(targetHirerId, dbTransaction);

      // 2. Set this.Type to "Remove"
      this.Type = HirerChangeRequestTypeEnum.REMOVE;

      // 3. Call this.create() method by passing loginUser, dbTransaction
      await this.create(loginUser, dbTransaction);

      // 4. Insert into hirerChangeRequest_RemoveHirer
      const removeHirer = await HirerChangeRequestRemoveHirer.init();
      removeHirer.RequestId = this.RequestId;
      removeHirer.TargetHirerId = targetHirerId;

      await removeHirer.create(loginUser, dbTransaction);

      // 5. Return the returned HirerChangeRequest instance and RemoveHirer instance.
      return {
        hirerChangeRequest: this,
        RemoveHirer: removeHirer,
      };
    } catch (error) {
      console.error('Error creating remove request:', error);
      throw error;
    }
  }

  public static async findAll(
    loginUser: LoginUser,
    dbTransaction: any,
    rentalId: string,
  ): Promise<RentalHirerChangeRequest[]> {
    try {
      // 1. Make sure user got "HIRER_CHANGE_REQUEST" privilege.
      const systemCode =
        ApplicationConfig.getComponentConfigValue('system-code');
      const isPrivileged = await loginUser.checkPrivileges(
        systemCode,
        'HIRER_CHANGE_REQUEST',
      );
      if (!isPrivileged) {
        throw new ClassError(
          'HirerChangeRequest',
          'HirerChangeRequestErrMsg01',
          "You do not have 'HIRER_CHANGE_REQUEST' privilege.",
        );
      }

      // 2. Make sure rentalId not null.
      if (!rentalId) {
        throw new ClassError(
          'HirerChangeRequest',
          'HirerChangeRequestErrMsg12',
          'rentalId is required.',
        );
      }

      // 3. Instantiate rental (to check rental id valid)
      await Rental.init(dbTransaction, rentalId);

      // 4. Call Repo.findAll({ where: { RentalId: rentalId }, transaction: dbTransaction })
      const records = await RentalHirerChangeRequest._Repository.findAll({
        where: { RentalId: rentalId },
        transaction: dbTransaction,
      });

      // 5. Instantiate the returned items
      const result = (records || []).map(
        (rec) => new RentalHirerChangeRequest(rec.get({ plain: true })),
      );

      // 6. Return the instance array
      return result;
    } catch (error) {
      console.error('Error finding all RentalHirerChangeRequest:', error);
      throw error;
    }
  }

  public async getNewHirer(
    dbTransaction: any,
  ): Promise<HirerChangeRequestNewHirer> {
    try {
      // 1. Make sure this.RequestId got value.
      if (!this.RequestId) {
        throw new ClassError(
          'HirerChangeRequest',
          'HirerChangeRequestErrMsg13',
          'RequestId is required to get new hirer.',
        );
      }

      // 2. Call HirerChangeRequestNewHirer.findAll()
      const newHirerRecords = await HirerChangeRequestNewHirer.findAll(
        {
          RequestId: this.RequestId,
        },
        dbTransaction,
      );

      // 3. Return the newHirer instance
      return newHirerRecords[0] || null;
    } catch (error) {
      console.error('Error getting new hirer:', error);
      throw error;
    }
  }

  public async getRemoveHirer(
    dbTransaction: any,
  ): Promise<HirerChangeRequestRemoveHirer> {
    try {
      // 1. Make sure this.RequestId got value.
      if (!this.RequestId) {
        throw new ClassError(
          'HirerChangeRequest',
          'HirerChangeRequestErrMsg13',
          'RequestId is required to get remove hirer.',
        );
      }

      // 2. Call HirerChangeRequestRemoveHirer.findAll()
      const removeHirerRecords = await HirerChangeRequestRemoveHirer.findAll(
        {
          RequestId: this.RequestId,
        },
        dbTransaction,
      );

      // 3. Return the removeHirer instance
      return removeHirerRecords[0] || null;
    } catch (error) {
      console.error('Error getting remove hirer:', error);
      throw error;
    }
  }

  public async sign(
    loginUser: LoginUser,
    dbTransaction: any,
    signatureId: string,
  ): Promise<HirerChangeRequestSignature> {
    try {
      // 1. Make sure this.RequestId got value.
      if (!this.RequestId) {
        throw new ClassError(
          'HirerChangeRequest',
          'HirerChangeRequestErrMsg13',
          'RequestId is required to sign signature.',
        );
      }

      // Validate signatureId
      const hcrSignature = await HirerChangeRequestSignature.init(
        signatureId,
        dbTransaction,
      );

      //Call hcrSignature.markSigned() method by passing loginUser and dbTransaction
      await hcrSignature.markSigned(loginUser, dbTransaction);

      // const isSigned = await this.checkIsAllSigned(dbTransaction);

      // if (isSigned && this.Type === HirerChangeRequestTypeEnum.ADD) {
      //   this.Status = HirerChangeRequestStatusEnum.AWAITINGNEWHIRER;
      //   this.UpdatedAt = new Date();
      //   this.UpdatedById = loginUser.ObjectId;
      //   await RentalHirerChangeRequest._Repository.update(this.toJSON(), {
      //     where: { RequestId: this.RequestId },
      //     transaction: dbTransaction,
      //   });
      // }
      return hcrSignature;
    } catch (error) {
      throw error;
    }
  }

  public async complete(
    loginUser: LoginUser,
    dbTransaction: any,
    customerId?: string,
    customerType?: string,
  ): Promise<RentalHirerChangeRequest> {
    // 1. Validations
    // a. Make sure user got "HIRER_CHANGE_REQUEST" privilege.
    const systemCode = ApplicationConfig.getComponentConfigValue('system-code');
    const isPrivileged = await loginUser.checkPrivileges(
      systemCode,
      'HIRER_CHANGE_REQUEST',
    );
    if (!isPrivileged) {
      throw new ClassError(
        'HirerChangeRequest',
        'HirerChangeRequestErrMsg01',
        "You do not have 'HIRER_CHANGE_REQUEST' privilege.",
      );
    }

    // b. Make sure this.RequestId not null.
    if (!this.RequestId) {
      throw new ClassError(
        'HirerChangeRequest',
        'HirerChangeRequestErrMsg06',
        'RequestId is required.',
      );
    }

    // c. Make sure all signatures are signed.
    const signatures = await this.getSignatures(dbTransaction);
    if (!signatures.length || !signatures.every((sig) => sig.SignedAt)) {
      throw new ClassError(
        'HirerChangeRequest',
        'HirerChangeRequestErrMsg14',
        'All signatures must be signed before completing the request.',
      );
    }

    // 2. Create/Remove Hirer
    if (this.Type === HirerChangeRequestTypeEnum.ADD) {
      //Make sure CustomerId is set, if not throw error
      if (!customerId || !customerType) {
        throw new ClassError(
          'HirerChangeRequest',
          'HirerChangeRequestErrMsg15',
          'CustomerId and CustomerType are required for new hirer.',
        );
      }
      // c. Create joint hirer using JointHirer.create()
      const jointHirer = await JointHirer.init();
      jointHirer.RentalId = this.RentalId;
      jointHirer.CustomerId = customerId;
      jointHirer.CustomerType = customerType;
      await jointHirer.create(loginUser, dbTransaction);
    } else if (this.Type === HirerChangeRequestTypeEnum.REMOVE) {
      // a. Get joint hirer from remove hirer request
      const removeHirer = await this.getRemoveHirer(dbTransaction);
      const jointHirer = await JointHirer.init(
        removeHirer.TargetHirerId,
        dbTransaction,
      );

      //Call jointHirer.remove() method by passing loginUser and dbTransaction
      await jointHirer.remove(loginUser, dbTransaction);
    }

    // 3. Call _Repo update method
    const now = new Date();
    const entityValueBefore: IRentalHirerChangeRequestAttr = this.toJSON();
    this.Status = HirerChangeRequestStatusEnum.COMPLETED;
    this.UpdatedAt = new Date();
    this.UpdatedById = loginUser.ObjectId;
    const entityValueAfter: IRentalHirerChangeRequestAttr = this.toJSON();
    await RentalHirerChangeRequest._Repository.update(entityValueAfter, {
      where: { RequestId: this.RequestId },
      transaction: dbTransaction,
    });

    // 4. Record activity history
    const activity = new Activity();
    activity.ObjectId = this._createId();
    activity.Action = ActionEnum.UPDATE;
    activity.Description = `Mark request as completed.`;
    activity.EntityId = this.RequestId;
    activity.EntityType = this.ObjectType;
    activity.EntityValueBefore = JSON.stringify(entityValueBefore);
    activity.EntityValueAfter = JSON.stringify(entityValueAfter);

    await activity.create(loginUser.ObjectId, dbTransaction);

    // 5. Return updated instance
    return this;
  }

  public async cancel(
    loginUser: LoginUser,
    dbTransaction: any,
    cancelRemarks: string,
  ): Promise<RentalHirerChangeRequest> {
    // 1. Make sure user got "HIRER_CHANGE_REQUEST" privilege.
    const systemCode = ApplicationConfig.getComponentConfigValue('system-code');
    const isPrivileged = await loginUser.checkPrivileges(
      systemCode,
      'HIRER_CHANGE_REQUEST',
    );
    if (!isPrivileged) {
      throw new ClassError(
        'HirerChangeRequest',
        'HirerChangeRequestErrMsg01',
        "You do not have 'HIRER_CHANGE_REQUEST' privilege.",
      );
    }

    // 2. Make sure this.RequestId not null.
    if (!this.RequestId) {
      throw new ClassError(
        'HirerChangeRequest',
        'HirerChangeRequestErrMsg06',
        'RequestId is required.',
      );
    }

    // 3. Call _Repo.update() method
    const entityValueBefore: IRentalHirerChangeRequestAttr = this.toJSON();
    this.Status = HirerChangeRequestStatusEnum.CANCELLED;
    this.CancelRemarks = cancelRemarks;
    this.UpdatedAt = new Date();
    this.UpdatedById = loginUser.ObjectId;
    const entityValueAfter: IRentalHirerChangeRequestAttr = this.toJSON();

    await RentalHirerChangeRequest._Repository.update(entityValueAfter, {
      where: { RequestId: this.RequestId },
      transaction: dbTransaction,
    });

    // 4. Record activity history
    const activity = new Activity();
    activity.ObjectId = this._createId();
    activity.Action = ActionEnum.UPDATE;
    activity.Description = `Cancel hirer change request.`;
    activity.EntityId = this.RequestId;
    activity.EntityType = this.ObjectType;
    activity.EntityValueBefore = JSON.stringify(entityValueBefore);
    activity.EntityValueAfter = JSON.stringify(entityValueAfter);

    await activity.create(loginUser.ObjectId, dbTransaction);

    // 5. Return updated instance
    return this;
  }
}
