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

import { IReceivableItemDocPopulated, ReceivableItemModel } from '../models/ReceivableItem.model';
import { COLLATERALS_LOOKUP, getBBCSheetsForBorrowerAndType } from './collaterals.db';
import { ECollaterals } from '../enums/collaterals.enum';
import { ReceivableModel } from '../models/Receivable.model';
import { ReceivableAvailabilityModel } from '../models/ReceivableAvailability.model';
import { getBBCSheetsByType, getUniqueDates } from './bbcSheets.db';
import { MODEL_NAMES } from '../models/_models';

export interface IPartiallyPaidInvoice {
  invoiceNumber: string;
  invoiceDate: Date;
  customer: string;
  originalAmount: number;
  restatedAmount: number;
  changeCounter: number;
  lastChangeDate: Date;
  difference: number;
}

export const CUSTOMER_NUMBER_SEPARATOR = '~@~';

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

export const getReceivableItemsSKUAndValue = async (
  bbcDate: string,
  borrowerId: string,
) =>
  await ReceivableModel.aggregate<{ _id: string; qty: number }>([
    {
      $match: { bbcDate, borrowerId },
    },
    { $unwind: '$invoices' },
    {
      $group: {
        _id: '$invoices.invoiceNumber',
        qty: { $first: '$invoices.invoiceAmount' },
      },
    },
    {
      $sort: { qty: -1 },
    },
  ]);

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

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

export const aggregateSKUDateAgingPlot = async (bbcSheetIds: mongoose.Types.ObjectId[]) => {
  const generateSumAggregation = (start: Date, end: Date) => ({
    $sum: {
      $add: {
        $cond: [
          {
            $and: [
              { $lte: ['$invoiceDate', start] },
              { $gte: ['$invoiceDate', end] },
            ],
          },
          '$invoiceAmount',
          '$$REMOVE',
        ],
      },
    },
  });
  const a = await ReceivableItemModel.aggregate<{
    '0-30': number;
    '30-60': number;
    '60-90': number;
    '90+': number;
  }>([
    { $match: { 'bbcSheetId': { $in: bbcSheetIds } } },
    ...COLLATERALS_LOOKUP,
    {
      $group: {
        _id: '$bbcDate',
        '0-30': generateSumAggregation(today, thirtyDaysAgo),
        '30-60': generateSumAggregation(thirtyDaysAgo, sixtyDaysAgo),
        '60-90': generateSumAggregation(sixtyDaysAgo, ninetyDaysAgo),
        '90+': {
          $sum: {
            $add: {
              $cond: [
                {
                  $lte: ['$invoiceDate', ninetyDaysAgo],
                },
                '$invoiceAmount',
                '$$REMOVE',
              ],
            },
          },
        },
      },
    },
  ]);

  return a[0];
};

export const aggregateAllValuesForBbc = async (sheetIds: mongoose.Types.ObjectId[]) => {
  return ReceivableItemModel.aggregate([
    {
      $match: {
        'bbcSheetId': {
          $in: sheetIds,
        },
      },
    },
    ...COLLATERALS_LOOKUP,
    {
      $group: {
        '_id': '$invoiceNumber',
        'value': {
          $first: '$invoiceAmount',
        },
        'invoiceDetail': {
          $first: '$invoiceDetail1',
        },
        'customerTitle': {
          $first: '$customerTitle',
        },
      },
    },
    {
      $project: {
        '_id': 0,
        'invoiceNumber': '$_id',
        'value': 1,
        'invoiceDetail': 1,
        'customerTitle': 1,
      },
    },
    {
      $sort: {
        'value': -1,
      },
    },
    {
      $limit: 500,
    },
  ]);
};

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

  const receivableTotal = getTotalBbcValue[0].total;
  const topItemsForBbc = await ReceivableItemModel.aggregate([
    { $match: { 'bbcSheetId': { $in: bbcSheetIds } } },
    {
      $group: {
        _id: '$customerTitle',
        totalItemValue: { $sum: '$invoiceAmount' },
      },
    },
    { $sort: { totalItemValue: -1 } },
    { $limit: 5 },
    { $project: { _id: 0, name: '$_id', totalItemValue: 1 } },
  ]);
  return { receivableTotal, topItemsForBbc };
};

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

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

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

  const receivableTotal = getTotalBbcValue[0].total;

  const topItemsForBbc = await ReceivableItemModel.aggregate([
    ...commonAggregation,
    {
      $group: {
        '_id': '$customerTitle',
        'totalItemValue': {
          $sum: '$invoiceAmount',
        },
      },
    },
    { $sort: { 'totalItemValue': -1 } },
    { $limit: 5 },
    { $project: { '_id': 0, 'name': '$_id', 'totalItemValue': 1 } },
  ]);
  return { receivableTotal, topItemsForBbc };
};

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

export const getAverageInvoiceSize = async (borrowerId: string) => {
  const bbcGroups = await getUniqueDates(borrowerId, 100, ECollaterals.RECEIVABLE);
  const sheetIds = bbcGroups.reduce((acc, bbc) => [...acc, ...bbc.ids], []);
  return ReceivableItemModel.aggregate([
    {
      $match: {
        'bbcSheetId': {
          $in: sheetIds,
        },
      },
    },
    ...COLLATERALS_LOOKUP,
    {
      $group: {
        '_id': '$bbc._id',
        'bbcDate': {
          $first: '$bbc.bbcDate',
        },
        'totalAmount': {
          $sum: '$invoiceAmount',
        },
        'totalInvoices': {
          $sum: 1,
        },
        'averageInvoiceSize': {
          $avg: '$invoiceAmount',
        },
      },
    },
    {
      $project: {
        '_id': 1,
        'bbcDate': 1,
        'totalAmount': 1,
        'totalInvoices': 1,
        'averageInvoiceSize': {
          $round: [
            '$averageInvoiceSize', 2,
          ],
        },
      },
    },
    {
      $sort: {
        'bbcDate': 1,
      },
    },
  ]);
};

