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

import { BorrowerModel } from '../models/Borrower.model';
import { defaultDateFormat, convertDateToFormat, isProductActive } from '../helpers/date.helper';
import { IInventoryAvailabilitySummary } from '../models/InventoryAvailability.model';
import { IReserveSummary, IReserveSummaryItem } from '../models/Reserve.model';
import { IAvailabilitySummary } from '../models/ReceivableAvailability.model';
import { IEquipmentSummary } from '../models/Equipment.model';
import { ELoanTypes } from '../enums/loan-types.enum';
import { EReserveTypes } from '../enums/reserve-types.enum';
import { EEquipmentTypes } from '../enums/equipment-types.enum';
import { ELedgerReportType } from '../db/loan-statement.db';
import { getLedger, ReportLedgerOptions, ReportLedgerParams } from '../db/reports.db';

import {
  IInvestorSummaryDataRow,
  InvestorSummaryService,
} from './reports/investor-summary.service';
import { defaultPDFPageMarginSize } from './pdf.service';
import { AvailabilityService } from './availability.service';
import { BorrowerService } from './borrowers.service';
import { CollateralAdjustmentsService } from './collateral-adjustments.service';
import { CollateralsService } from './collaterals.service';
import { EquipmentService } from './equipment.service';
import { LoanChargesService } from './loan-charges.service';
import { LoanTransactionsService } from './loan-transactions.service';
import { SignsService } from './signs.service';
import { UploadsService } from './uploads.service';

export interface IBankReportData {
  borrowerId?: string;
  borrowerName: string;
  productCode: string;
  productType: string;
  commitment: number;
  startDate: Date;
  maturityDate: Date;
  inventoryGrossValue: number;
  inventoryAdvanceRate: number;
  inventoryReserves: number;
  inventoryNet: number;
  ARGross: number;
  ARAdvanceRate: number;
  ARReserves: number;
  ARNet: number;
  otherReserves: number;
  collateralAdjustments: number;
  totalCollateral: number;
  loanBalanceRevolver: number;
  netRevolverAvailability: number;
  equipmentCollateral: number;
  intellectualProperty: number;
  realEstate: number;
  bootCollateral: number;
  termLoanReserves: number;
  loanTermBalance: number;
  netTermAvailability: number;
  participantBalance: number;
  NAISC: string;
}

interface IReportDataItem {
  name: string;
  grossValue: number;
  advanceRate: number;
  netValue: number;
}

interface ISummaryReportDataGroup {
  NAISCCode: string;
  isExternalAppraisal: boolean;
  isCompliant: boolean;
  productCode: string;
  items: IReportDataItem[];
}

interface ISummaryReportData {
  borrowerName: string;
  bbcDate: Date;
  revolverDataGroup: ISummaryReportDataGroup;
  termDataGroup: ISummaryReportDataGroup;
}

const createReportItemFromInventory = (item: IInventoryAvailabilitySummary): IReportDataItem => {
  return {
    name: item.inventoryName,
    grossValue: -item.totalValue,
    advanceRate: item.advanceRate * 100,
    netValue: -item.availability,
  };
};

const createReportItemFromEquipment = (items: IEquipmentSummary): IReportDataItem[] => {
  const titleMap = {
    EQUIPMENT: 'Equipment',
    INTELLECTUAL_PROPERTY: 'Intellectual property',
    REAL_ESTATE: 'Real estate',
    BOOT_COLLATERAL: 'Boot collateral',
  };
  return Object.entries(titleMap).map(([key, title]) => {
    return <IReportDataItem>{
      name: title,
      grossValue: items[key].amount,
      advanceRate: items[key].advanceRate,
      netValue: items[key].availability,
    };
  });
};

const createReportItemFromReserve = (item: IReserveSummaryItem, reserveName: string): IReportDataItem => {
  return {
    name: reserveName,
    grossValue: 0,
    advanceRate: 0,
    netValue: -item.amount,
  };
};

