import mongoose from 'mongoose';
import dayjs from 'dayjs';

import { InventoryModel } from '../models/Inventory.model';
import { getOlderBBCDates } from './bbcDates.db';
import { getBBCSheetsByType } from './bbcSheets.db';
import { ECollaterals } from '../enums/collaterals.enum';
import { InventoryItemModel } from '../models/InventoryItem.model';
import { COLLATERALS_LOOKUP, getBBCSheetsForBorrowerAndType } from './collaterals.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 getInventoryItemsSKUAndValue = async (sheetIds: string[]) =>
  await InventoryItemModel.aggregate<{
    _id: string;
    totalAmount: number;
  }>([
    {
      $match: { bbcSheetId: { $in: sheetIds.map((sheetId) => new mongoose.Types.ObjectId(sheetId)) } },
    },
    {
      $group: {
        _id: '$sku',
        totalAmount: {
          $sum: '$value',
        },
      },
    },
    {
      $sort: { totalAmount: -1 },
    },
  ]);

export const getInventoriesAndTheirQtyByBorrowerId = async (borrowerId: string, allBbcDates: { bbcDate: Date }[]) =>
  await InventoryModel.aggregate<{ _id: string; qty: number }>([
    {
      $match: {
        borrowerId,
        bbcDate: {
          $in: allBbcDates.map((i) => i.bbcDate)
        }
      }
    },
    { $unwind: '$items' },
    {
      $group: {
        _id: '$bbcDate',
        qty: { $sum: '$items.qty' },
      },
    },
    {
      $sort: {
        _id: 1,
      },
    },
  ]);

export const getMostRecentInventoryBBCDate = async (borrowerId: string) => {
  const inventory = await InventoryModel.find({ borrower: borrowerId })
    .sort([['bbcDate', -1]])
    .limit(1);
  return inventory[0].bbcDate;
};

export const getLastDigitFromAllValues = async (allBbcDates: { bbcDate: Date, ids: mongoose.Types.ObjectId[] }[]) => {
  const allIds = allBbcDates.reduce((acc, bbcDate) => [...acc, ...bbcDate.ids], []);
  return InventoryItemModel.aggregate([
    {
      $match: {
        'bbcSheetId': {
          $in: allIds
        }
      }
    },
    ...COLLATERALS_LOOKUP,
    {
      $group: {
        '_id': '$bbc.bbcDate',
        'borrowerId': {
          $first: '$bbc.borrowerId',
        },
        'items': {
          $addToSet: {
            'value': '$value',
          },
        },
      },
    },
    {
      $project: {
        '_id': '$borrowerId',
        'bbcDate': '$_id',
        'digitArray': {
          $map: {
            input: '$items',
            as: 'item',
            in: {
              $substr: [
                {
                  $toString: '$$item.value',
                },
                0,
                1,
              ],
            },
          },
        },
      },
    },
    {
      $sort: {
        'bbcDate': 1
      }
    },
    {
      $group: {
        '_id': 1,
        'bbcDates': {
          $push: '$bbcDate'
        },
        'digitArrays': {
          $push: '$digitArray'
        },
      }
    }
  ]);
};

export const groupSKUsByDate = async (borrowerId: string) => {
  const bbcDate = await getMostRecentInventoryBBCDate(borrowerId);
  const date = new Date(bbcDate);
  const oneMonthAgo = dayjs(date).subtract(1, 'month').toDate();
  const threeMonthsAgo = dayjs(date).subtract(3, 'month').toDate();
  const sixMonthsAgo = dayjs(date).subtract(6, 'month').toDate();

  const items = await InventoryModel.aggregate<{
    _id: string;
    valuesAtTime: { bbcDate: string; count: number }[];
  }>([
    {
      $match: {
        bbcDate: { $in: [date, oneMonthAgo, threeMonthsAgo, sixMonthsAgo] },
      },
    },
    {
      $unwind: '$items',
    },
    {
      $group: {
        _id: '$items.sku',
        valuesAtTime: { $push: { bbcDate: '$bbcDate', count: '$items.qty' } },
      },
    },
  ]);

  return {
    items,
    oneMonthAgo,
    threeMonthsAgo,
    sixMonthsAgo,
    recent: date,
  };
};

export const getAllTimeOldestSKUDateNew = async (borrowerId: string, bbcDate: Date) => {
  const bbcDates = await getOlderBBCDates(borrowerId, bbcDate);
  const sheets = await getBBCSheetsByType(bbcDates.map((bbcDate) => bbcDate._id.toString()), ECollaterals.INVENTORY);
  return InventoryItemModel.aggregate<{ _id: string; oldestDate: string }>([
    {
      $match: {
        bbcSheetId: {
          '$in': sheets.map((sheet) => sheet._id)
        }
      }
    },
    {
      $group: {
        _id: '$sku',
        oldestDate: {
          $min: '$skuDate',
        },
      },
    },
  ]);
};

const todayDayjs = dayjs();
const today = todayDayjs.toDate();
const thirtyDaysAgo = todayDayjs.subtract(30, 'day').toDate();
const sixtyDaysAgo = todayDayjs.subtract(60, 'day').toDate();
const ninetyDaysAgo = todayDayjs.subtract(90, 'day').toDate();

