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

import { defaultDateFormat } from '../../helpers/date.helper';
import { BorrowerModel } from '../../models/Borrower.model';
import { ELoanTransactionTypes, ILoanTransactionDoc, LoanTransaction } from '../../models/LoanTransaction.model';
import { ELoanTypes } from '../../enums/loan-types.enum';
import { IBankReportData, ReportsService } from '../reports.service';
import { LoanChargesService } from '../loan-charges.service';

export interface IInvestorSummaryDataRow {
  clientName: string;
  productName: string;
  maturityDate: string;
  commitmentAmount: number;
  balanceEnd: number;
  participationBalanceEnd: number;
  companyExposure: number;
  netAvailable: number;
  collectionsBetweenStartEnd: number;
  disbursementsBetweenStartEnd: number;
  netRevolverAvailability: number;
  ARNet: number;
  inventoryNet: number;
  otherCollateral: number;
  otherReservesAndAdjustments: number;
  totalRevolver: number;
  collectionsBetweenBeginningEnd: number;
  disbursementsBetweenBeginningEnd: number;
}

const getFirstTransactionDate = async (productId: string, transactionType: ELoanTransactionTypes) => {
  const firstDocs = await LoanTransaction
    .find({ productId: new mongoose.Types.ObjectId(productId), transactionType: transactionType })
    .sort('date')
    .limit(1)
    .lean();
  if (!firstDocs.length) {
    return new Date();
  }
  return firstDocs[0].date;
};

const getCollectionBetweenDates = async (productId: string, start: Date, end: Date, transactionType: ELoanTransactionTypes) => {
  const collections = await LoanTransaction.aggregate<ILoanTransactionDoc>([{
    $match: {
      date: { $gte: start, $lte: end },
      productId: new mongoose.Types.ObjectId(productId),
      transactionType: transactionType,
    },
  }]);
  return collections.reduce((sum, collection) => new Decimal(collection.amount).abs().add(sum).toNumber(), 0);
};

const emptySummary: IBankReportData = {
  ARAdvanceRate: 0,
  ARGross: 0,
  ARNet: 0,
  ARReserves: 0,
  NAISC: '',
  bootCollateral: 0,
  borrowerName: '',
  collateralAdjustments: 0,
  commitment: 0,
  equipmentCollateral: 0,
  intellectualProperty: 0,
  inventoryAdvanceRate: 0,
  inventoryGrossValue: 0,
  inventoryNet: 0,
  inventoryReserves: 0,
  loanBalanceRevolver: 0,
  loanTermBalance: 0,
  maturityDate: undefined,
  netRevolverAvailability: 0,
  netTermAvailability: 0,
  otherReserves: 0,
  participantBalance: 0,
  productCode: '',
  productType: '',
  realEstate: 0,
  startDate: undefined,
  termLoanReserves: 0,
  totalCollateral: 0,
};


export class InvestorSummaryService {

  constructor(
    private readonly loanChargesService: LoanChargesService,
    private readonly getReportsService: () => ReportsService,
  ) {
  }

  async getInvestorSummaryData(borrowerIds: string[], start: Date, end: Date) {
    const data: IInvestorSummaryDataRow[] = [];
    const reportsService = this.getReportsService();
    const borrowerSummaries = await reportsService.generateBankPortfolioSummary(borrowerIds, end);
    await Promise.all(borrowerIds.map(async (borrowerId) => {
      const borrower = await BorrowerModel.findById(borrowerId).lean();
      const borrowerProducts = await this.loanChargesService.getLoanProducts(borrowerId);
      await Promise.all(borrowerProducts.filter((product) => product.masterLoanProductId == null).map(async (product) => {
        const productId = product._id.toString();
        const isRevolver = product.type === ELoanTypes.REVOLVER;
        const { balance: balanceEnd } = await this.loanChargesService.getLoanProductBalance(productId, end);
        const collectionsBetweenStartEnd = await getCollectionBetweenDates(productId, start, end, ELoanTransactionTypes.COLLECTION);
        const disbursementsBetweenStartEnd = await getCollectionBetweenDates(productId, start, end, ELoanTransactionTypes.DISBURSEMENT);
        const collectionsBeginning = await getFirstTransactionDate(productId, ELoanTransactionTypes.COLLECTION);
        const disbursementBeginning = await getFirstTransactionDate(productId, ELoanTransactionTypes.COLLECTION);
        const collectionsBetweenBeginningEnd = await getCollectionBetweenDates(productId, collectionsBeginning, end, ELoanTransactionTypes.COLLECTION);
        const disbursementsBetweenBeginningEnd = await getCollectionBetweenDates(productId, disbursementBeginning, end, ELoanTransactionTypes.DISBURSEMENT);
        const participationBalanceEnd = await this.loanChargesService.getParticipationBalance(product.code, end);
        const companyExposure = new Decimal(balanceEnd).sub(participationBalanceEnd).toNumber();

        const productSummary = borrowerSummaries.find((summary) => summary.productCode === product.code) || emptySummary;
        const netRevolverAvailability = productSummary.netRevolverAvailability;
        const ARNet = productSummary.ARNet;
        const inventoryNet = productSummary.inventoryNet;
        const otherCollateral = new Decimal(productSummary.equipmentCollateral).add(productSummary.intellectualProperty).add(productSummary.realEstate).add(productSummary.bootCollateral).toNumber();
        const otherReserves = isRevolver ? productSummary.otherReserves : 0;
        const collateralAdjustments = isRevolver ? await reportsService.getCollateralAdjustmentsForLastSignedBBC(borrowerId, end) : 0;
        const otherReservesAndAdjustments = new Decimal(otherReserves).add(collateralAdjustments).toNumber();
        const totalRevolver = new Decimal(productSummary.totalCollateral).add(productSummary.equipmentCollateral).add(productSummary.intellectualProperty).toNumber();
        const netAvailable = new Decimal(inventoryNet).add(ARNet).add(otherCollateral).add(otherReservesAndAdjustments).minus(balanceEnd).toNumber();
        const newRow: IInvestorSummaryDataRow = {
          clientName: borrower.name,
          productName: product.name,
          maturityDate: dayjs(product.maturityDate).format(defaultDateFormat),
          commitmentAmount: product.commitment,
          balanceEnd,
          participationBalanceEnd,
          companyExposure,
          netAvailable,
          collectionsBetweenStartEnd,
          disbursementsBetweenStartEnd,
          netRevolverAvailability,
          ARNet,
          inventoryNet,
          otherCollateral,
          otherReservesAndAdjustments,
          totalRevolver,
          collectionsBetweenBeginningEnd,
          disbursementsBetweenBeginningEnd,
        };
        data.push(newRow);
      }));
    }));
    return data
      .sort((a, b) => {
        const borrowerComparison = a.clientName.localeCompare(b.clientName);
        if (borrowerComparison !== 0) {
          return borrowerComparison;
        } else {
          return a.productName.localeCompare(b.productName);
        }
      });
  };

}