const createReportItemsFromReceivable = (receivable: IAvailabilitySummary): {
  arUninsured: IReportDataItem,
  arInsured: IReportDataItem
} => {
  const getArUninsured = () => {
    return receivable
      ? {
        grossValue: receivable.uninsuredComponent,
        advanceRate: receivable.uninsuredComponent === 0 ? 0 : new Decimal(receivable.uninsuredAvailability).div(receivable.uninsuredComponent).mul(100).toDP(2).toNumber(),
        netValue: receivable.uninsuredAvailability,
      }
      : {
        grossValue: 0,
        advanceRate: 0,
        netValue: 0,
      };
  };
  const getArInsured = () => {
    return receivable
      ? {
        grossValue: receivable.insuredComponent,
        advanceRate: receivable.insuredComponent === 0 ? 0 : new Decimal(receivable.insuredAvailability).div(receivable.insuredComponent).mul(100).toDP(2).toNumber(),
        netValue: receivable.insuredAvailability,
      }
      : {
        grossValue: 0,
        advanceRate: 0,
        netValue: 0,
      };
  };
  return {
    arUninsured: {
      name: 'AR Uninsured',
      ...getArUninsured(),
    },
    arInsured: {
      name: 'AR Insured',
      ...getArInsured(),
    },
  };
};

const createTotalRows = (allResults: IReportDataItem[], productType: 'TERM' | 'REVOLVER', loanBalance: number) => {
  const productTypeTitles = {
    REVOLVER: {
      totalCollateral: 'TOTAL REVOLVING COLLATERAL',
      loanBalance: 'LOAN BALANCE (Revolving products only)',
      netAvailability: 'NET REVOLVER AVAILABILITY',
    },
    TERM: {
      totalCollateral: 'TOTAL TERM COLLATERAL',
      loanBalance: 'LOAN BALANCE (Term products only)',
      netAvailability: 'NET TERM AVAILABILITY',
    },
  };
  const totalCollateralRow = allResults.reduce((acc, item) => {
    acc.grossValue = new Decimal(acc.grossValue).plus(item.grossValue).toNumber();
    acc.netValue = new Decimal(acc.netValue).plus(item.netValue).toNumber();
    acc.advanceRate = item.grossValue === 0 ? 0 : new Decimal(acc.netValue).div(item.grossValue).toDP(2).toNumber();
    return acc;
  }, <IReportDataItem>{
    name: productTypeTitles[productType].totalCollateral,
    grossValue: 0,
    advanceRate: 0,
    netValue: 0,
  });
  const loanBalanceRow: IReportDataItem = {
    name: productTypeTitles[productType].loanBalance,
    grossValue: loanBalance,
    advanceRate: 0,
    netValue: loanBalance,
  };
  const netAvailabilityRow: IReportDataItem = {
    name: productTypeTitles[productType].netAvailability,
    grossValue: 0,
    advanceRate: 0,
    netValue: new Decimal(totalCollateralRow.netValue).sub(loanBalanceRow.netValue).toNumber(),
  };
  return { totalCollateralRow, loanBalanceRow, netAvailabilityRow };
};

const getInventoryGrossValue = (inventory: IInventoryAvailabilitySummary[]) => {
  const foundInv = inventory?.find((s) => s.inventoryName === 'TOTAL');
  if (foundInv) {
    return foundInv.totalValue;
  }
  return 0;
};

const getInventoryAdvanceRate = (inventory: IInventoryAvailabilitySummary[]) => {
  const foundInv = inventory?.find((s) => s.inventoryName === 'TOTAL');
  if (foundInv) {
    return foundInv.availability;
  }
  return 0;
};

const getInventoryReservesValue = (reserve: IReserveSummary) => {
  return reserve[EReserveTypes.INVENTORY]?.amount ?? 0;
};

const getARGross = (receivable: IAvailabilitySummary) => {
  return !receivable ? 0 : new Decimal(receivable.uninsuredComponent || 0).add(receivable.insuredComponent || 0).toNumber();
};

const getARAdvanceRate = (receivable: IAvailabilitySummary) => {
  return !receivable ? 0 : new Decimal(receivable.uninsuredAvailability || 0).add(receivable.insuredAvailability || 0).toNumber();
};