export const aggregateSKUDateAgingPlot = async (bbcSheetIds: mongoose.Types.ObjectId[]) => {

  const generateSumAggregation = (start: Date, end: Date) => ({
    $sum: {
      $add: {
        $cond: [
          {
            $and: [
              { $lte: ['$skuDate', start] },
              { $gte: ['$skuDate', end] },
            ],
          },
          '$value',
          '$$REMOVE',
        ],
      },
    },
  });

  const a = await InventoryItemModel.aggregate<{
    '0-30': number;
    '30-60': number;
    '60-90': number;
    '90+': number;
  }>([
    { $match: { 'bbcSheetId': { $in: bbcSheetIds } } },
    ...COLLATERALS_LOOKUP,
    {
      $group: {
        _id: '$bbc.bbcDate',
        '0-30': generateSumAggregation(today, thirtyDaysAgo),
        '30-60': generateSumAggregation(thirtyDaysAgo, sixtyDaysAgo),
        '60-90': generateSumAggregation(sixtyDaysAgo, ninetyDaysAgo),
        '90+': {
          $sum: {
            $add: {
              $cond: [
                {
                  $lte: ['$skuDate', ninetyDaysAgo],
                },
                '$value',
                '$$REMOVE',
              ],
            },
          },
        },
      },
    },
  ]);

  return a[0];
};

interface ISkus {
  category: string,
  sku: string,
  skuDescription: string,
  value: number,
}

export const aggregateAllValuesForBbc = async (bbcSheetIds: mongoose.Types.ObjectId[]) => {
  return InventoryItemModel.aggregate<ISkus>([
    {
      $match: {
        bbcSheetId: { $in: bbcSheetIds },
      },
    },
    {
      $group: {
        _id: '$sku',
        value: { $first: '$value' },
        skuDescription: { $first: '$skuDescription1' },
        category: { $first: '$category' }
      },
    },
    {
      $project: {
        _id: 0,
        sku: '$_id',
        value: 1,
        skuDescription: 1,
        category: 1,
      },
    },
    { $sort: { value: -1 } },
    { $limit: 500 },
  ]);
}

export const aggregateBbcExposures = async (bbcSheetIds: mongoose.Types.ObjectId[]) => {
  const getTotalBbcValue = await InventoryItemModel.aggregate([
    {
      $match: {
        'bbcSheetId': { $in: bbcSheetIds }
      }
    },
    { $group: { _id: 0, total: { $sum: '$value' } } },
  ]);

  const inventoryTotal = getTotalBbcValue[0].total;

  const topItemsForBbc = await InventoryItemModel.aggregate([
    {
      $match: {
        'bbcSheetId': { $in: bbcSheetIds }
      }
    },
    {
      $group: {
        _id: '$category',
        totalItemValue: { $sum: '$value' },
      },
    },
    { $sort: { totalItemValue: -1 } },
    { $limit: 10 },
    { $project: { _id: 0, name: '$_id', totalItemValue: 1 } },
  ]);
  return { inventoryTotal, topItemsForBbc };
};

export const aggregateBbcExposuresGrouped = async (bbcSheetIds: mongoose.Types.ObjectId[]) => {

  const commonAggregation = [
    {
      $match: {
        'bbcSheetId': { $in: bbcSheetIds }
      }
    },
    ...COLLATERALS_LOOKUP,
    {
      $lookup: {
        from: 'categorygroups',
        let: {
          'res_cod_it': 'category',
          'borrowerId': '$borrowerId'
        },
        pipeline: [
          {
            $match: {
              $expr: {
                $eq: [
                  '$bbc.borrowerId', '$$borrowerId'
                ]
              }
            }
          },
          {
            $unwind: '$items'
          }
        ],
        as: 'item'
      }
    },
    {
      $project: {
        'bbc': 1,
        '_id': 1,
        'skuDate': 1,
        'category': 1,
        'value': 1,
        'group': {
          '$filter': {
            'input': '$item',
            'as': 'groupEx',
            'cond': {
              '$eq': [
                '$$groupEx.items', '$category'
              ]
            }
          }
        }
      }
    },
    {
      $addFields: {
        'groupName': {
          $first: '$group.groupName'
        }
      }
    },
    {
      $project: {
        'borrowerId': 1,
        'bbc': 1,
        'skuDate': 1,
        'category': {
          $ifNull: [
            '$groupName', '$category'
          ]
        },
        'value': 1,
      }
    },
  ];

  const getTotalBbcValue = await InventoryItemModel.aggregate([
    ...commonAggregation,
    {
      $group: {
        '_id': 0,
        'total': {
          $sum: '$value'
        }
      }
    }
  ]);

  const inventoryTotal = getTotalBbcValue[0].total;

  const topItemsForBbc = await InventoryItemModel.aggregate([
    ...commonAggregation,
    {
      $group: {
        '_id': '$category',
        'totalItemValue': {
          $sum: '$value'
        }
      }
    },
    { $sort: { 'totalItemValue': -1 } },
    { $limit: 5 },
    { $project: { '_id': 0, 'name': '$_id', 'totalItemValue': 1 } },
  ]);
  return { inventoryTotal, topItemsForBbc };
};

export const getAllCategories = async (borrowerId: string) => {
  const bbcSheets = await getBBCSheetsForBorrowerAndType(borrowerId, ECollaterals.INVENTORY);
  return InventoryItemModel.aggregate<{ categories: string[] }>([
    {
      $match: {
        'bbcSheetId': {
          $in: bbcSheets.map((bbcSheet) => new mongoose.Types.ObjectId(String(bbcSheet._id)))
        }
      }
    },
    {
      $group: {
        '_id': null,
        'categories': {
          $addToSet: '$category'
        }
      }
    },
    {
      $project: {
        '_id': 0
      }
    },
  ]);
};
