import { TQueryParams } from '../../models/QueryResult.model';
import { addHeaderToData } from '../../helpers/excel.helper';
import { getUniqueDatesForPeriod } from '../../db/bbcSheets.db';
import { getPartialPaidTable, IPartiallyPaidInvoice } from '../../db/receivables.db';
import { ECollaterals } from '../../enums/collaterals.enum';
import { IReceivableItemDoc, IReceivableItemDocPopulated } from '../../models/ReceivableItem.model';

export const getPartialPaidFull = async (params: TQueryParams) => {
  const { suspiciousInvoices, restatedInvoices, partiallyPaidInvoices } = await getPartialPaid(params);

  const header = getPartialPaidHeader();

  const suspiciousInvoicesFull = addHeaderToData(header, suspiciousInvoices);
  const restatedInvoicesFull = addHeaderToData(header, restatedInvoices);
  const partiallyPaidInvoicesFull = addHeaderToData(header, partiallyPaidInvoices);

  return {
    'suspicious': suspiciousInvoicesFull,
    'restated': restatedInvoicesFull,
    'partially paid': partiallyPaidInvoicesFull,
  };
};

export const getPartialPaid = async (params: TQueryParams) => {
  const { borrowerId, startDate, endDate } = params;
  const allInventoryBbcDates = await getUniqueDatesForPeriod(borrowerId.toString(), ECollaterals.RECEIVABLE, new Date(startDate), new Date(endDate));
  const sheetIds = allInventoryBbcDates.reduce((acc, bbc) => [...acc, ...bbc.ids], []);
  const allInvoices = await getPartialPaidTable(sheetIds);

  const groupedInvoices = allInvoices.reduce((acc: { [invoiceNumber: string]: IReceivableItemDocPopulated[] }, i) => {
    acc[i.invoiceNumber] = acc[i.invoiceNumber] || [];
    acc[i.invoiceNumber].push(i);
    return acc;
  }, {});

  const getInvoiceDifference = (invoices: Partial<IReceivableItemDocPopulated>[]): IPartiallyPaidInvoice => {
    const invoice1 = invoices[0];
    const invoice2 = invoices[invoices.length - 1];
    return {
      invoiceNumber: invoice1.invoiceNumber,
      invoiceDate: invoice1.invoiceDate,
      customer: invoice1.customerTitle,
      originalAmount: invoice1.invoiceAmount,
      restatedAmount: invoice2.invoiceAmount,
      changeCounter: invoices.length - 1,
      lastChangeDate: invoice2.bbc.bbcDate,
      difference: +Math.abs(invoice1.invoiceAmount - invoice2.invoiceAmount).toFixed(3),
    };
  };

  const getInvoiceChain = (invoices: Partial<IReceivableItemDoc>[]) => {
    let firstInvoice: Partial<IReceivableItemDoc> = null;
    let lastInvoice: Partial<IReceivableItemDoc> = null;
    const invoiceChain = [];
    let lastMatchIndex = 0;
    let shouldSkip = false;
    invoices.forEach((invoice, index) => {
      if (shouldSkip) {
        return;
      }
      if (firstInvoice === null) {
        firstInvoice = { ...invoice };
        invoiceChain.push(firstInvoice);
        return;
      }

      lastInvoice = { ...invoice };

      if (
        lastInvoice.invoiceAmount === firstInvoice.invoiceAmount ||
        lastInvoice.invoiceAmount === invoices[index - 1].invoiceAmount
      ) {
        lastMatchIndex = index;
        return;
      }

      if (
        lastInvoice.invoiceAmount > firstInvoice.invoiceAmount &&
        lastInvoice.invoiceAmount > invoices[index - 1].invoiceAmount ||
        lastInvoice.invoiceAmount < firstInvoice.invoiceAmount &&
        lastInvoice.invoiceAmount < invoices[index - 1].invoiceAmount
      ) {
        invoiceChain.push(lastInvoice);
        lastMatchIndex = index;
        return;
      }

      if (
        lastInvoice.invoiceAmount > firstInvoice.invoiceAmount &&
        lastInvoice.invoiceAmount < invoices[index - 1].invoiceAmount ||
        lastInvoice.invoiceAmount < firstInvoice.invoiceAmount &&
        lastInvoice.invoiceAmount > invoices[index - 1].invoiceAmount
      ) {
        shouldSkip = true;
        return;
      }
    });
    return { invoiceChain, lastMatchIndex };
  };

  const megaChain = [];
  const suspiciousInvoices: IPartiallyPaidInvoice[] = [];
  const restatedInvoices: IPartiallyPaidInvoice[] = [];
  const partiallyPaidInvoices: IPartiallyPaidInvoice[] = [];

  Object.keys(groupedInvoices).forEach((invoiceNumber) => {
    const invoices = [...groupedInvoices[invoiceNumber]];
    const invoiceChains: Partial<IReceivableItemDoc>[][] = [];
    let totalIndex = 0;
    while (totalIndex !== invoices.length - 1) {
      const { invoiceChain, lastMatchIndex } = getInvoiceChain([...invoices].slice(totalIndex));
      invoiceChains.push(invoiceChain);
      totalIndex = totalIndex + lastMatchIndex;
    }
    megaChain.push(invoiceChains);
  });

  megaChain.forEach((chains) => {
    if (chains.length > 1) {
      chains.forEach((chain) => {
        const diff = getInvoiceDifference(chain);
        suspiciousInvoices.push(diff);
      });
    } else {
      chains.forEach((chain) => {
        const diff = getInvoiceDifference(chain);
        if (diff.originalAmount - diff.restatedAmount > 0) {
          partiallyPaidInvoices.push(diff);
        } else if (diff.originalAmount - diff.restatedAmount < 0) {
          restatedInvoices.push(diff);
        }
      });
    }
  });
  return { suspiciousInvoices, restatedInvoices, partiallyPaidInvoices };
};

const getPartialPaidHeader = () => {
  return {
    invoiceNumber: 'Invoice number',
    customer: 'Customer',
    originalAmount: 'Original amount',
    restatedAmount: 'Last amount',
    difference: 'Difference',
    lastChangeDate: 'Last change date',
    changeCounter: 'Changed',
  };
};