const getOtherValue = (reserves: IReserveSummary) => {
  return Object
    .entries(reserves)
    .reduce((acc, [key, el]) => {
      if (key === EReserveTypes.EQUIPMENT ||
        key === EReserveTypes.INVENTORY ||
        key === EReserveTypes.RECEIVABLES) {
        return acc;
      }
      return new Decimal(acc || 0).sub(el.amount).toNumber();
    }, 0);
};



export class ReportsService {

  margin = defaultPDFPageMarginSize;

  constructor(
    private readonly availabilityService: AvailabilityService,
    private readonly borrowerService: BorrowerService,
    private readonly collateralAdjustmentsService: CollateralAdjustmentsService,
    private readonly collateralsService: CollateralsService,
    private readonly equipmentService: EquipmentService,
    private readonly loanChargesService: LoanChargesService,
    private readonly loanTransactionsService: LoanTransactionsService,
    private readonly signsService: SignsService,
    private readonly uploadsService: UploadsService,
    private readonly investorSummaryService: InvestorSummaryService,
  ) {
  }

  async getCollateralAdjustmentsForLastSignedBBC(borrowerId: string, date: Date) {
    const lastSignedBBC = await this.signsService.getLatestSignedBBCDate(borrowerId, date);
    if (!lastSignedBBC) {
      return 0;
    }
    const summary = await this.collateralAdjustmentsService.getCollateralAdjustmentSummary(lastSignedBBC._id.toString());
    return Object.values(summary).reduce((acc, summaryRow) => (new Decimal(acc).add(summaryRow.amount).toNumber()), 0);
  };

  private async getProductBalances(productId: string, isRevolver: boolean, date: Date) {
    const lastTransaction = await this.loanTransactionsService.getLastTransactionForDate(productId, date);
    const balance = lastTransaction ? lastTransaction.balance : 0;
    const availability = -balance;
    const balances: Partial<IBankReportData> = {
      loanBalanceRevolver: isRevolver ? balance : 0,
      netRevolverAvailability: isRevolver ? availability : 0,
      loanTermBalance: isRevolver ? 0 : balance,
      netTermAvailability: isRevolver ? 0 : availability,
    };
    return balances;
  };

  public async getBorrowerAndProducts(borrowerId: string, date: Date, useSignedBBC = false, requireBBC = true) {
    const borrower = await BorrowerModel.findById(borrowerId);
    if (!borrower) {
      return null;
    }

    const latestBBC = useSignedBBC
      ? await this.signsService.getLatestSignedBBCDate(borrowerId, date)
      : await this.collateralsService.getLatestBBC(borrowerId, date);
    if (!latestBBC && requireBBC) {
      return null;
    }

    const summaries = latestBBC ? await this.availabilityService.getAllSummaries(latestBBC._id.toString(), null) : null;

    const products = await this.loanChargesService.getLoanProducts(borrowerId);
    return { borrower, latestBBC, summaries, products };
  }

