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

import { BBCDateModel } from '../../models/BBCDate.model';
import { getUniqueDatesForPeriod } from '../../db/bbcSheets.db';
import { ECollaterals } from '../../enums/collaterals.enum';
import { CUSTOMER_NUMBER_SEPARATOR, getUniqInvoices } from '../../db/receivables.db';
import { COLLATERALS_LOOKUP, getCollateralDocsByBBC } from '../../db/collaterals.db';
import { TQueryParams } from '../../models/QueryResult.model';
import { addHeaderToData } from '../../helpers/excel.helper';
import { ReceivableItemModel } from '../../models/ReceivableItem.model';

export const getARTurnFull = async (params: TQueryParams) => {
  const data = await getARTurn(params);
  const header = getARTurnHeader();
  return addHeaderToData(header, data);
};

const getInvoices = async (bbcSheetIds: mongoose.Types.ObjectId[]) => {
  return ReceivableItemModel.aggregate([
    {
      $match: {
        $and: [
          { 'bbcSheetId': { $in: bbcSheetIds } },
        ],
      },
    },
    ...COLLATERALS_LOOKUP,
  ]);
};

export const getARTurn = async (params: TQueryParams) => {
  const { borrowerId, bbcDateId, extraParams } = params;
  const { daysFilter } = <{ daysFilter: number }>extraParams;
  const currentBbcDate = await BBCDateModel.findById(bbcDateId).lean();
  if (!currentBbcDate) {
    return [];
  }
  const rangeStart = dayjs(new Date(currentBbcDate.bbcDate)).subtract(daysFilter, 'day').toDate();
  const rangeEnd = new Date(currentBbcDate.bbcDate);
  const allInventoryBbcDates = await getUniqueDatesForPeriod(borrowerId.toString(), ECollaterals.RECEIVABLE, new Date(rangeStart), new Date(rangeEnd));
  const sheetIds = allInventoryBbcDates.reduce((acc, bbc) => [...acc, ...bbc.ids], []);
  const uniqInvoices = (await getUniqInvoices(sheetIds)).pop();
  if (!uniqInvoices) {
    return [];
  }
  const bbcIds = allInventoryBbcDates.map((bbc) => bbc._id.toString());
  const allInvoicesNew = await getCollateralDocsByBBC(bbcIds, ECollaterals.RECEIVABLE);
  const mappedUniqInvoices = uniqInvoices.uniqueValues.map((value) => {
    const [customerTitle, invoiceNumber] = value.split(CUSTOMER_NUMBER_SEPARATOR);
    return { customerTitle, invoiceNumber };
  });
  const lastInvoiceNew = allInvoicesNew[allInvoicesNew.length - 1];
  if (!lastInvoiceNew) {
    return [];
  }
  const workedInvoice = [];
  mappedUniqInvoices.forEach((uniqInvoice) => {
    const foundInvoice = lastInvoiceNew.items
      .find((invoice) => invoice.customerTitle === uniqInvoice.customerTitle && invoice.invoiceNumber === uniqInvoice.invoiceNumber);
    if (!foundInvoice) {
      workedInvoice.push(uniqInvoice);
    }
  });
  workedInvoice.sort((invoice1, invoice2) => invoice1.invoiceNumber - invoice2.invoiceNumber);
  const lastInvoices = [];

  const allInvoices = await getInvoices(sheetIds);

  workedInvoice.forEach((invoice) => {
    const testFirstInvoices = allInvoices
      .filter((i) => i.customerTitle === invoice.customerTitle && i.invoiceNumber === invoice.invoiceNumber)
      .sort((invoice1, invoice2) => new Date(invoice1.bbcDate).valueOf() - new Date(invoice2.bbcDate).valueOf());
    const firstInvoice = testFirstInvoices.length > 0 ? testFirstInvoices[0] : null;
    const lastInvoice = testFirstInvoices.length > 0 ? testFirstInvoices[testFirstInvoices.length - 1] : null;
    const lastIndex = allInventoryBbcDates.findIndex((i) => i._id.toString() === lastInvoice.bbc._id.toString());
    const dateRemoved = new Date(allInventoryBbcDates[lastIndex + 1].bbcDate);
    const numberOfDays = dayjs(dateRemoved).diff(dayjs(new Date(firstInvoice.invoiceDate)), 'day');
    lastInvoices.push({
      invoiceNumber: firstInvoice.invoiceNumber,
      dateIssued: firstInvoice.invoiceDate,
      dateRemoved,
      numberOfDays,
      valueRemoved: firstInvoice.invoiceAmount,
      customer: firstInvoice.customerTitle,
      totalValue: numberOfDays * firstInvoice.invoiceAmount,
    });
  });

  const groupedInvoices = lastInvoices.reduce((acc, invoice) => {
    acc[invoice.customer] = acc[invoice.customer] || [];
    acc[invoice.customer].push(invoice);
    return acc;
  }, {});

  return Object.entries(groupedInvoices).reduce((acc, [customer, invoices]: [string, any[]]) => {
    const invoiceTotalDays = invoices.reduce((totalAverage, invoice) => new Decimal(totalAverage).add(invoice.numberOfDays).toNumber(), 0);
    const averageNumberDays = new Decimal(invoiceTotalDays).div(invoices.length).toDP(2).toNumber();
    const invoiceTotalValue = invoices.reduce((totalAverage, invoice) => new Decimal(totalAverage).add(invoice.totalValue).toNumber(), 0);
    const invoiceTotalRemoved = invoices.reduce((totalAverage, invoice) => new Decimal(totalAverage).add(invoice.valueRemoved).toNumber(), 0);
    const weightedAveragedDays = invoiceTotalRemoved === 0
      ? 0
      : new Decimal(invoiceTotalValue).div(invoiceTotalRemoved).toDP(2).toNumber();
    const data = {
      customer,
      averageNumberDays,
      weightedAveragedDays,
    };
    return [...acc, data];
  }, []);
};

const getARTurnHeader = () => {
  return {
    customer: 'Customer',
    averageNumberDays: 'Average Number of Days to clear invoice',
    weightedAveragedDays: 'Weighted Averaged Days (by Value)',
  };
};
