import {
  ICreditNoteEditDTO,
  ICreditNoteEditedPayload,
  ICreditNoteEditingPayload,
} from '@/interfaces';
import { Knex } from 'knex';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import ItemsEntriesService from '@/services/Items/ItemsEntriesService';
import UnitOfWork from '@/services/UnitOfWork';
import events from '@/subscribers/events';
import { Inject, Service } from 'typedi';
import BaseCreditNotes from './CreditNotes';

@Service()
export default class EditCreditNote extends BaseCreditNotes {
  @Inject()
  itemsEntriesService: ItemsEntriesService;

  @Inject()
  eventPublisher: EventPublisher;

  @Inject()
  uow: UnitOfWork;

  /**
   * Edits the given credit note.
   * @param {number} tenantId -
   * @param {ICreditNoteEditDTO} creditNoteEditDTO -
   */
  public editCreditNote = async (
    tenantId: number,
    creditNoteId: number,
    creditNoteEditDTO: ICreditNoteEditDTO
  ) => {
    const { CreditNote, Contact } = this.tenancy.models(tenantId);

    // Retrieve the sale invoice or throw not found service error.
    const oldCreditNote = await this.getCreditNoteOrThrowError(
      tenantId,
      creditNoteId
    );
    // Validate customer existance.
    const customer = await Contact.query()
      .modify('customer')
      .findById(creditNoteEditDTO.customerId)
      .throwIfNotFound();

    // Validate items ids existance.
    await this.itemsEntriesService.validateItemsIdsExistance(
      tenantId,
      creditNoteEditDTO.entries
    );
    // Validate non-sellable entries items.
    await this.itemsEntriesService.validateNonSellableEntriesItems(
      tenantId,
      creditNoteEditDTO.entries
    );
    // Validate the items entries existance.
    await this.itemsEntriesService.validateEntriesIdsExistance(
      tenantId,
      creditNoteId,
      'CreditNote',
      creditNoteEditDTO.entries
    );
    // Transformes the given DTO to storage layer data.
    const creditNoteModel = await this.transformCreateEditDTOToModel(
      tenantId,
      creditNoteEditDTO,
      customer.currencyCode,
      oldCreditNote
    );
    // Sales the credit note transactions with associated entries.
    return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
      // Triggers `onCreditNoteEditing` event.
      await this.eventPublisher.emitAsync(events.creditNote.onEditing, {
        creditNoteEditDTO,
        oldCreditNote,
        trx,
        tenantId,
      } as ICreditNoteEditingPayload);

      // Saves the credit note graph to the storage.
      const creditNote = await CreditNote.query(trx).upsertGraphAndFetch({
        id: creditNoteId,
        ...creditNoteModel,
      });
      // Triggers `onCreditNoteEdited` event.
      await this.eventPublisher.emitAsync(events.creditNote.onEdited, {
        trx,
        oldCreditNote,
        creditNoteId,
        creditNote,
        creditNoteEditDTO,
        tenantId,
      } as ICreditNoteEditedPayload);

      return creditNote;
    });
  };
}