  public async generatePortfolioSummary(borrowerIds: string[], date: Date) {
    if (borrowerIds.length === 0) {
      return {};
    }
    const allResults: { [borrowerId: string]: ISummaryReportData } = {};
    await Promise.all(borrowerIds.map(async (borrowerId) => {
      const borrowerData = await this.getBorrowerAndProducts(borrowerId, date, true);
      if (!borrowerData) {
        return;
      }
      const { borrower, latestBBC, summaries, products } = borrowerData;
      if (!borrower) {
        return;
      }
      const revolverProduct = products.find((product) => product.type === ELoanTypes.REVOLVER && isProductActive(product.startDate, product.deactivationDate, date));
      const termProduct = products.find((product) => product.type === ELoanTypes.TERM);

      let revolverDataGroup: ISummaryReportDataGroup = null;

      if (revolverProduct) {
        const inventoryItems = summaries.inventory.map(createReportItemFromInventory);
        const inventoryReserve = createReportItemFromReserve(summaries.reserve['INVENTORY'], 'Inventory Reserves');
        const ARReserve = createReportItemFromReserve(summaries.reserve['RECEIVABLES'], 'AR Reserves');
        const otherReserve = createReportItemFromReserve(summaries.reserve['OTHER'], 'Other non-equipment reserves');
        const { arUninsured, arInsured } = createReportItemsFromReceivable(summaries.receivable);
        const revolverItems = [...inventoryItems, inventoryReserve, arUninsured, arInsured, ARReserve, otherReserve];
        const revolverBalance = await this.loanChargesService.getLoanProductBalance(revolverProduct._id.toString(), date);
        const {
          totalCollateralRow: totalCollateralRowRevolver,
          loanBalanceRow: loanBalanceRowRevolver,
          netAvailabilityRow: netAvailabilityRowRevolver,
        } = createTotalRows(revolverItems, 'REVOLVER', revolverBalance.balance);
        revolverDataGroup = {
          NAISCCode: '',
          productCode: '',
          isCompliant: false,
          isExternalAppraisal: false,
          items: [...revolverItems, totalCollateralRowRevolver, loanBalanceRowRevolver, netAvailabilityRowRevolver],
        };
      }

      let termDataGroup: ISummaryReportDataGroup = null;
      if (termProduct) {
        const equipmentItems = createReportItemFromEquipment(summaries.equipment);
        const termReserve = createReportItemFromReserve(summaries.reserve['EQUIPMENT'], 'Term Loan Reserves (total)');
        const termItems = [...equipmentItems, termReserve];
        const termBalance = await this.loanChargesService.getLoanProductBalance(termProduct._id.toString(), date);
        const {
          totalCollateralRow: totalCollateralRowTerm,
          loanBalanceRow: loanBalanceRowTerm,
          netAvailabilityRow: netAvailabilityRowTerm,
        } = createTotalRows(termItems, 'TERM', termBalance.balance);
        termDataGroup = {
          NAISCCode: '',
          productCode: '',
          isCompliant: false,
          isExternalAppraisal: false,
          items: [...termItems, totalCollateralRowTerm, loanBalanceRowTerm, netAvailabilityRowTerm],
        };
      }
      allResults[borrowerId] = {
        borrowerName: borrower.name,
        bbcDate: latestBBC.bbcDate,
        revolverDataGroup,
        termDataGroup,
      };
    }));
    return allResults;
  }

  public async getPortfolioSummaryExcel(borrowerIds: string[], date: Date) {
    const report = await this.generatePortfolioSummary(borrowerIds, date);
    const emptyRow = [['']];
    const reportHeader = [
      ['PORTFOLIO LOAN BALANCE AND COLLATERAL REPORT'],
      [`RUN DATE ${convertDateToFormat(date)}`],
      [''],
    ];
    const reportToCovert = Object.values(report).reduce((acc, borrowerData) => {
      const headers = [
        [`LOAN BALANCE AND COLLATERAL REPORT ${borrowerData.borrowerName}, Last BBC date - ${convertDateToFormat(borrowerData.bbcDate)}`],
        ['', 'Gross Value', 'Advance Rate', 'Net Value'],
      ];
      const convertToExcel = (dataGroup: ISummaryReportDataGroup) => {
        if (!dataGroup) {
          return [];
        }
        return dataGroup.items.reduce((acc, revolverItem: IReportDataItem) => {
          if (revolverItem.name === 'TOTAL') {
            return acc;
          }
          return [...acc, [...Object.values(revolverItem).map((v) => v === 0 ? '-' : v)]];
        }, []);
      };
      const revolverData = convertToExcel(borrowerData.revolverDataGroup);
      const termData = convertToExcel(borrowerData.termDataGroup);
      return [...acc, ...headers, ...revolverData, ...emptyRow, ...termData, ...emptyRow, ...emptyRow];
    }, [...reportHeader]);
    return await this.uploadsService.convertDataToFile([{ portfolio: reportToCovert }]);
  }

