import { Injectable } from '@nestjs/common';
import { SalesPriceFormulaDataService } from 'src/modules/transaction/sales-price-formula/data/services/sales-price-formula-data.service';
import { TaxDataService } from 'src/modules/transaction/tax/data/services/tax-data.service';
import { TransactionEntity } from '../../entities/transaction.entity';
import { calculateFormula } from 'src/modules/transaction/sales-price-formula/domain/usecases/managers/helpers/calculation-formula.helper';
import * as math from 'mathjs';
import {
  TransactionBundlingItemEntity,
  TransactionItemEntity,
} from '../../entities/transaction-item.entity';
import { STATUS } from 'src/core/strings/constants/base.constants';

@Injectable()
export class PriceCalculator {
  constructor(
    private formulaService: SalesPriceFormulaDataService,
    private taxService: TaxDataService,
  ) {}

  private initialValue(formulas: any[]) {
    const tax = {};
    for (const formula of formulas) {
      tax[`${formula.name}_value`] = null;
    }

    return tax;
  }

  async calculate(transaction: TransactionEntity) {
    const prices = [];
    const transaction_taxes = [];

    for (let i = 0; i < transaction.items.length; i++) {
      const item = transaction.items[i];
      const price = await this.calculateItem(item);
      const priceValues = this.calculatePrice(price);
      prices.push(priceValues);
      transaction_taxes.push(price.taxesValue);

      if (item.bundling_items) {
        for (let b = 0; b < item.bundling_items.length; b++) {
          const bundling = item.bundling_items[b];

          const bundlingPrice = await this.calculateItem(bundling);
          const bundlingValues = this.calculatePrice(bundlingPrice);

          const item_taxes = { item_taxes: bundlingPrice.taxesValue };
          Object.assign(bundling, bundlingValues, item_taxes);
          item.bundling_items[b] = bundling;
        }
      }

      const item_taxes = { item_taxes: price.taxesValue };
      Object.assign(item, priceValues, item_taxes);
      transaction.items[i] = item;
    }

    const tatal_taxes = this.mergeAndSumArrays(transaction_taxes);
    transaction.taxes = tatal_taxes;
    const { payment_total_dpp, ...otherValue } =
      this.sumArrayBasedOnObjectKey(prices);

    return {
      dpp_value: payment_total_dpp,
      other: otherValue,
      tax_datas: tatal_taxes,
    };
  }

  async calculateItem(
    transaction: TransactionItemEntity | TransactionBundlingItemEntity,
  ) {
    // const itemShare = 20 / 100;
    // const profitShare = 10 / 100;

    const { profitShare, tenantShare } = await this.formulaService.itemTax(
      transaction.item_id,
    );

    const tax = await this.taxService.taxKeyValue();
    /* Update constant from 
      - profit_share -> tenant_share
      - item_share -> profit_share 
      */
    const taxes = {
      ...tax,
      tenant_share: tenantShare,
      profit_share: profitShare,
    };
    const dpp = await this.formulaService.salesPriceFormula();
    const taxFormula = await this.taxService.getManyByOptions({
      where: {
        status: STATUS.ACTIVE,
      },
    });
    const shareFormulas = await this.formulaService.profitShareFormula();
    const taxShareFormulas = shareFormulas.map((formula) => {
      return {
        ...formula,
        name: formula.value_for,
      };
    });
    const formulas = [...taxFormula, ...taxShareFormulas];
    const values = {
      total: transaction.total_net_price,
      ...this.initialValue(formulas),
      ...taxes,
    };

    // const dpp = formulas.find((formula) => formula.value_for == 'dpp');

    const dppValue = calculateFormula(dpp.formula_string, taxes, values.total);

    values['dpp'] = dppValue;
    values['dpp_value'] = dppValue;

    let calledVariable = [];
    const taxesValue = [];
    do {
      const valueFor = this.withNullValue(values, calledVariable);

      const formula = formulas.find((formula) => {
        const name = formula['name'] ?? formula['value_for'];
        return `${name}_value` == valueFor;
      });
      let result = null;

      try {
        if (!formula) {
          break;
        }
        result = math.evaluate(formula.formula_string, values);
        calledVariable = [];
      } catch (error) {
        calledVariable.push(valueFor);
        console.log(error.message);
      }

      values[valueFor] = result;
      if (result != null) {
        const tax = taxFormula.find(({ id }) => id == formula.id);
        if (tax != null) {
          taxesValue.push({
            tax_id: tax?.id,
            tax_name: tax?.name,
            taxt_value: result,
            tax_total_value: result,
            transaction_id: transaction.id,
          });
        }
      }
      // values[`${valueFor}_value`] = result;
    } while (this.containsNullValue(values));

    // const itemShareFormula = shareFormulas.find(
    //   (f) => f.value_for == 'tenant_share',
    // );
    // values['tenant_share_value'] = math.evaluate(
    //   itemShareFormula.formula_string,
    //   values,
    // );

    // const profitShareFormula = shareFormulas.find(
    //   (f) => f.value_for == 'profit_share',
    // );
    // values['profit_share_value'] = math.evaluate(
    //   profitShareFormula.formula_string,
    //   values,
    // );

    return { dpp_value: dppValue, tax_datas: values, taxesValue };
  }

