import mongoose from 'mongoose';
import _ from 'lodash';

import { InventoryItemModel } from '../models/InventoryItem.model';
import { COLLATERALS_LOOKUP, getCollateralDocsByBBC } from './collaterals.db';
import { getBBCSheetsByType } from './bbcSheets.db';
import { ECollaterals } from '../enums/collaterals.enum';
import { MODEL_NAMES } from '../models/_models';
import { getBBCDatesForPeriod } from './bbcDates.db';

export const getSKUUnitCostForAllDates = async (sheetsIds: mongoose.Types.ObjectId[]) => {
  return InventoryItemModel.aggregate<{
    _id: string;
    currentCost: number;
    minCost: { minCost: number, bbcDate: string };
    qty: number;
  }>([
    {
      $match: {
        'bbcSheetId': {
          $in: sheetsIds,
        },
      },
    },
    ...COLLATERALS_LOOKUP,
    {
      $sort: {
        'bbc.bbcDate': -1,
      },
    },
    {
      $group: {
        '_id': '$sku',
        'currentCost': {
          $first: '$unitCost',
        },
        'minCost': {
          $min: {
            'minCost': '$unitCost',
            'bbcDate': '$bbc.bbcDate',
          },
        },
        'qty': {
          $first: '$qty',
        },
      },
    },
  ]);
};

export const getAllUniqSKUInRangeNew = async (bbcSheetIds: string[]): Promise<{ _id: string, skus: string[] }[]> => {
  return InventoryItemModel.aggregate([
    {
      $match: {
        'bbcSheetId': {
          $in: bbcSheetIds.map((bbcSheetId) => new mongoose.Types.ObjectId(bbcSheetId)),
        },
      },
    },
    {
      $lookup: {
        from: MODEL_NAMES.BBCSheets,
        localField: 'bbcSheetId',
        foreignField: '_id',
        as: 'bbcSheet',
      },
    },
    {
      $unwind: {
        path: '$bbcSheet',
      },
    }, {
      $lookup: {
        from: MODEL_NAMES.BBCDates,
        localField: 'bbcSheet.bbcDateId',
        foreignField: '_id',
        as: 'bbc',
      },
    },
    {
      $unwind: {
        path: '$bbc',
      },
    },
    {
      $sort: {
        'bbc.bbcDate': 1,
      },
    },
    {
      $group: {
        '_id': '$bbc.borrowerId',
        'skus': {
          $addToSet: '$sku',
        },
      },
    },
  ]);
};

export const getSKUQtyValueUnitCost = async (bbcDateId: string) => {
  const bbcSheets = await getBBCSheetsByType([bbcDateId], ECollaterals.INVENTORY);
  const bbcSheetIds = bbcSheets.map((sheet) => sheet._id);
  return InventoryItemModel.aggregate<{ _id: string; unitCost: number; qty: number; value: number }>([
    {
      $match: {
        bbcSheetId: { $in: bbcSheetIds },
      },
    },
    ...COLLATERALS_LOOKUP,
    {
      $group: {
        '_id': '$sku',
        'unitCost': { $first: '$unitCost' },
        'qty': { $first: '$qty' },
        'value': { $first: '$value' },
      },
    },
    { $sort: { _id: -1 } },
  ]);
};

const getMovementDifference = async (skus: string[], BBCs) => {
  if (skus) {
    const groupedBbc = skus.map((sku) => {
      let minUnitCost = 0;
      const totalSum = BBCs.map((bbc) => {
        const values = bbc.items
          .filter((item) => item.sku === sku)
          .map((item) => item.unitCost);
        if (values.length) {
          minUnitCost = Math.min(...values);
        }
        const sum = bbc.items.reduce((acc, item) => {
          if (item.sku === sku) {
            return acc + item.qty;
          }
          return acc;
        }, 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) {
          differences.push({
            bbc: sumGroup.bbc,
            diff: group.totalSum[index - 1].sum - sumGroup.sum,
            previousValue: group.totalSum[index - 1].sum,
            currentValue: sumGroup.sum,
          });
        }
      });
      return { ...group, differences };
    });

    const reportAdditions = [];
    const reportRemoved = [];
    groupedBbcWithDifferences.forEach((group) => {
      const totalDifference = group.differences.reduce((acc, g) => {
        if (g.diff > 0) {
          const diff = acc.increasesOverPeriod + g.diff;
          return {
            ...acc,
            increasesOverPeriod: diff,
            previousValue: g.previousValue,
            currentValue: g.currentValue,
          };
        } else {
          const diff = acc.decreasesOverPeriod + Math.abs(g.diff);
          return {
            ...acc,
            decreasesOverPeriod: diff,
            previousValue: g.previousValue,
            currentValue: g.currentValue,
          };
        }
      }, { increasesOverPeriod: 0, decreasesOverPeriod: 0, previousValue: 0, currentValue: 0 });

      const { increasesOverPeriod, decreasesOverPeriod, previousValue, currentValue } = totalDifference;
      if (increasesOverPeriod === 0 && decreasesOverPeriod === 0) {
        return;
      }
      if (increasesOverPeriod > 0) {
        reportRemoved.push({
          sku: group.sku,
          units: increasesOverPeriod,
          value: increasesOverPeriod * group.minUnitCost,
          previousValue: previousValue,
          currentValue: currentValue,
        });
      }
      if (decreasesOverPeriod > 0) {
        reportAdditions.push({
          sku: group.sku,
          units: decreasesOverPeriod,
          value: decreasesOverPeriod * group.minUnitCost,
          previousValue: previousValue,
          currentValue: currentValue,
        });
      }
    });
    return {
      reportAdditions: _.orderBy(reportAdditions, ['sku'], ['asc']),
      reportRemoved: _.orderBy(reportRemoved, ['sku'], ['asc']),
    };
  }
  return { reportAdditions: [], reportRemoved: [] };
}

export const getInventoryMovement = async (borrowerId: string, startDate: string, endDate: string) => {
  const bbcDates = await getBBCDatesForPeriod(borrowerId, new Date(startDate), new Date(endDate));
  const bbcIds = bbcDates.map((bbc) => String(bbc._id));
  const bbcSheets = await getBBCSheetsByType(bbcIds, ECollaterals.INVENTORY);
  const skuGroups = await getAllUniqSKUInRangeNew(bbcSheets.map((bbcSheet) => String(bbcSheet._id)));
  if (skuGroups.length > 0) {
    const { skus } = skuGroups.pop();
    const BBCs = await getCollateralDocsByBBC(bbcIds, ECollaterals.INVENTORY);
    return await getMovementDifference(skus, BBCs);
  }
  return { reportAdditions: [], reportRemoved: [] };
}