  public async generateBankPortfolioSummary(borrowerIds: string[], date: Date, {
    useSignedBBC,
    useStoredBalance,
  } = { useSignedBBC: true, useStoredBalance: false }) {
    const dateForBalance = dayjs.utc(date).endOf('day').toDate();
    const productsGrouped = await Promise.all(borrowerIds.map(async (borrowerId) => {
      const borrowerData = await this.getBorrowerAndProducts(borrowerId, date, useSignedBBC, false);
      if (!borrowerData) {
        return [];
      }

      const { borrower, summaries, products } = borrowerData;
      if (!borrower) {
        return [];
      }

      const borrowerSettings = await this.borrowerService.getBorrowerSettings(borrowerId);
      const borrowerOptions = borrowerSettings.data.options;
      const NAISCCodeOption = borrowerOptions.find((option) => option['dataName'] === 'NAISC Code');
      const NAISCCode = NAISCCodeOption ? NAISCCodeOption.dataValue as string : '';

      const hasTermProduct = products.some((product) => product.type === ELoanTypes.TERM);
      const manyTermProduct = products.filter((product) => product.type === ELoanTypes.TERM).length > 1;

      return await Promise.all(products.map(async (product) => {
        if (product.masterLoanProductId != null) {
          return null as IBankReportData;
        }
        if (dayjs(product.deactivationDate).isBefore(dayjs(date))) {
          return null as IBankReportData;
        }
        if (dayjs(product.startDate).isAfter(dayjs(date))) {
          return null as IBankReportData;
        }
        const isRevolver = product.type === ELoanTypes.REVOLVER;
        const productMainData: Partial<IBankReportData> = {
          borrowerId,
          borrowerName: borrower.name,
          productCode: product.code,
          productType: product.type,
          commitment: product.commitment,
          startDate: product.startDate,
          maturityDate: product.maturityDate,
          NAISC: NAISCCode,
          ...(await this.getProductBalances(product._id.toString(), isRevolver, dateForBalance)),
        };
        if (!summaries) {
          return {
            inventoryGrossValue: 0,
            inventoryAdvanceRate: 0,
            inventoryReserves: 0,
            inventoryNet: 0,
            ARGross: 0,
            ARAdvanceRate: 0,
            ARReserves: 0,
            ARNet: 0,
            otherReserves: 0,
            collateralAdjustments: 0,
            totalCollateral: 0,
            loanBalanceRevolver: 0,
            netRevolverAvailability: 0,

            equipmentCollateral: 0,
            intellectualProperty: 0,
            realEstate: 0,
            bootCollateral: 0,
            termLoanReserves: 0,
            loanTermBalance: 0,
            netTermAvailability: 0,
            participantBalance: 0,
            ...(await this.getProductBalances(product._id.toString(), isRevolver, dateForBalance)),
            ...productMainData,
          } as IBankReportData;
        } else {

          const balances = await this.getProductBalances(product._id.toString(), isRevolver, dateForBalance);
          if (useStoredBalance) {
            const bbc = await this.collateralsService.getLatestBBC(borrowerId, date);
            if (bbc) {
              const savedData = await this.signsService.getSavedData(bbc._id.toString());
              if (savedData.loanBalances?.REVOLVER) {
                balances.loanBalanceRevolver = savedData.loanBalances?.REVOLVER;
                balances.loanTermBalance = savedData.loanBalances?.TERM;
              }
            }
          }
          const inventoryGrossValue = isRevolver ? getInventoryGrossValue(summaries.inventory) : 0;
          const inventoryAdvanceRate = isRevolver ? getInventoryAdvanceRate(summaries.inventory) : 0;
          const inventoryReserves = isRevolver ? getInventoryReservesValue(summaries.reserve) : 0;
          const inventoryNet = new Decimal(inventoryAdvanceRate).sub(inventoryReserves).toNumber();
          const ARGross = isRevolver ? getARGross(summaries.receivable) : 0;
          const ARAdvanceRate = isRevolver ? getARAdvanceRate(summaries.receivable) : 0;
          const ARReserves = isRevolver ? summaries.reserve[EReserveTypes.RECEIVABLES].amount : 0;
          const ARNet = isRevolver ? (summaries.receivable?.ARAvailability || 0) : 0;
          const otherReserves = isRevolver ? getOtherValue(summaries.reserve) : 0;
          const collateralAdjustments = isRevolver ? await this.getCollateralAdjustmentsForLastSignedBBC(borrowerId, date) : 0;
          const totalCollateral = new Decimal(inventoryAdvanceRate).sub(inventoryReserves).add(ARNet).add(otherReserves).add(collateralAdjustments).toNumber();
          const loanBalanceRevolver = isRevolver ? balances.loanBalanceRevolver : 0;
          const netRevolverAvailability = new Decimal(totalCollateral).sub(loanBalanceRevolver).toNumber();

          const productEquipmentSummary = (isRevolver && hasTermProduct)
            ? null
            : manyTermProduct
              ? (await this.equipmentService.getLatestEquipmentForDate(borrowerId, date)).summary
              : await this.equipmentService.getEquipmentSummaryForProduct(borrowerId, date, product._id.toString());

          const equipmentCollateral = productEquipmentSummary ? productEquipmentSummary[EEquipmentTypes.EQUIPMENT].amount : 0;
          const intellectualProperty = productEquipmentSummary ? productEquipmentSummary[EEquipmentTypes.INTELLECTUAL_PROPERTY].amount : 0;
          const realEstate = productEquipmentSummary ? productEquipmentSummary[EEquipmentTypes.REAL_ESTATE].amount : 0;
          const bootCollateral = productEquipmentSummary ? productEquipmentSummary[EEquipmentTypes.BOOT_COLLATERAL].amount : 0;
          const termLoanReserves = !(isRevolver && hasTermProduct) ? summaries.reserve[EReserveTypes.EQUIPMENT].amount : 0;
          const loanTermBalance = !isRevolver ? balances.loanTermBalance : 0;
          const netTermAvailability = new Decimal(equipmentCollateral).sub(termLoanReserves).sub(loanTermBalance).toNumber();

          const participantBalance = await this.loanChargesService.getParticipationBalance(product.code, date);

          return {
            ...productMainData,
            inventoryGrossValue,
            inventoryAdvanceRate,
            inventoryReserves,
            inventoryNet,
            ARGross,
            ARAdvanceRate,
            ARReserves,
            ARNet,
            otherReserves,
            collateralAdjustments,
            totalCollateral,
            loanBalanceRevolver,
            netRevolverAvailability,

            equipmentCollateral,
            intellectualProperty,
            realEstate,
            bootCollateral,
            termLoanReserves,
            loanTermBalance,
            netTermAvailability,
            participantBalance,
          } as IBankReportData;
        }
      }));
    }));
    return productsGrouped
      .reduce((acc, group) => [...acc, ...group], [])
      .filter((report) => !!report);
  }

