"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TermLoanService = void 0;
const lodash_1 = __importDefault(require("lodash"));
const dayjs_1 = __importDefault(require("dayjs"));
const decimal_js_1 = __importDefault(require("decimal.js"));
const mongoose_1 = __importDefault(require("mongoose"));
const loan_charge_type_enum_1 = require("../enums/loan-charge-type.enum");
const loan_types_enum_1 = require("../enums/loan-types.enum");
const numbers_helper_1 = require("../helpers/numbers.helper");
const TermLoanSettings_model_1 = require("../models/TermLoanSettings.model");
const TermLoan_model_1 = require("../models/TermLoan.model");
const TermLoanCalculated_model_1 = require("../models/TermLoanCalculated.model");
const LoanProducts_model_1 = require("../models/LoanProducts.model");
const financial_indexes_service_1 = require("./financial-indexes.service");
const newEmptySettings = {
    termMonths: 12,
    amortizationProfileMonths: 12,
    gracePeriodMonths: 1,
    principalDueDay: 7,
};
class TermLoanService {
    financialIndexesService;
    loanChargesService;
    getLoanStatementStatusService;
    getLoanTransactionsService;
    getUploadsService;
    constructor(financialIndexesService, loanChargesService, getLoanStatementStatusService, getLoanTransactionsService, getUploadsService) {
        this.financialIndexesService = financialIndexesService;
        this.loanChargesService = loanChargesService;
        this.getLoanStatementStatusService = getLoanStatementStatusService;
        this.getLoanTransactionsService = getLoanTransactionsService;
        this.getUploadsService = getUploadsService;
    }
    async createTermLoan(productId, actual) {
        const product = await this.loanChargesService.getLoanProductById(productId);
        const charges = await this.loanChargesService.getLoanChargeForProduct(productId);
        if (charges.length === 0) {
            return null;
        }
        const adminCharge = charges.find((charge) => charge.chargeType === loan_charge_type_enum_1.ELoanChargeType.ADMIN_FEE);
        const interestCharge = charges.find((charge) => charge.chargeType === loan_charge_type_enum_1.ELoanChargeType.INTEREST_FEE);
        if (!adminCharge || !interestCharge) {
            return null;
        }
        const primeRate = await this.financialIndexesService.getFinancialIndexValue(financial_indexes_service_1.EFinancialIndex.PRIME_RATE);
        const newEmptyTerm = {
            productId: new mongoose_1.default.Types.ObjectId(String(product._id)),
            initialBalance: product.commitment,
            adminRate: adminCharge.percent,
            interestRate: interestCharge.percent,
            loanStartDate: product.startDate,
            primeRate: primeRate,
            actual,
            isMismatched: false,
            calculationStatus: TermLoan_model_1.ETermLoanStatus.NEW,
            lastCalculated: null,
        };
        return TermLoan_model_1.TermLoanModel.findOneAndUpdate({ productId, actual }, newEmptyTerm, { upsert: true, new: true });
    }
    async refreshChargeSettings(productId, actual) {
        const product = await this.loanChargesService.getLoanProductById(productId);
        const charges = await this.loanChargesService.getLoanChargeForProduct(productId);
        if (charges.length === 0) {
            return null;
        }
        const adminCharge = charges.find((charge) => charge.chargeType === loan_charge_type_enum_1.ELoanChargeType.ADMIN_FEE);
        const interestCharge = charges.find((charge) => charge.chargeType === loan_charge_type_enum_1.ELoanChargeType.INTEREST_FEE);
        if (!adminCharge || !interestCharge) {
            return null;
        }
        const newEmptyTerm = {
            productId: new mongoose_1.default.Types.ObjectId(String(product._id)),
            initialBalance: product.commitment,
            adminRate: adminCharge.percent,
            interestRate: interestCharge.percent,
            loanStartDate: product.startDate,
            actual,
        };
        return TermLoan_model_1.TermLoanModel.findOneAndUpdate({ productId, actual }, newEmptyTerm, { upsert: true, new: true });
    }
    async createTermLoanSettings(termLoanId) {
        return TermLoanSettings_model_1.TermLoanSettingsModel.findOneAndUpdate({ termLoanId }, { termLoanId, ...newEmptySettings }, {
            upsert: true,
            new: true,
        });
    }
    async getCreateTermLoan(productId, actual) {
        if (!productId) {
            return null;
        }
        const foundTermLoan = await TermLoan_model_1.TermLoanModel.findOne({ productId, actual }).lean();
        const termLoan = foundTermLoan ? foundTermLoan : await this.createTermLoan(productId, actual);
        if (!termLoan) {
            return null;
        }
        const foundSettings = termLoan ? await TermLoanSettings_model_1.TermLoanSettingsModel.findOne({ termLoanId: termLoan._id }).lean() : null;
        const settings = foundSettings ? foundSettings : await this.createTermLoanSettings(String(termLoan._id));
        const pureTermLoan = lodash_1.default.pick(termLoan, TermLoan_model_1.TERM_LOAN_FIELDS);
        const termLoanFull = {
            ...pureTermLoan,
            settings: lodash_1.default.pick(settings, TermLoanSettings_model_1.TERM_LOAN_SETTINGS_FIELDS),
        };
        return termLoanFull;
    }
    async getTermLoan(productId, actual) {
        const termLoan = await this.getCreateTermLoan(productId, actual);
        if (!termLoan) {
            return null;
        }
        const calculatedTermLoan = await TermLoanCalculated_model_1.TermLoanCalculatedModel
            .find({ termLoanId: termLoan._id })
            .sort({ relevantStatement: 1, openingBalance: -1, monthlyPrincipal: -1 })
            .lean();
        const mappedCalculatedTermLoan = calculatedTermLoan.map((c) => {
            const startDate = termLoan.loanStartDate;
            const minStart = dayjs_1.default.max((0, dayjs_1.default)(startDate), (0, dayjs_1.default)(c.relevantStatement).startOf('month'));
            const period = `${minStart.format('MM/DD/YYYY')} - ${(0, dayjs_1.default)(c.relevantStatement).format('MM/DD/YYYY')}`;
            return {
                ...c,
                period,
            };
        });
        return { ...termLoan, calculated: mappedCalculatedTermLoan };
    }
    async saveTermLoanSettings(productId, actual, settings) {
        await this.refreshChargeSettings(productId, actual);
        let termLoan = await TermLoan_model_1.TermLoanModel.findOne({ productId, actual }).lean();
        if (!termLoan || termLoan.calculationStatus === TermLoan_model_1.ETermLoanStatus.NEW) {
            termLoan = await this.createTermLoan(productId, actual);
        }
        const updatedSettings = await TermLoanSettings_model_1.TermLoanSettingsModel.findOneAndUpdate({ termLoanId: termLoan._id }, settings, { new: true });
        return { productId, settings: lodash_1.default.pick(updatedSettings, TermLoanSettings_model_1.TERM_LOAN_SETTINGS_FIELDS) };
    }
    async calculateAllTermLoans() {
        const products = await LoanProducts_model_1.LoanProduct.find({ type: loan_types_enum_1.ELoanTypes.TERM }).lean();
        for (const product of products) {
            await this.calculateTermLoan(product._id.toString(), true);
        }
    }
    async setTermLoanExpected(productId, recalculate = false) {
        const termLoan = await this.getCreateTermLoan(productId, true);
        if (termLoan) {
            await this.updateTermLoanStatus(termLoan._id.toString(), TermLoan_model_1.ETermLoanStatus.EXPECTED);
            if (recalculate) {
                await this.calculateTermLoan(productId, true);
            }
        }
    }
    async updateTermLoanStatus(termLoanId, calculationStatus, lastCalculated = null) {
        try {
            await TermLoan_model_1.TermLoanModel.findByIdAndUpdate(termLoanId, { calculationStatus });
            if (lastCalculated) {
                await TermLoan_model_1.TermLoanModel.findByIdAndUpdate(termLoanId, { lastCalculated });
            }
        }
        catch (e) {
            console.error(e);
        }
    }
    async calculateTermLoan(productId, actual) {
        const loanTransactionsService = this.getLoanTransactionsService();
        let termLoan = await this.getCreateTermLoan(productId, actual);
        if (!termLoan || termLoan.calculationStatus === TermLoan_model_1.ETermLoanStatus.NEW) {
            await this.createTermLoan(productId, actual);
            termLoan = await this.getCreateTermLoan(productId, actual);
        }
        if (!termLoan) {
            return;
        }
        await this.updateTermLoanStatus(termLoan._id.toString(), TermLoan_model_1.ETermLoanStatus.CALCULATING);
        const daysInYear = 360;
        const initialTransaction = await loanTransactionsService.getLastTransactionForDate(productId, termLoan.loanStartDate);
        const initialData = actual
            ? { date: termLoan.loanStartDate, amount: initialTransaction ? initialTransaction.balance : 0 }
            : { date: termLoan.loanStartDate, amount: termLoan.initialBalance };
        const initialBalance = actual ? initialData.amount : termLoan.initialBalance;
        const startDate = (0, dayjs_1.default)(initialData.date);
        await TermLoan_model_1.TermLoanModel.findByIdAndUpdate(termLoan._id, { loanStartDate: startDate, initialBalance: initialBalance });
        const endGraceDate = (0, dayjs_1.default)(startDate).add(termLoan.settings.gracePeriodMonths, 'month');
        const endTermMonths = (0, dayjs_1.default)(startDate).add(termLoan.settings.termMonths, 'month');
        const endAmortizationDate = (0, dayjs_1.default)(startDate).add(termLoan.settings.amortizationProfileMonths, 'month');
        const product = await this.loanChargesService.getLoanProductById(productId);
        const minPercent = product.minPercent ?? 0;
        const maxPercent = (product.maxPercent ?? 0) === 0 ? 1 : product.maxPercent;
        const getDynamicInterestRate = async (date) => {
            const primeRate = await this.financialIndexesService.getFinancialIndexValue(financial_indexes_service_1.EFinancialIndex.PRIME_RATE, date);
            const interestRate = new decimal_js_1.default(primeRate).add(termLoan.interestRate).toDP(4).toNumber();
            return Math.min(Math.max(interestRate, minPercent), maxPercent);
        };
        let currentDate = startDate;
        const getMonthPayments = async (date) => {
            const transactions = await loanTransactionsService.getPaymentsForPeriod(productId, date.toDate(), date.endOf('month').toDate());
            if (!initialTransaction) {
                return transactions;
            }
            return transactions.filter((transaction) => {
                return ((0, dayjs_1.default)(transaction.date).format('YYYY-MM-DD') !== (0, dayjs_1.default)(initialTransaction.date).format('YYYY-MM-DD'));
            });
        };
        const getMonthDisbursements = async (date) => {
            const transactions = await loanTransactionsService.getLastDisbursementTransactionForDate(productId, date.toDate(), date.endOf('month').toDate());
            if (!initialTransaction) {
                return transactions;
            }
            return transactions.filter((transaction) => {
                return ((0, dayjs_1.default)(transaction.date).format('YYYY-MM-DD') !== (0, dayjs_1.default)(initialTransaction.date).format('YYYY-MM-DD'));
            });
        };
        const checkGracePeriodDate = (checkDate) => {
            return checkDate < endGraceDate.endOf('month');
        };
        const interestRates = await this.financialIndexesService.getFinancialAllIndexes(financial_indexes_service_1.EFinancialIndex.PRIME_RATE);
        const transactions = await loanTransactionsService.getLoanTransactionsForProduct(productId);
        const findLastIndexValue = (date) => {
            const found = interestRates.find((i) => i.date.getTime() <= date.getTime());
            const primeRate = found ? found.value : 0;
            const interestRate = new decimal_js_1.default(primeRate).add(termLoan.interestRate).toDP(4).toNumber();
            return Math.min(Math.max(interestRate, minPercent), maxPercent);
        };
        const findLastTransaction = (date) => {
            return transactions.find((i) => i.date.getTime() <= date.getTime());
        };
        const getComplexEMI = async (termLoan) => {
            const calculateBalance = async (termLoan, EMI) => {
                console.log({ EMI });
                let balance = initialData.amount;
                let accumulatedMonthlyInterest = 0;
                let lastMonthInterest = 0;
                let EMICurrentDate = currentDate;
                let firstPeriod = true;
                const endAmortization = endAmortizationDate;
                let monthPayments = [];
                while (EMICurrentDate.startOf('day').isBefore(endAmortization.startOf('day')) || EMICurrentDate.startOf('day').isSame(endAmortization.startOf('day'))) {
                    const lastTransaction = findLastTransaction(EMICurrentDate.subtract(1, 'day').toDate());
                    if (actual && lastTransaction && (0, dayjs_1.default)(lastTransaction.date).format('YYYY-MM-DD') === EMICurrentDate.subtract(1, 'day').format('YYYY-MM-DD')) {
                        balance = lastTransaction.balance;
                    }
                    const interestRate = findLastIndexValue(EMICurrentDate.toDate());
                    const interestRateDaily = interestRate / daysInYear;
                    const dailyInterest = EMICurrentDate.startOf('day').isAfter(endAmortizationDate.startOf('day')) ? 0 : new decimal_js_1.default(balance).mul(interestRateDaily).toDP(2).toNumber();
                    accumulatedMonthlyInterest = new decimal_js_1.default(accumulatedMonthlyInterest).add(dailyInterest).toDP(2).toNumber();
                    if (termLoan.actual) {
                        if ((EMICurrentDate.format('YYYY-MM-DD') === EMICurrentDate.startOf('month').format('YYYY-MM-DD')) ||
                            (EMICurrentDate.format('YYYY-MM-DD') === startDate.format('YYYY-MM-DD'))) {
                            monthPayments = await getMonthPayments(EMICurrentDate);
                        }
                    }
                    if ((EMICurrentDate.format('YYYY-MM-DD') === EMICurrentDate.endOf('month').format('YYYY-MM-DD')) ||
                        (EMICurrentDate.format('YYYY-MM-DD') === endAmortizationDate.format('YYYY-MM-DD'))) {
                        if (EMICurrentDate.format('YYYY-MM-DD') === endAmortizationDate.format('YYYY-MM-DD')) {
                            lastMonthInterest = lastMonthInterest + accumulatedMonthlyInterest;
                        }
                        else {
                            lastMonthInterest = accumulatedMonthlyInterest;
                        }
                        accumulatedMonthlyInterest = 0;
                        firstPeriod = false;
                    }
                    if (monthPayments.length > 0) {
                        const principal = monthPayments
                            .filter((p) => (0, dayjs_1.default)(p.date).format('YYYY-MM-DD') === EMICurrentDate.format('YYYY-MM-DD'))
                            .reduce((acc, p) => acc + Math.abs(p.amount), 0);
                        if (principal > 0) {
                            balance = new decimal_js_1.default(balance).sub(principal).toDP(2).toNumber();
                            lastMonthInterest = 0;
                        }
                    }
                    else {
                        if ((EMICurrentDate.format('YYYY-MM-DD') === EMICurrentDate.date(termLoan.settings.principalDueDay).format('YYYY-MM-DD'))) {
                            const isGracePeriod = checkGracePeriodDate(EMICurrentDate);
                            const principal = (isGracePeriod || firstPeriod) ? 0 : new decimal_js_1.default(EMI).sub(lastMonthInterest).toDP(2).toNumber();
                            balance = new decimal_js_1.default(balance).sub(principal).toDP(2).toNumber();
                            lastMonthInterest = 0;
                        }
                    }
                    EMICurrentDate = EMICurrentDate.add(1, 'day');
                }
                const principal = new decimal_js_1.default(EMI).sub(lastMonthInterest).toDP(2).toNumber();
                balance = new decimal_js_1.default(balance).sub(principal).toDP(2).toNumber();
                return balance;
            };
            let lowerBound = 0;
            let upperBound = initialBalance;
            let optimalEMI = 0;
            const tolerance = 0.01;
            while (new decimal_js_1.default(upperBound).sub(lowerBound).toDP(2).toNumber() > tolerance) {
                const mid = new decimal_js_1.default(lowerBound).add(upperBound).div(2).toDP(2).toNumber();
                const currentBalance = await calculateBalance(termLoan, mid);
                if (currentBalance < 0) {
                    upperBound = mid;
                }
                else {
                    lowerBound = mid;
                    optimalEMI = mid;
                }
            }
            return new decimal_js_1.default(optimalEMI).add(tolerance).toDP(2).toNumber();
        };
        const getNextPaymentDate = (date) => {
            let paymentDate = date.date(termLoan.settings.principalDueDay);
            if (paymentDate < date) {
                paymentDate = paymentDate.add(1, 'month').date(termLoan.settings.principalDueDay);
            }
            return paymentDate;
        };
        const complexEMI = await getComplexEMI(termLoan);
        console.log({ complexEMI });
        const getDaysInMonth = (date) => {
            const maxStartDate = dayjs_1.default.max(startDate, date.startOf('month')).startOf('day');
            const maxEndDate = dayjs_1.default.min(endAmortizationDate, date.endOf('month')).startOf('day');
            return maxEndDate.diff(maxStartDate, 'day') + 1;
        };
        let calculatedDailyResults = [];
        const calculatedMonthlyResults = [];
        let monthOpeningBalance = initialBalance;
        let openingBalance = initialBalance;
        let closingBalance = openingBalance;
        let monthInterest = 0;
        let lastMonthInterest = 0;
        let lastMonthPrincipal = 0;
        let dayAdminFee = 0;
        let monthAdminFee = 0;
        const charges = await this.loanChargesService.getLoanChargeForProduct(productId);
        const adminCharge = charges.find((charge) => charge.chargeType === loan_charge_type_enum_1.ELoanChargeType.ADMIN_FEE);
        const minAdminFee = adminCharge ? adminCharge.minimumAmount : 0;
        let monthPayments = [];
        let monthDisbursements = [];
        let isLastPeriod = false;
        while ((currentDate.startOf('day').isBefore(endAmortizationDate.startOf('day')) || currentDate.startOf('day').isSame(endAmortizationDate.startOf('day'))) &&
            !isLastPeriod) {
            if ((currentDate.format('YYYY-MM-DD') === currentDate.startOf('month').format('YYYY-MM-DD')) ||
                (currentDate.format('YYYY-MM-DD') === startDate.format('YYYY-MM-DD'))) {
                if (actual) {
                    monthPayments = await getMonthPayments(currentDate.startOf('day'));
                    monthDisbursements = await getMonthDisbursements(currentDate.startOf('day'));
                }
                monthOpeningBalance = openingBalance;
            }
            const lastTransaction = await loanTransactionsService.getLastTransactionForDate(productId, currentDate.subtract(1, 'day').toDate());
            if (actual && lastTransaction && (0, dayjs_1.default)(lastTransaction.date).format('YYYY-MM-DD') === currentDate.subtract(1, 'day').format('YYYY-MM-DD')) {
                openingBalance = lastTransaction.balance;
            }
            if (currentDate.format('YYYY-MM-DD') === currentDate.date(termLoan.settings.principalDueDay).add(1, 'day').format('YYYY-MM-DD')) {
                openingBalance = closingBalance;
            }
            dayAdminFee = openingBalance * termLoan.adminRate / getDaysInMonth(currentDate);
            monthAdminFee = monthAdminFee + dayAdminFee;
            const interestRate = await getDynamicInterestRate(currentDate.toDate());
            const interestRateDaily = interestRate / daysInYear;
            const dailyInterest = new decimal_js_1.default(openingBalance).mul(interestRateDaily).toDP(2).toNumber();
            const principalDueDay = getNextPaymentDate(currentDate);
            if (monthPayments.length > 0) {
                const foundPayment = monthPayments.find((payment) => (0, dayjs_1.default)(payment.date).format('YYYY-MM-DD') === currentDate.format('YYYY-MM-DD'));
                if (foundPayment) {
                    closingBalance = foundPayment.balance;
                }
                const lastTransaction = await loanTransactionsService.getLastTransactionForDate(productId, currentDate.toDate());
                if (actual && lastTransaction && (0, dayjs_1.default)(lastTransaction.date).format('YYYY-MM-DD') === currentDate.format('YYYY-MM-DD')) {
                    closingBalance = lastTransaction.balance;
                }
            }
            else {
                if (currentDate.format('YYYY-MM-DD') === principalDueDay.format('YYYY-MM-DD')) {
                    const monthlyPayment = new decimal_js_1.default(complexEMI).sub(lastMonthInterest).toDP(2).toNumber();
                    const isLastPeriod = monthlyPayment > openingBalance;
                    const isGracePeriod = checkGracePeriodDate(principalDueDay);
                    const monthlyPrincipal = (isGracePeriod) ? 0 : (isLastPeriod ? openingBalance : monthlyPayment);
                    closingBalance = (0, numbers_helper_1.roundToXDigits)(openingBalance - monthlyPrincipal);
                }
            }
            monthInterest = (0, numbers_helper_1.roundToXDigits)(monthInterest + dailyInterest);
            const dailyResult = {
                day: currentDate.format('YYYY-MM-DD'),
                interestRate: interestRate,
                interest: dailyInterest,
                paymentDate: currentDate.date() === termLoan.settings.principalDueDay,
            };
            calculatedDailyResults.push(dailyResult);
            if ((currentDate.format('YYYY-MM-DD') === currentDate.endOf('month').format('YYYY-MM-DD')) ||
                (currentDate.format('YYYY-MM-DD') === endAmortizationDate.format('YYYY-MM-DD')) ||
                (currentDate.format('YYYY-MM-DD') === endTermMonths.format('YYYY-MM-DD'))) {
                if (monthPayments.length > 0 && calculatedMonthlyResults.length > 0) {
                    calculatedMonthlyResults[calculatedMonthlyResults.length - 1].payments = monthPayments.map((p) => ({
                        paymentId: new mongoose_1.default.Types.ObjectId(String(p._id)),
                        amount: Math.abs(p.amount),
                    }));
                    calculatedMonthlyResults[calculatedMonthlyResults.length - 1].monthlyPrincipal = monthPayments.reduce((acc, p) => acc + Math.abs(p.amount), 0);
                }
                const actualMonthlyPayment = new decimal_js_1.default(complexEMI).sub(monthInterest).toDP(2).toNumber();
                isLastPeriod = actualMonthlyPayment > monthOpeningBalance || (currentDate.format('YYYY-MM-DD') === endTermMonths.format('YYYY-MM-DD'));
                const isGracePeriod = checkGracePeriodDate(principalDueDay);
                const monthlyPrincipal = (isGracePeriod) ? 0 : (isLastPeriod ? monthOpeningBalance : actualMonthlyPayment);
                const totalPayment = monthPayments.length > 0 ? monthPayments.reduce((acc, p) => new decimal_js_1.default(acc).add(Math.abs(p.amount)).toDP(2).toNumber(), 0) : lastMonthPrincipal;
                const totalDisbursements = monthDisbursements.length > 0 ? monthDisbursements.reduce((acc, p) => new decimal_js_1.default(acc).add(p.amount).toDP(2).toNumber(), 0) : 0;
                const closingBalance = new decimal_js_1.default(monthOpeningBalance).sub(totalPayment).add(totalDisbursements).toDP(2).toNumber();
                // openingBalance = closingBalance;
                const monthlyResult = {
                    termLoanId: termLoan._id,
                    openingBalance: monthOpeningBalance,
                    relevantStatement: currentDate.endOf('month').format('YYYY-MM-DD'),
                    monthlyInterest: monthInterest,
                    monthlyPrincipal: monthlyPrincipal,
                    closingBalance: closingBalance,
                    adminFee: Math.max((0, numbers_helper_1.roundToXDigits)(monthAdminFee), minAdminFee),
                    interestRate: interestRate,
                    interestDaily: monthOpeningBalance * interestRate / daysInYear,
                    gracePeriod: isGracePeriod,
                    amortization: true, // TODO calculate
                    paymentDueDate: principalDueDay.date(termLoan.settings.principalDueDay).toDate(),
                    payments: null,
                    disbursements: Math.abs(totalDisbursements),
                    dailyResults: calculatedDailyResults,
                };
                calculatedMonthlyResults.push(monthlyResult);
                lastMonthInterest = monthInterest;
                lastMonthPrincipal = monthlyPrincipal;
                monthInterest = 0;
                monthAdminFee = 0;
                calculatedDailyResults = [];
            }
            currentDate = currentDate.add(1, 'day');
        }
        const paymentDates = [...new Set(calculatedMonthlyResults.map((res) => (0, dayjs_1.default)(res.paymentDueDate).format('YYYY-MM-DD')))];
        const mergedResults = paymentDates.reduce((acc, paymentDate) => {
            const filteredDateResults = calculatedMonthlyResults.filter((res) => (0, dayjs_1.default)(res.paymentDueDate).format('YYYY-MM-DD') === paymentDate);
            if (filteredDateResults.length === 1) {
                return [...acc, ...filteredDateResults];
            }
            const totalMonthlyInterest = filteredDateResults.reduce((acc, res) => {
                return (0, numbers_helper_1.roundToXDigits)(acc + res.monthlyInterest);
            }, 0);
            const mergedOpeningBalance = filteredDateResults[0].openingBalance;
            const totalMonthlyPrincipal = Math.min(filteredDateResults[0].closingBalance, (0, numbers_helper_1.roundToXDigits)(complexEMI - totalMonthlyInterest));
            const mergedClosingBalance = filteredDateResults[0].closingBalance;
            const totalAdminFee = filteredDateResults.reduce((acc, res) => {
                return (0, numbers_helper_1.roundToXDigits)(acc + res.adminFee);
            }, 0);
            const lastStatement = {
                termLoanId: filteredDateResults[0].termLoanId,
                openingBalance: mergedOpeningBalance,
                relevantStatement: filteredDateResults[0].relevantStatement,
                monthlyInterest: totalMonthlyInterest,
                monthlyPrincipal: totalMonthlyPrincipal,
                closingBalance: mergedClosingBalance,
                adminFee: totalAdminFee,
                interestRate: filteredDateResults[0].interestRate,
                interestDaily: filteredDateResults[0].openingBalance * filteredDateResults[0].interestRate / daysInYear,
                gracePeriod: filteredDateResults[0].gracePeriod,
                amortization: true, // TODO calculate
                paymentDueDate: filteredDateResults[0].paymentDueDate,
                payments: null,
                disbursements: filteredDateResults[0].disbursements,
                dailyResults: filteredDateResults[0].dailyResults, // totalCalculatedDailyResults
            };
            const lastPaymentDate = (0, dayjs_1.default)(filteredDateResults[1].paymentDueDate).isBefore((0, dayjs_1.default)(filteredDateResults[1].relevantStatement))
                ? getNextPaymentDate((0, dayjs_1.default)(filteredDateResults[1].relevantStatement)).toDate()
                : filteredDateResults[1].paymentDueDate;
            const emptyStatement = {
                termLoanId: filteredDateResults[1].termLoanId,
                openingBalance: mergedClosingBalance,
                relevantStatement: filteredDateResults[1].relevantStatement,
                monthlyInterest: 0,
                monthlyPrincipal: endTermMonths.format() !== endAmortizationDate.format() ? mergedClosingBalance : 0,
                closingBalance: 0,
                adminFee: 0,
                interestRate: 0,
                interestDaily: 0,
                gracePeriod: filteredDateResults[1].gracePeriod,
                amortization: true, // TODO calculate
                paymentDueDate: lastPaymentDate,
                payments: null,
                disbursements: filteredDateResults[1].disbursements,
                dailyResults: filteredDateResults[1].dailyResults,
            };
            return [...acc, lastStatement, emptyStatement];
        }, []);
        if (mergedResults[mergedResults.length - 1].closingBalance !== 0) {
            // mergedResults[mergedResults.length - 1].monthlyPrincipal = mergedResults[mergedResults.length - 1].closingBalance;
            const lastUnfinishedResult = mergedResults[mergedResults.length - 1];
            const finishingResult = JSON.parse(JSON.stringify(lastUnfinishedResult));
            finishingResult.openingBalance = lastUnfinishedResult.closingBalance;
            finishingResult.monthlyInterest = 0;
            finishingResult.monthlyPrincipal = 0;
            finishingResult.adminFee = 0;
            finishingResult.interestRate = 0;
            finishingResult.interestDaily = 0;
            finishingResult.closingBalance = 0;
            finishingResult.dailyResults = [];
            mergedResults.push(finishingResult);
        }
        console.log('saving...');
        const filteredResults = mergedResults
            .map((res) => ({ ...res, monthlyPrincipal: res.monthlyPrincipal > 0 ? res.monthlyPrincipal : 0 }));
        const filteredRes = (await Promise.all(filteredResults.map(async (res) => {
            if (!actual) {
                return res;
            }
            const trDate = typeof res.paymentDueDate === 'string' ? new Date(res.paymentDueDate) : res.paymentDueDate;
            const lastTransaction = await loanTransactionsService.getLastTransactionForDate(product._id.toString(), trDate);
            if (lastTransaction && lastTransaction.balance >= 0) {
                return res;
            }
        }))).filter((res) => !!res);
        let balanceIsZero = false;
        const afterClosingBalance = filteredRes.reduce((acc, res) => {
            if (balanceIsZero) {
                return acc;
            }
            balanceIsZero = res.closingBalance === 0;
            return [...acc, res];
        }, []);
        await this.saveCalculatedTermLoan(afterClosingBalance, productId);
        await this.updateTermLoanStatus(termLoan._id.toString(), TermLoan_model_1.ETermLoanStatus.CALCULATED, new Date());
        return await this.getTermLoan(productId, actual);
    }
    async saveCalculatedTermLoan(calculatedTermLoans, productId) {
        if (calculatedTermLoans.length === 0) {
            return;
        }
        const termLoanId = calculatedTermLoans[0].termLoanId;
        await TermLoanCalculated_model_1.TermLoanCalculatedModel.deleteMany({ termLoanId, payments: null });
        await Promise.all(calculatedTermLoans.map(async (calculatedTermLoan) => {
            if (calculatedTermLoan.payments === null) {
                await TermLoanCalculated_model_1.TermLoanCalculatedModel.findOneAndDelete({
                    termLoanId: calculatedTermLoan.termLoanId,
                    relevantStatement: calculatedTermLoan.relevantStatement,
                });
                const newCalculatedTermLoan = new TermLoanCalculated_model_1.TermLoanCalculatedModel(calculatedTermLoan);
                await newCalculatedTermLoan.save();
            }
            else {
                const foundTermLoanCalculated = await TermLoanCalculated_model_1.TermLoanCalculatedModel.findOne({
                    termLoanId: calculatedTermLoan.termLoanId,
                    relevantStatement: calculatedTermLoan.relevantStatement,
                }).lean();
                if (!foundTermLoanCalculated) {
                    const newCalculatedTermLoan = new TermLoanCalculated_model_1.TermLoanCalculatedModel(calculatedTermLoan);
                    await newCalculatedTermLoan.save();
                }
                else {
                    const loanStatementStatusService = this.getLoanStatementStatusService();
                    const statementStatus = await loanStatementStatusService.getStatementStatus(productId, new Date(calculatedTermLoan.relevantStatement));
                    if (!statementStatus.locked) {
                        await TermLoanCalculated_model_1.TermLoanCalculatedModel.findByIdAndUpdate(foundTermLoanCalculated._id, calculatedTermLoan);
                    }
                }
            }
        }));
    }
    async getTermLoanExcel(productId, actual, short) {
        const termLoan = await this.getTermLoan(productId, actual);
        const header = short
            ? ['PERIOD', 'OPENING BALANCE', 'PRINCIPAL', 'INTEREST', 'CLOSING BALANCE', 'ADMIN FEE']
            : ['DATE', 'OPENING BALANCE', 'INTEREST', 'DAILY INTEREST ACCRUED', 'PRINCIPAL', 'CLOSING BALANCE', 'STATEMENT DATE'];
        const getMonthlyData = (data) => {
            return data.calculated.map((row) => {
                return [
                    new Date(row.relevantStatement),
                    row.openingBalance,
                    row.monthlyPrincipal,
                    row.monthlyInterest,
                    row.closingBalance,
                    row.adminFee,
                ];
            });
        };
        const getDailyData = (data) => {
            let lastMonthClosingBalance = null;
            let lastPaymentClosingBalance = null;
            let lastPaymentMonthlyPrincipal = null;
            let lastPaymentDate = null;
            const calculatedResults = data.calculated.reduce((acc, row) => {
                let isPaymentDay = false;
                let openingBalance = lastMonthClosingBalance ?? row.openingBalance;
                let closingBalance = openingBalance;
                return [
                    ...acc,
                    ...row.dailyResults.map((daily, index) => {
                        isPaymentDay = isPaymentDay || daily.paymentDate;
                        openingBalance = closingBalance;
                        if ((0, dayjs_1.default)(daily.day).format('YYYY-MM-DD') === (0, dayjs_1.default)(lastPaymentDate).format('YYYY-MM-DD')) {
                            closingBalance = lastPaymentClosingBalance - lastPaymentMonthlyPrincipal;
                        }
                        const newRow = [
                            new Date(daily.day),
                            openingBalance,
                            daily.interestRate,
                            daily.interest,
                            daily.paymentDate ? lastPaymentMonthlyPrincipal : null,
                            closingBalance,
                            row.relevantStatement,
                        ];
                        if ((0, dayjs_1.default)(daily.day).format('YYYY-MM-DD') === (0, dayjs_1.default)(lastPaymentDate).format('YYYY-MM-DD')) {
                            openingBalance = closingBalance;
                        }
                        isPaymentDay = isPaymentDay || daily.paymentDate;
                        if (daily.paymentDate) {
                            lastPaymentClosingBalance = row.closingBalance;
                            lastPaymentMonthlyPrincipal = row.monthlyPrincipal;
                            lastPaymentDate = row.paymentDueDate;
                        }
                        if (index + 1 === row.dailyResults.length) {
                            lastMonthClosingBalance = row.closingBalance;
                        }
                        return newRow;
                    })
                ];
            }, []);
            const lastRow = data.calculated[data.calculated.length - 1];
            const lastPayment = [
                new Date(lastRow.paymentDueDate),
                lastRow.openingBalance,
                null,
                null,
                lastRow.monthlyPrincipal,
                lastRow.closingBalance,
                lastRow.relevantStatement,
            ];
            return [...calculatedResults, lastPayment];
        };
        const product = await LoanProducts_model_1.LoanProduct.findById(productId).lean();
        const preparedData = short ? getMonthlyData(termLoan) : getDailyData(termLoan);
        const dataToConvert = [{ [product.name]: [header, ...preparedData] }];
        const uploadsService = this.getUploadsService();
        return await uploadsService.convertDataToFile(dataToConvert);
    }
    async isBorrowerTermLoanActual(borrowerId) {
        const products = await this.loanChargesService.getLoanProducts(borrowerId);
        const termLoanProduct = products.find((product) => product.type === loan_types_enum_1.ELoanTypes.TERM);
        if (!termLoanProduct) {
            return true;
        }
        const termLoan = await TermLoan_model_1.TermLoanModel.findOne({ productId: termLoanProduct._id, actual: true });
        if (!termLoan) {
            return true;
        }
        return termLoan.calculationStatus === TermLoan_model_1.ETermLoanStatus.CALCULATED;
    }
}
exports.TermLoanService = TermLoanService;
