import * as R from 'ramda';
import { sumBy } from 'lodash';
import { FinancialDatePeriods } from '../FinancialDatePeriods';
import {
  IDateRange,
  IProfitLossHorizontalDatePeriodNode,
  IProfitLossSheetAccountNode,
  IProfitLossSheetAccountsNode,
  IProfitLossSheetCommonNode,
  IProfitLossSheetNode,
} from '@/interfaces';

export const ProfitLossSheetDatePeriods = (Base) =>
  class extends R.compose(FinancialDatePeriods)(Base) {
    /**
     * Retrieves the date periods based on the report query.
     * @returns {IDateRange[]}
     */
    get datePeriods(): IDateRange[] {
      return this.getDateRanges(
        this.query.fromDate,
        this.query.toDate,
        this.query.displayColumnsBy
      );
    }

    /**
     * Retrieves the date periods of the given node based on the report query.
     * @param   {IProfitLossSheetCommonNode} node
     * @param   {Function} callback
     * @returns {}
     */
    protected getReportNodeDatePeriods = (
      node: IProfitLossSheetCommonNode,
      callback: (
        node: IProfitLossSheetCommonNode,
        fromDate: Date,
        toDate: Date,
        index: number
      ) => any
    ) => {
      return this.getNodeDatePeriods(
        this.query.fromDate,
        this.query.toDate,
        this.query.displayColumnsBy,
        node,
        callback
      );
    };

    // --------------------------
    // # Account Nodes.
    // --------------------------
    /**
     * Retrieve account node date period total.
     * @param   {IProfitLossSheetAccount} node
     * @param   {Date} fromDate
     * @param   {Date} toDate
     * @returns {}
     */
    private getAccountNodeDatePeriodTotal = (
      node: IProfitLossSheetAccountNode,
      fromDate: Date,
      toDate: Date
    ) => {
      const periodTotal = this.repository.periodsAccountsLedger
        .whereAccountId(node.id)
        .whereFromDate(fromDate)
        .whereToDate(toDate)
        .getClosingBalance();

      return this.getDatePeriodTotalMeta(periodTotal, fromDate, toDate);
    };

    /**
     * Retrieve account node date period.
     * @param   {IProfitLossSheetAccountNode} node
     * @returns {IProfitLossSheetAccountNode}
     */
    public getAccountNodeDatePeriod = (node: IProfitLossSheetAccountNode) => {
      return this.getReportNodeDatePeriods(
        node,
        this.getAccountNodeDatePeriodTotal
      );
    };

    /**
     * Account date periods to the given account node.
     * @param   {IProfitLossSheetAccountNode} node
     * @returns {IProfitLossSheetAccountNode}
     */
    public assocAccountNodeDatePeriod = (
      node: IProfitLossSheetAccountNode
    ): IProfitLossSheetAccountNode => {
      const datePeriods = this.getAccountNodeDatePeriod(node);

      return R.assoc('horizontalTotals', datePeriods, node);
    };

    // --------------------------
    // # Aggregate nodes.
    // --------------------------
    /**
     * Retrieves sumation of the given aggregate node children totals.
     * @param   {IProfitLossSheetAccountsNode} node
     * @param   {number} index
     * @returns {number}
     */
    private getAggregateDatePeriodIndexTotal = (
      node: IProfitLossSheetAccountsNode,
      index: number
    ): number => {
      return sumBy(node.children, `horizontalTotals[${index}].total.amount`);
    };

    /**
     *
     * @param    {IProfitLossSheetAccount} node
     * @param    {Date} fromDate
     * @param    {Date} toDate
     * @param    {number} index
     * @returns  {IProfitLossSheetAccount}
     */
    private getAggregateNodeDatePeriodTotal = R.curry(
      (
        node: IProfitLossSheetAccountsNode,
        fromDate: Date,
        toDate: Date,
        index: number
      ): IProfitLossHorizontalDatePeriodNode => {
        const periodTotal = this.getAggregateDatePeriodIndexTotal(node, index);

        return this.getDatePeriodTotalMeta(periodTotal, fromDate, toDate);
      }
    );

    /**
     * Retrieves aggregate horizontal date periods.
     * @param   {IProfitLossSheetAccountsNode} node
     * @returns {IProfitLossSheetAccountsNode}
     */
    private getAggregateNodeDatePeriod = (
      node: IProfitLossSheetAccountsNode
    ): IProfitLossHorizontalDatePeriodNode[] => {
      return this.getReportNodeDatePeriods(
        node,
        this.getAggregateNodeDatePeriodTotal
      );
    };

    /**
     * Assoc horizontal date periods to aggregate node.
     * @param   {IProfitLossSheetAccountsNode} node
     * @returns {IProfitLossSheetAccountsNode}
     */
    protected assocAggregateDatePeriod = (
      node: IProfitLossSheetAccountsNode
    ): IProfitLossSheetAccountsNode => {
      const datePeriods = this.getAggregateNodeDatePeriod(node);

      return R.assoc('horizontalTotals', datePeriods, node);
    };

    // --------------------------
    // # Equation nodes.
    // --------------------------
    /**
     * Retrieves equation date period node.
     * @param   {IProfitLossSheetNode[]} accNodes
     * @param   {IProfitLossSheetNode} node
     * @param   {Date} fromDate
     * @param   {Date} toDate
     * @param   {number} index
     * @returns {IProfitLossHorizontalDatePeriodNode}
     */
    private getEquationNodeDatePeriod = R.curry(
      (
        accNodes: IProfitLossSheetNode[],
        equation: string,
        node: IProfitLossSheetNode,
        fromDate: Date,
        toDate: Date,
        index: number
      ): IProfitLossHorizontalDatePeriodNode => {
        const tableNodes = this.getNodesTableForEvaluating(
          `horizontalTotals[${index}].total.amount`,
          accNodes
        );
        // Evaluate the given equation.
        const total = this.evaluateEquation(equation, tableNodes);

        return this.getDatePeriodTotalMeta(total, fromDate, toDate);
      }
    );

    /**
     * Retrieves the equation node date periods.
     * @param   {IProfitLossSheetNode[]} node
     * @param   {string} equation
     * @param   {IProfitLossSheetNode} node
     * @returns {IProfitLossHorizontalDatePeriodNode[]}
     */
    private getEquationNodeDatePeriods = R.curry(
      (
        accNodes: IProfitLossSheetNode[],
        equation: string,
        node: IProfitLossSheetNode
      ): IProfitLossHorizontalDatePeriodNode[] => {
        return this.getReportNodeDatePeriods(
          node,
          this.getEquationNodeDatePeriod(accNodes, equation)
        );
      }
    );

    /**
     * Assoc equation node date period.
     * @param   {IProfitLossSheetNode[]}
     * @param   {IProfitLossSheetNode} node
     * @returns {IProfitLossSheetNode}
     */
    protected assocEquationNodeDatePeriod = R.curry(
      (
        accNodes: IProfitLossSheetNode[],
        equation: string,
        node: IProfitLossSheetNode
      ): IProfitLossSheetNode => {
        const periods = this.getEquationNodeDatePeriods(
          accNodes,
          equation,
          node
        );
        return R.assoc('horizontalTotals', periods, node);
      }
    );
  };