  public async getBalancesCollateralReportExcel(borrowerIds: string[], date: Date) {
    const reportHeader = [
      ['PORTFOLIO LOAN BALANCE AND COLLATERAL REPORT'],
      [`RUN DATE ${convertDateToFormat(date)}`],
      [''],
    ];

    const keyOrder = [
      'borrowerName',
      'productCode',
      'productType',
      'commitment',
      'startDate',
      'maturityDate',
      'inventoryGrossValue',
      'inventoryAdvanceRate',
      'inventoryReserves',
      'inventoryNet',
      'ARGross',
      'ARAdvanceRate',
      'ARReserves',
      'ARNet',
      'otherReserves',
      'collateralAdjustments',
      'totalCollateral',
      'loanBalanceRevolver',
      'netRevolverAvailability',
      'equipmentCollateral',
      'intellectualProperty',
      'realEstate',
      'bootCollateral',
      'termLoanReserves',
      'loanTermBalance',
      'netTermAvailability',
      'participantBalance',
      'NAISC',
    ];

    const tableHeader: { [K in keyof IBankReportData]: string } = {
      borrowerName: 'Borrower name',
      productCode: 'Product code',
      productType: 'Product type',
      commitment: 'Commitment',
      startDate: 'Start Date',
      maturityDate: 'Maturity Date',
      inventoryGrossValue: 'Inventory Gross Value',
      inventoryAdvanceRate: 'Inventory @ Advance Rate',
      inventoryReserves: 'Inventory Reserves',
      inventoryNet: 'Inventory Net',
      ARGross: 'AR Gross',
      ARAdvanceRate: 'AR Advance Rate',
      ARReserves: 'AR Reserves',
      ARNet: 'AR Net',
      otherReserves: 'Other Reserves',
      collateralAdjustments: 'Collateral Adjustments',
      totalCollateral: 'Total Revolver Collateral',
      loanBalanceRevolver: 'LOAN BALANCE Revolver',
      netRevolverAvailability: 'Available to borrow',
      equipmentCollateral: 'Equipment Collateral',
      intellectualProperty: 'Intellectual Property',
      realEstate: 'Real Estate',
      bootCollateral: 'Boot Collateral',
      termLoanReserves: 'Term Loan Reserves (total)',
      loanTermBalance: 'LOAN BALANCE (Term products only)',
      netTermAvailability: 'NET TERM AVAILABILITY',
      participantBalance: 'Participant balance',
      NAISC: 'NAISC Code',
    };

    const reportData = await this.generateBankPortfolioSummary(borrowerIds, date, {
      useSignedBBC: true,
      useStoredBalance: false,
    });
    const headerArray = Object.values(reorderObject(tableHeader, keyOrder));
    const dataAsArrays = reportData.map((row) => Object.values(reorderObject(row, keyOrder)));
    return await this.uploadsService.convertDataToFile([{ portfolio: [...reportHeader, headerArray, ...dataAsArrays] }]);
  }

