import Decimal from 'decimal.js';
import dayjs from 'dayjs';

import { getBBCDatesForPeriod } from '../../db/bbcDates.db';
import { getBBCSheetsByType } from '../../db/bbcSheets.db';
import { ECollaterals } from '../../enums/collaterals.enum';
import { getCollateralDocsByBBC } from '../../db/collaterals.db';
import { IInventoryItem } from '../../models/InventoryItem.model';
import { IBBCDateDoc } from '../../models/BBCDate.model';
import { TQueryParams } from '../../models/QueryResult.model';
import { addHeaderToData } from '../../helpers/excel.helper';
import { getAllUniqSKUInRangeNew } from '../../db/inventories.db';

interface IGroupedCollaterals extends IBBCDateDoc {
  items: IInventoryItem[];
}

export const getInventoryTurnFull = async (params: TQueryParams) => {
  const { tableByUnit, tableByValue } = await getInventoryTurn(params);

  const headerByUnit = getInventoryTurnHeader('byUnit');
  const headerByValue = getInventoryTurnHeader('byValue');

  const tableByUnitFull = addHeaderToData(headerByUnit, tableByUnit);
  const tableByValueFull = addHeaderToData(headerByValue, tableByValue);

  return { 'by unit': tableByUnitFull, 'by value': tableByValueFull };
};

export const getInventoryTurn = async (params: TQueryParams) => {
  const { borrowerId, startDate, endDate } = params;
  const bbcDates = await getBBCDatesForPeriod(String(borrowerId), new Date(startDate), new Date(endDate));
  const bbcIds = bbcDates.map((bbc) => String(bbc._id));
  const bbcSheets = await getBBCSheetsByType(bbcIds, ECollaterals.INVENTORY);
  const skuGroup = (await getAllUniqSKUInRangeNew(bbcSheets.map((bbcSheet) => String(bbcSheet._id)))).pop();
  const BBCs: IGroupedCollaterals[] = await getCollateralDocsByBBC(bbcIds, ECollaterals.INVENTORY);
  return getDifference(skuGroup ? skuGroup.skus.sort() : [], BBCs);
};