  calculatePrice(prices: any) {
    const data = prices.tax_datas;
    const filteredObject: any = Object.keys(data)
      .filter((key) => key.endsWith('_value'))
      .reduce((acc, key) => ({ ...acc, [key]: data[key] }), {});

    const { dpp_value, tenant_share_value, profit_share_value, ...tax } =
      filteredObject;
    const taxes = this.sumPriceObject(tax);
    return {
      total_profit_share: profit_share_value,
      total_share_tenant: tenant_share_value,
      payment_total_tax: taxes,
      payment_total_dpp: dpp_value,
      tax,
    };
  }

  sumArrayBasedOnObjectKey(arr) {
    return arr.reduce((acc, cur) => {
      Object.keys(cur).forEach((key) => {
        if (!acc[key]) {
          acc[key] = 0;
        }
        acc[key] += cur[key];
      });
      return acc;
    }, {});
  }

  sumPriceObject(taxes: any): number {
    let total = 0;
    for (const tax in taxes) {
      total += taxes[tax] ?? 0;
    }

    return total;
  }

  containsNullValue(obj) {
    if (typeof obj !== 'object' || obj === null) {
      return obj === null; // Return true if the value itself is null
    }

    for (const key in obj) {
      if (obj.hasOwnProperty(key)) {
        if (this.containsNullValue(obj[key])) {
          return true; // If any nested value is null, return true
        }
      }
    }

    return false; // If no null values are found, return false
  }

  mergeAndSumArrays(arrays): any {
    const mergedData = {};

    arrays.forEach((arr) => {
      arr.forEach((item) => {
        if (!mergedData[item.tax_id]) {
          mergedData[item.tax_id] = {
            tax_name: item.tax_name,
            taxt_value: 0,
            tax_total_value: 0,
            transaction_id: item.transaction_id,
          };
        }

        mergedData[item.tax_id].taxt_value += item.taxt_value;
        mergedData[item.tax_id].tax_total_value += item.tax_total_value;
      });
    });

    return Object.values(mergedData);
  }

  withNullValue(mainObj, called = []) {
    const obj = { ...mainObj };
    for (const variable of called) {
      delete obj[variable];
    }
    if (typeof obj !== 'object' || obj === null) {
      return null; // Return null for non-object values
    }

    for (const key in obj) {
      if (obj.hasOwnProperty(key)) {
        if (obj[key] === null) {
          return key; // Found a null value, return the key
        } else if (typeof obj[key] === 'object') {
          const nestedKey = this.withNullValue(obj[key]);
          if (nestedKey !== null) {
            return `${key}.${nestedKey}`; // Found null in nested object, return nested key path
          }
        }
      }
    }

    return null; // No null values found, return null
  }
}
