import { Service, Inject } from 'typedi';
import {
  IVendorCreditEditDTO,
  IVendorCreditEditedPayload,
  IVendorCreditEditingPayload,
} from '@/interfaces';
import BaseVendorCredit from './BaseVendorCredit';
import UnitOfWork from '@/services/UnitOfWork';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import ItemsEntriesService from '@/services/Items/ItemsEntriesService';
import events from '@/subscribers/events';
import HasTenancyService from '@/services/Tenancy/TenancyService';

@Service()
export default class EditVendorCredit extends BaseVendorCredit {
  @Inject()
  private eventPublisher: EventPublisher;

  @Inject()
  private uow: UnitOfWork;

  @Inject()
  private itemsEntriesService: ItemsEntriesService;

  @Inject()
  private tenancy: HasTenancyService;

  /**
   * Deletes the given vendor credit.
   * @param {number} tenantId - Tenant id.
   * @param {number} vendorCreditId - Vendor credit id.
   */
  public editVendorCredit = async (
    tenantId: number,
    vendorCreditId: number,
    vendorCreditDTO: IVendorCreditEditDTO
  ) => {
    const { VendorCredit, Contact } = this.tenancy.models(tenantId);

    // Retrieve the vendor credit or throw not found service error.
    const oldVendorCredit = await this.getVendorCreditOrThrowError(
      tenantId,
      vendorCreditId
    );
    // Validate customer existance.
    const vendor = await Contact.query()
      .modify('vendor')
      .findById(vendorCreditDTO.vendorId)
      .throwIfNotFound();

    // Validate items ids existance.
    await this.itemsEntriesService.validateItemsIdsExistance(
      tenantId,
      vendorCreditDTO.entries
    );
    // Validate non-sellable entries items.
    await this.itemsEntriesService.validateNonSellableEntriesItems(
      tenantId,
      vendorCreditDTO.entries
    );
    // Validate the items entries existance.
    await this.itemsEntriesService.validateEntriesIdsExistance(
      tenantId,
      vendorCreditId,
      'VendorCredit',
      vendorCreditDTO.entries
    );
    // Transformes edit DTO to model storage layer.
    const vendorCreditModel = this.transformCreateEditDTOToModel(
      tenantId,
      vendorCreditDTO,
      vendor.currencyCode,
      oldVendorCredit
    );
    // Edits the vendor credit graph under unit-of-work envirement.
    return this.uow.withTransaction(tenantId, async (trx) => {
      // Triggers `onVendorCreditEditing` event.
      await this.eventPublisher.emitAsync(events.vendorCredit.onEditing, {
        tenantId,
        oldVendorCredit,
        vendorCreditDTO,
        trx,
      } as IVendorCreditEditingPayload);

      // Saves the vendor credit graph to the storage.
      const vendorCredit = await VendorCredit.query(trx).upsertGraphAndFetch({
        id: vendorCreditId,
        ...vendorCreditModel,
      });
      // Triggers `onVendorCreditEdited event.
      await this.eventPublisher.emitAsync(events.vendorCredit.onEdited, {
        tenantId,
        oldVendorCredit,
        vendorCredit,
        vendorCreditId,
        vendorCreditDTO,
        trx,
      } as IVendorCreditEditedPayload);

      return vendorCredit;
    });
  };
}
