// @ts-nocheck
import * as R from 'ramda';
import * as moment from 'moment';
import { first, isEmpty } from 'lodash';
import {
  ICashflowAccountTransaction,
  ICashflowAccountTransactionsQuery,
} from '../../types/BankingTransactions.types';
import { BankTransactionStatus } from './_constants';
import { FinancialSheet } from '@/modules/FinancialStatements/common/FinancialSheet';
import { formatBankTransactionsStatus } from './_utils';
import { GetBankAccountTransactionsRepository } from './GetBankAccountTransactionsRepo.service';
import { runningBalance } from '@/utils/running-balance';
import { I18nService } from 'nestjs-i18n';

export class GetBankAccountTransactions extends FinancialSheet {
  private runningBalance: any;
  private query: ICashflowAccountTransactionsQuery;
  private repo: GetBankAccountTransactionsRepository;
  private i18n: I18nService;

  /**
   * Constructor method.
   * @param {IAccountTransaction[]} transactions -
   * @param {number} openingBalance -
   * @param {ICashflowAccountTransactionsQuery} query -
   */
  constructor(
    repo: GetBankAccountTransactionsRepository,
    query: ICashflowAccountTransactionsQuery,
    i18n: I18nService,
  ) {
    super();

    this.repo = repo;
    this.query = query;
    this.i18n = i18n;

    this.runningBalance = runningBalance(this.repo.openingBalance);
  }

  /**
   * Retrieves the transaction status.
   * @param {} transaction
   * @returns {BankTransactionStatus}
   */
  private getTransactionStatus(transaction: any): BankTransactionStatus {
    const categorizedTrans = this.repo.uncategorizedTransactionsMapByRef.get(
      `${transaction.referenceType}-${transaction.referenceId}`,
    );
    const matchedTrans = this.repo.matchedBankTransactionsMapByRef.get(
      `${transaction.referenceType}-${transaction.referenceId}`,
    );
    if (!isEmpty(categorizedTrans)) {
      return BankTransactionStatus.Categorized;
    } else if (!isEmpty(matchedTrans)) {
      return BankTransactionStatus.Matched;
    } else {
      return BankTransactionStatus.Manual;
    }
  }

  /**
   * Retrieves the uncategoized transaction id from the given transaction.
   * @param transaction
   * @returns {number|null}
   */
  private getUncategorizedTransId(transaction: any): number {
    // The given transaction would be categorized, matched or not, so we'd take a look at
    // the categorized transaction first to get the id if not exist, then should look at the matched
    // transaction if not exist too, so the given transaction has no uncategorized transaction id.
    const categorizedTrans = this.repo.uncategorizedTransactionsMapByRef.get(
      `${transaction.referenceType}-${transaction.referenceId}`,
    );
    const matchedTrans = this.repo.matchedBankTransactionsMapByRef.get(
      `${transaction.referenceType}-${transaction.referenceId}`,
    );
    // Relation between the transaction and matching always been one-to-one.
    const firstCategorizedTrans = first(categorizedTrans);
    const firstMatchedTrans = first(matchedTrans);

    return (
      firstCategorizedTrans?.id ||
      firstMatchedTrans?.uncategorizedTransactionId ||
      null
    );
  }

  /**
   *Transformes the account transaction to to cashflow transaction node.
   * @param {IAccountTransaction} transaction
   * @returns {ICashflowAccountTransaction}
   */
  private transactionNode = (transaction: any): ICashflowAccountTransaction => {
    const status = this.getTransactionStatus(transaction);
    const uncategorizedTransactionId =
      this.getUncategorizedTransId(transaction);

    return {
      date: transaction.date,
      formattedDate: moment(transaction.date).format('YYYY-MM-DD'),

      withdrawal: transaction.credit,
      deposit: transaction.debit,

      formattedDeposit: this.formatNumber(transaction.debit),
      formattedWithdrawal: this.formatNumber(transaction.credit),

      referenceId: transaction.referenceId,
      referenceType: transaction.referenceType,

      formattedTransactionType: this.i18n.t(transaction.referenceTypeFormatted),

      transactionNumber: transaction.transactionNumber,
      referenceNumber: transaction.referenceNumber,

      runningBalance: this.runningBalance.amount(),
      formattedRunningBalance: this.formatNumber(this.runningBalance.amount()),

      balance: 0,
      formattedBalance: '',
      status,
      formattedStatus: formatBankTransactionsStatus(status),
      uncategorizedTransactionId,
    };
  };

  /**
   * Associate cashflow transaction node with running balance attribute.
   * @param {IAccountTransaction} transaction
   * @returns {ICashflowAccountTransaction}
   */
  private transactionRunningBalance = (
    transaction: ICashflowAccountTransaction,
  ): ICashflowAccountTransaction => {
    const amount = transaction.deposit - transaction.withdrawal;

    const biggerThanZero = R.lt(0, amount);
    const lowerThanZero = R.gt(0, amount);

    const absAmount = Math.abs(amount);

    R.when(R.always(biggerThanZero), this.runningBalance.decrement)(absAmount);
    R.when(R.always(lowerThanZero), this.runningBalance.increment)(absAmount);

    const runningBalance = this.runningBalance.amount();

    return {
      ...transaction,
      runningBalance,
      formattedRunningBalance: this.formatNumber(runningBalance),
    };
  };

  /**
   * Associate to balance attribute to cashflow transaction node.
   * @param {ICashflowAccountTransaction} transaction
   * @returns {ICashflowAccountTransaction}
   */
  private transactionBalance = (
    transaction: ICashflowAccountTransaction,
  ): ICashflowAccountTransaction => {
    const balance =
      transaction.runningBalance +
      transaction.withdrawal * -1 +
      transaction.deposit;

    return {
      ...transaction,
      balance,
      formattedBalance: this.formatNumber(balance),
    };
  };

  /**
   * Transformes the given account transaction to cashflow report transaction.
   * @param {ICashflowAccountTransaction} transaction
   * @returns {ICashflowAccountTransaction}
   */
  private transactionTransformer = (
    transaction,
  ): ICashflowAccountTransaction => {
    return R.compose(
      this.transactionBalance,
      this.transactionRunningBalance,
      this.transactionNode,
    )(transaction);
  };

  /**
   * Retrieve the report transactions node.
   * @param {} transactions
   * @returns {ICashflowAccountTransaction[]}
   */
  private transactionsNode = (transactions): ICashflowAccountTransaction[] => {
    return R.map(this.transactionTransformer)(transactions);
  };

  /**
   * Retrieve the reprot data node.
   * @returns {ICashflowAccountTransaction[]}
   */
  public reportData(): ICashflowAccountTransaction[] {
    return this.transactionsNode(this.repo.transactions);
  }
}