  async getLedger(productIds: string[], startDate: Date, endDate: Date, reportType: ELedgerReportType, showFloatedBalance = true) {
    const params: ReportLedgerParams = {
      endDate,
      productIds,
      reportType,
      startDate,
    };
    const options: ReportLedgerOptions = { showBalances: showFloatedBalance, showFloatedBalance };
    return getLedger(params, options);
  }

  async getInvestorSummaryReport(selectedBorrowerIds: string[], start: Date, end: Date) {
    const startFormatted = dayjs(start).format(defaultDateFormat);
    const endFormatted = dayjs(end).format(defaultDateFormat);
    const keyOrder = [
      'clientName',
      'productName',
      'maturityDate',
      'commitmentAmount',
      'balanceEnd',
      'participationBalanceEnd',
      'companyExposure',
      'netAvailable',
      'collectionsBetweenStartEnd',
      'disbursementsBetweenStartEnd',
      'availableToBorrow',
      'ARNet',
      'inventoryNet',
      'otherCollateral',
      'otherReservesAndAdjustments',
      'totalRevolverCollateral',
      'collectionsBetweenBeginningEnd',
      'disbursementsBetweenBeginningEnd',
    ];

    const header = [
      ['Report period'],
      ['From', startFormatted],
      ['To', endFormatted],
      [''],
    ];

    const tableHeader: { [K in keyof IInvestorSummaryDataRow]: string } = {
      clientName: 'Client name',
      productName: 'Product name',
      maturityDate: 'Maturity date',
      commitmentAmount: 'Commitment amount',
      balanceEnd: `Balance as at ${endFormatted}`,
      participationBalanceEnd: `Participation balance as at ${endFormatted}`,
      companyExposure: 'Exposure',
      netAvailable: 'Net available',
      collectionsBetweenStartEnd: `Collections between ${startFormatted} & ${endFormatted}`,
      disbursementsBetweenStartEnd: `Disbursements between ${startFormatted} & ${endFormatted}`,
      netRevolverAvailability: 'Available to borrow',
      ARNet: 'AR net',
      inventoryNet: 'Inventory net',
      otherCollateral: 'Other collateral (sum of equipment, ip, real estate, boot )',
      otherReservesAndAdjustments: 'Other Reserves & Adjustments',
      totalRevolver: 'Total revolver collateral + total equipment & ip collateral)',
      collectionsBetweenBeginningEnd: `Collections between beginning to ${endFormatted}`,
      disbursementsBetweenBeginningEnd: `Disbursements between beginning to ${endFormatted}`,
    };

    const data = await this.investorSummaryService.getInvestorSummaryData(selectedBorrowerIds, start, end);
    const headerArray = Object.values(reorderObject(tableHeader, keyOrder));
    const dataAsArrays = data.map((row) => Object.values(reorderObject(row, keyOrder)));
    return [{ report: [...header, headerArray, ...dataAsArrays] }];
  }
}

function reorderObject(obj: object, keyOrder: string[]): object {
  const reorderedObject: object = {};
  keyOrder.forEach(key => {
    if (Object.hasOwn(obj, key)) {
      reorderedObject[key] = obj[key];
    }
  });
  return reorderedObject;
}
