import { Inject, Service } from 'typedi';
import { Knex } from 'knex';
import { AccountNormal, ILedgerEntry, IRefundCreditNote } from '@/interfaces';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import LedgerStorageService from '@/services/Accounting/LedgerStorageService';
import Ledger from '@/services/Accounting/Ledger';

@Service()
export default class RefundCreditNoteGLEntries {
  @Inject()
  ledgerStorage: LedgerStorageService;

  @Inject()
  tenancy: HasTenancyService;

  /**
   * Retrieves the refund credit common GL entry.
   * @param {IRefundCreditNote} refundCreditNote
   * @returns
   */
  private getRefundCreditCommonGLEntry = (
    refundCreditNote: IRefundCreditNote
  ) => {
    return {
      currencyCode: refundCreditNote.currencyCode,
      exchangeRate: refundCreditNote.exchangeRate,

      transactionType: 'RefundCreditNote',
      transactionId: refundCreditNote.id,
      date: refundCreditNote.date,
      userId: refundCreditNote.userId,

      referenceNumber: refundCreditNote.referenceNo,

      createdAt: refundCreditNote.createdAt,
      indexGroup: 10,

      credit: 0,
      debit: 0,

      note: refundCreditNote.description,
      branchId: refundCreditNote.branchId,
    };
  };

  /**
   * Retrieves the refudn credit receivable GL entry.
   * @param   {IRefundCreditNote} refundCreditNote
   * @param   {number} ARAccountId
   * @returns {ILedgerEntry}
   */
  private getRefundCreditGLReceivableEntry = (
    refundCreditNote: IRefundCreditNote,
    ARAccountId: number
  ): ILedgerEntry => {
    const commonEntry = this.getRefundCreditCommonGLEntry(refundCreditNote);

    return {
      ...commonEntry,
      debit: refundCreditNote.amount,
      accountId: ARAccountId,
      contactId: refundCreditNote.creditNote.customerId,
      index: 1,
      accountNormal: AccountNormal.DEBIT,
    };
  };

  /**
   * Retrieves the refund credit withdrawal GL entry.
   * @param   {number} refundCreditNote
   * @returns {ILedgerEntry}
   */
  private getRefundCreditGLWithdrawalEntry = (
    refundCreditNote: IRefundCreditNote
  ): ILedgerEntry => {
    const commonEntry = this.getRefundCreditCommonGLEntry(refundCreditNote);

    return {
      ...commonEntry,
      credit: refundCreditNote.amount,
      accountId: refundCreditNote.fromAccountId,
      index: 2,
      accountNormal: AccountNormal.DEBIT,
    };
  };

  /**
   * Retrieve the refund credit note GL entries.
   * @param {IRefundCreditNote} refundCreditNote
   * @param {number} receivableAccount
   * @returns {ILedgerEntry[]}
   */
  public getRefundCreditGLEntries(
    refundCreditNote: IRefundCreditNote,
    ARAccountId: number
  ): ILedgerEntry[] {
    const receivableEntry = this.getRefundCreditGLReceivableEntry(
      refundCreditNote,
      ARAccountId
    );
    const withdrawalEntry =
      this.getRefundCreditGLWithdrawalEntry(refundCreditNote);

    return [receivableEntry, withdrawalEntry];
  }

  /**
   * Creates refund credit GL entries.
   * @param {number} tenantId
   * @param {IRefundCreditNote} refundCreditNote
   * @param {Knex.Transaction} trx
   */
  public createRefundCreditGLEntries = async (
    tenantId: number,
    refundCreditNoteId: number,
    trx?: Knex.Transaction
  ) => {
    const { Account, RefundCreditNote } = this.tenancy.models(tenantId);

    // Retrieve the refund with associated credit note.
    const refundCreditNote = await RefundCreditNote.query(trx)
      .findById(refundCreditNoteId)
      .withGraphFetched('creditNote');

    // Receivable account A/R.
    const receivableAccount = await Account.query().findOne(
      'slug',
      'accounts-receivable'
    );
    // Retrieve refund credit GL entries.
    const refundGLEntries = this.getRefundCreditGLEntries(
      refundCreditNote,
      receivableAccount.id
    );
    const ledger = new Ledger(refundGLEntries);

    // Saves refund ledger entries.
    await this.ledgerStorage.commit(tenantId, ledger, trx);
  };

  /**
   * Reverts refund credit note GL entries.
   * @param {number} tenantId
   * @param {number} refundCreditNoteId
   * @param {number} receivableAccount
   * @param {Knex.Transaction} trx
   */
  public revertRefundCreditGLEntries = async (
    tenantId: number,
    refundCreditNoteId: number,
    trx?: Knex.Transaction
  ) => {
    await this.ledgerStorage.deleteByReference(
      tenantId,
      refundCreditNoteId,
      'RefundCreditNote',
      trx
    );
  };
}