export const getAverageOverInvoiceSize = async (borrowerId: string, averageInvoiceSize) => {
  const bbcSheets = await getBBCSheetsByType([averageInvoiceSize._id], ECollaterals.RECEIVABLE);
  const sheetIds = bbcSheets.map((sheet) => sheet._id);
  return ReceivableItemModel.aggregate([
    {
      $match: {
        'bbcSheetId': {
          $in: sheetIds,
        },
      },
    },
    ...COLLATERALS_LOOKUP,
    {
      $match: {
        'invoiceAmount': { $gte: averageInvoiceSize.averageInvoiceSize },
      },
    },
    {
      $sort: {
        'invoiceAmount': -1,
      },
    },
    {
      $limit: 5,
    },
    {
      $addFields: {
        'bbcDate': '$bbc.bbcDate',
      },
    },
  ]);
};

export const getPartialPaidTable = async (bbcSheetIds: mongoose.Types.ObjectId[]) => {
  return ReceivableItemModel.aggregate<IReceivableItemDocPopulated>([
    {
      $match: {
        'bbcSheetId': { $in: bbcSheetIds },
      },
    },
    ...COLLATERALS_LOOKUP,
    {
      $sort: {
        'bbc.bbcDate': 1,
      },
    },
  ]);
};

export const getFirstLastBBCInvoiceWithNumberOccurrence = async (bbcSheetIds: mongoose.Types.ObjectId[], {
  customerTitle,
  invoiceNumber,
}, firstLast: 'first' | 'last') => {
  return ReceivableItemModel.aggregate([
    {
      $match: {
        $and: [
          { 'bbcSheetId': { $in: bbcSheetIds } },
          { 'customerTitle': customerTitle },
          { 'invoiceNumber': invoiceNumber },
        ],
      },
    },
    ...COLLATERALS_LOOKUP,
    {
      $sort: {
        'bbc.bbcDate': firstLast === 'first' ? 1 : -1,
      },
    },
    {
      $limit: 1,
    },
  ]);
};

export const getUniqInvoices = async (bbcSheetIds: mongoose.Types.ObjectId[]) => {
  return ReceivableItemModel.aggregate<{ _id: null, uniqueNumbers: string[], uniqueValues: string[] }>([
    {
      $match: {
        'bbcSheetId': {
          $in: bbcSheetIds,
        },
      },
    },
    ...COLLATERALS_LOOKUP,
    {
      $sort: {
        'bbc.bbcDate': 1,
      },
    },
    {
      $group: {
        '_id': null,
        'uniqueNumbers': { $addToSet: '$invoiceNumber' },
        'uniqueValues': { $addToSet: { $concat: ['$customerTitle', CUSTOMER_NUMBER_SEPARATOR, '$invoiceNumber'] } },
      },
    },
  ]);
};

export const setReceivableManualInputsToggle = async (bbcDateId: string, useManualInputs: boolean) => {
  await ReceivableAvailabilityModel.updateOne({ bbcDateId }, { useManualInputs });
};

export const getReceivableItemsCustomerAndValue = async (sheetIds: string[]) =>
  await ReceivableItemModel.aggregate<{
    _id: string;
    totalAmount: number;
  }>([
    {
      $match: {
        bbcSheetId: { $in: sheetIds.map((sheetId) => new mongoose.Types.ObjectId(sheetId)) },
      },
    }, {
      $group: {
        _id: '$customerTitle',
        totalAmount: {
          $sum: '$invoiceAmount',
        },
      },
    }, {
      $sort: {
        totalAmount: -1,
      },
    },
  ]);

export const getDuplicates = async (bbcDateId: string) => {
  const bbcSheets = await getBBCSheetsByType([bbcDateId], ECollaterals.RECEIVABLE);
  const bbcSheetIds = bbcSheets.map((sheet) => sheet._id);

  return ReceivableItemModel.aggregate([
    {
      $match: {
        'bbcSheetId': { $in: bbcSheetIds },
      },
    },
    {
      $project: {
        '_id': 1,
        'invoiceNumber': '$invoiceNumber',
      },
    },
    {
      $group: {
        '_id': '$invoiceNumber',
        'count': {
          $sum: 1,
        },
      },
    },
    {
      $match: {
        _id: {
          $ne: null,
        },
        count: {
          $gt: 1,
        },
      },
    },
  ]);
};

export const getAverageOver = async (borrowerId: string) => {
  const average = await getAverageInvoiceSize(borrowerId);
  const aggregationPromises = average.map((item) => {
    return getAverageOverInvoiceSize(borrowerId, item);
  });
  const aggregations = await Promise.all(aggregationPromises);
  return aggregations.reduce((acc, i) => [...acc, ...i], []);
}

export const compareInvoices = (items1: any[], items2: any[]) => {
  const addedInvoices = _.differenceBy(items1, items2, 'invoiceNumber');
  const removedInvoices = _.differenceBy(items2, items1, 'invoiceNumber');
  return { addedInvoices, removedInvoices };
}