const getDifference = (uniqSKUs: string[], BBCs: IGroupedCollaterals[]) => {
  if (uniqSKUs) {
    const groupedBbc = uniqSKUs.map((sku) => {
      let minUnitCost = 0;
      const totalSum = BBCs.map((bbc) => {
        const skuItems = bbc.items.filter((item) => item.sku === sku);
        const unitCosts = skuItems.map((item) => item.qty === 0 ? 0 : new Decimal(item.value).div(item.qty).toNumber());
        if (unitCosts.length) {
          minUnitCost = Math.min(...unitCosts);
        }
        const sum = skuItems.reduce((acc, item) => new Decimal(acc).add(item.qty).toNumber(), 0);
        return { bbc: bbc.bbcDate, sum };
      });
      return { sku, totalSum, minUnitCost };
    });
    const groupedBbcWithDifferences = groupedBbc.map((group) => {
      const differences = [];
      group.totalSum.forEach((sumGroup, index) => {
        if (index > 0) {
          const firstDate = dayjs(group.totalSum[index - 1].bbc);
          const lastDate = dayjs(sumGroup.bbc);
          const diffDays = lastDate.diff(firstDate, 'day');
          const diff = new Decimal(group.totalSum[index - 1].sum).sub(sumGroup.sum).toNumber();
          const diffPerDay = diffDays > 0 ? new Decimal(diff).div(diffDays).toNumber() : 0;
          differences.push({
            bbc: sumGroup.bbc,
            diff,
            diffDays,
            decreasePerDay: diffPerDay > 0 ? diffPerDay : 0,
            increasePerDay: diffPerDay < 0 ? diffPerDay : 0,
          });
        }
      });
      return { ...group, differences };
    });
    const tableByUnit = [];
    groupedBbcWithDifferences.forEach((group) => {
      const totalDifference = group.differences.reduce((acc, group) => {
        if (group.diff < 0) {
          const diff = new Decimal(acc.increasesOverPeriod).add(group.diff).toNumber();
          return { ...acc, increasesOverPeriod: diff };
        } else {
          const diff = new Decimal(acc.decreasesOverPeriod).add(Math.abs(group.diff)).toNumber();
          return { ...acc, decreasesOverPeriod: diff };
        }
      }, { increasesOverPeriod: 0, decreasesOverPeriod: 0 });
      const { increasesOverPeriod, decreasesOverPeriod } = totalDifference;
      const current = group.totalSum[group.totalSum.length - 1].sum;
      const firstDate = dayjs(group.totalSum[0].bbc);
      const lastDate = dayjs(group.totalSum[group.totalSum.length - 1].bbc);
      const dayInRange = lastDate.diff(firstDate, 'day');
      const averageIncreasesPerDay = new Decimal(increasesOverPeriod).div(dayInRange).toNumber();
      const averageDecreasesPerDay = new Decimal(decreasesOverPeriod).div(dayInRange).toNumber();
      // const increaseDiffs = group.differences.filter((diff) => diff.increasePerDay < 0);
      // const decreaseDiffs = group.differences.filter((diff) => diff.decreasePerDay > 0);
      // const averageIncreasePerDay_test = increaseDiffs.length > 0
      //   ? new Decimal(increaseDiffs.reduce((acc, diff) => new Decimal(diff.increasePerDay).add(acc).toNumber(), 0)).div(decreaseDiffs.length).toNumber()
      //   : 0;
      // const averageDecreasesPerDay_test = decreaseDiffs.length > 0
      //   ? new Decimal(decreaseDiffs.reduce((acc, diff) => new Decimal(diff.decreasePerDay).add(acc).toNumber(), 0)).div(decreaseDiffs.length).toNumber()
      //   : 0;
      const daysToClear = averageDecreasesPerDay === 0
        ? 0
        : new Decimal(current).div(averageDecreasesPerDay).toDP().toNumber();
      const ranking = averageDecreasesPerDay === 0
        ? Math.round(averageIncreasesPerDay)
        : new Decimal(averageIncreasesPerDay).div(averageDecreasesPerDay).toDP().toNumber();
      tableByUnit.push({
        sku: group.sku,
        current,
        minUnitCost: group.minUnitCost,
        increasesOverPeriod,
        decreasesOverPeriod,
        averageIncreasesPerDay,
        averageDecreasesPerDay,
        daysToClear,
        ranking,
      });
    });
    tableByUnit.sort((a, b) => a.ranking > b.ranking ? -1 : 0);
    const tableByValue = [];
    tableByUnit.forEach((unit) => {
      const minUnitCost = unit.minUnitCost;
      const current = new Decimal(unit.current).mul(minUnitCost).toNumber();
      const increasesOverPeriod = new Decimal(unit.increasesOverPeriod).mul(minUnitCost).toNumber();
      const decreasesOverPeriod = new Decimal(unit.decreasesOverPeriod).mul(minUnitCost).toNumber();
      const averageIncreasesPerDay = new Decimal(unit.averageIncreasesPerDay).mul(minUnitCost).toNumber();
      const averageDecreasesPerDay = new Decimal(unit.averageDecreasesPerDay).mul(minUnitCost).toNumber();
      const daysToClear = Math.round(new Decimal(unit.daysToClear).mul(minUnitCost).toNumber());
      const ranking = Math.round(new Decimal(unit.ranking).mul(minUnitCost).toNumber());
      tableByValue.push({
        sku: unit.sku,
        minUnitCost,
        current,
        increasesOverPeriod,
        decreasesOverPeriod,
        averageIncreasesPerDay,
        averageDecreasesPerDay,
        daysToClear,
        ranking,
      });
    });
    return { tableByUnit, tableByValue };
  }
  return { tableByUnit: [], tableByValue: [] };
};

const getInventoryTurnHeader = (type: 'byUnit' | 'byValue') => {
  const headerMap = {
    'byUnit': {
      sku: 'SKU',
      current: 'Current number of units',
      increasesOverPeriod: 'Increases in units over period',
      decreasesOverPeriod: 'Decreases in units over period',
      averageIncreasesPerDay: 'Average increases in units per day',
      averageDecreasesPerDay: 'Average decreases in units per day',
      daysToClear: 'Days to clear',
      ranking: 'Ranking',
    },
    'byValue': {
      sku: 'SKU',
      current: 'Current value',
      minUnitCost: 'Min unit cost',
      increasesOverPeriod: 'Increases over in value period',
      decreasesOverPeriod: 'Decreases over in value period',
      averageIncreasesPerDay: 'Average value increases per day',
      averageDecreasesPerDay: 'Average value decreases per day',
      daysToClear: 'Days to clear',
      ranking: 'Ranking',
    },
  };
  return headerMap[type];
};
