"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getLoanProducts = exports.useFloatingBalance = exports.getAverageActualBalance = exports.getAverageBalance = exports.getPostponedTransactions = exports.getLoanProductBalance = exports.getLoanProductById = exports.fieldsToUnset = void 0;
const mongoose_1 = __importDefault(require("mongoose"));
const dayjs_1 = __importDefault(require("dayjs"));
const minMax_1 = __importDefault(require("dayjs/plugin/minMax"));
const decimal_js_1 = __importDefault(require("decimal.js"));
const date_helper_1 = require("../helpers/date.helper");
const loan_statement_db_1 = require("./loan-statement.db");
const PostponedTransactions_model_1 = require("../models/PostponedTransactions.model");
const LoanProducts_model_1 = require("../models/LoanProducts.model");
const LoanTransaction_model_1 = require("../models/LoanTransaction.model");
const loan_types_enum_1 = require("../enums/loan-types.enum");
const _models_1 = require("../models/_models");
dayjs_1.default.extend(minMax_1.default);
exports.fieldsToUnset = ['order', '__v', 'createdAt', 'updatedAt', 'borrowerId'];
const getLoanProductById = async (productId) => {
    return LoanProducts_model_1.LoanProduct.findById(productId).lean();
};
exports.getLoanProductById = getLoanProductById;
const getLoanProductBalance = async (productId, asOfDate = new Date()) => {
    const simpleTransactions = await LoanTransaction_model_1.LoanTransaction.aggregate([
        {
            $match: {
                'productId': new mongoose_1.default.Types.ObjectId(productId),
                'date': { $lte: asOfDate },
            },
        }, {
            $sort: {
                'date': -1,
                'order': -1,
                'createdAt': -1,
            },
        }, {
            $limit: 1,
        },
    ]);
    const collectionsTransactions = await LoanTransaction_model_1.LoanTransaction.aggregate([
        {
            $match: {
                'productId': new mongoose_1.default.Types.ObjectId(productId),
                'effectiveDate': { $lte: asOfDate },
                $expr: { $ne: ['$date', '$effectiveDate'] },
            },
        }, {
            $sort: {
                'effectiveDate': -1,
                'order': -1,
                'createdAt': -1,
            },
        }, {
            $limit: 1,
        },
    ]);
    const normalTransactions = await LoanTransaction_model_1.LoanTransaction.aggregate([
        {
            $match: {
                'productId': new mongoose_1.default.Types.ObjectId(productId),
                'effectiveDate': { $lte: asOfDate },
                $expr: { $eq: ['$date', '$effectiveDate'] },
            },
        }, {
            $sort: {
                'effectiveDate': -1,
                'order': -1,
                'createdAt': -1,
            },
        }, {
            $limit: 1,
        },
    ]);
    const transactions = [...collectionsTransactions, ...normalTransactions].sort((a, b) => {
        if (a.date.valueOf() === b.date.valueOf()) {
            return a.effectiveDate.valueOf() - b.effectiveDate.valueOf();
        }
        return a.date.valueOf() - b.date.valueOf();
    });
    const collectionsTransaction = collectionsTransactions.length === 0 ? null : collectionsTransactions[0];
    const normalTransaction = normalTransactions.length === 0 ? null : normalTransactions[0];
    const balance = simpleTransactions.length === 0 ? 0 : simpleTransactions[0].balance;
    let floatedBalance = 0;
    if (normalTransaction && collectionsTransaction) {
        const totalCollections = await (0, loan_statement_db_1.getTotalForDate)(productId, collectionsTransaction.effectiveDate);
        if (normalTransaction.date.valueOf() === collectionsTransaction.date.valueOf()) {
            floatedBalance = new decimal_js_1.default(normalTransaction.floatedBalance).add(totalCollections).toNumber();
        }
        if (normalTransaction.date.valueOf() > collectionsTransaction.date.valueOf()) {
            floatedBalance = normalTransaction.floatedBalance;
        }
        if (normalTransaction.date.valueOf() < collectionsTransaction.date.valueOf()) {
            floatedBalance = collectionsTransaction.balance;
        }
    }
    if (normalTransaction && !collectionsTransaction) {
        floatedBalance = normalTransaction.floatedBalance;
    }
    const totalPostponed = await (0, exports.getPostponedTransactions)(asOfDate, productId);
    if (transactions.length === 0) {
        return { balance: 0, floatedBalance: totalPostponed };
    }
    return {
        balance: balance,
        floatedBalance: floatedBalance,
    };
};
exports.getLoanProductBalance = getLoanProductBalance;
const getPostponedTransactions = async (date, productId, removePostponed = false) => {
    const currentDay = (0, dayjs_1.default)(new Date(date)).format('YYYY-MM-DD');
    const postponedTransactions = await PostponedTransactions_model_1.PostponedTransaction.aggregate([
        {
            $match: {
                'productId': new mongoose_1.default.Types.ObjectId(productId),
            },
        }, {
            $lookup: {
                from: _models_1.MODEL_NAMES.loanTransactions,
                localField: 'transactionId',
                foreignField: '_id',
                as: 'transaction',
            },
        }, {
            $unwind: {
                path: '$transaction',
            },
        },
    ]);
    const currentDate = (0, dayjs_1.default)(`${currentDay}T00:00:00.000Z`);
    const mappedTransactions = postponedTransactions.reduce((acc, t) => {
        const transactionDate = (0, dayjs_1.default)(`${t.postponedToDate}T00:00:00.000Z`);
        if (transactionDate <= currentDate) {
            return [...acc, t];
        }
        return acc;
    }, []);
    let totalAmount = 0;
    await Promise.all(mappedTransactions.map(async (t) => {
        totalAmount = new decimal_js_1.default(totalAmount).add(t.transaction.amount).toNumber();
        if (removePostponed) {
            await PostponedTransactions_model_1.PostponedTransaction.findByIdAndDelete(t._id);
        }
    }));
    return totalAmount;
};
exports.getPostponedTransactions = getPostponedTransactions;
const getAverageBalance = async (productId, period, charge) => {
    const useFloating = await (0, exports.useFloatingBalance)(productId);
    if (charge) {
        period.start = dayjs_1.default.max(period.start, (0, dayjs_1.default)(charge.applyFrom));
    }
    const days = (0, date_helper_1.getDays)(period);
    if (days.length === 0) {
        return 0;
    }
    const balances = await Promise.all(days.map(async (day) => {
        return await (0, exports.getLoanProductBalance)(productId, new Date(day));
    }));
    const totalBalance = balances.reduce((acc, balance) => new decimal_js_1.default(acc).add(useFloating ? balance.floatedBalance : balance.balance).toNumber(), 0);
    return new decimal_js_1.default(totalBalance).div(days.length).toNumber();
};
exports.getAverageBalance = getAverageBalance;
const getAverageActualBalance = async (productId, period, charge) => {
    if (charge) {
        period.start = dayjs_1.default.max(period.start, (0, dayjs_1.default)(charge.applyFrom));
    }
    const days = (0, date_helper_1.getDays)({ ...period, end: (0, dayjs_1.default)(period.end).add(1, 'day') });
    if (days.length === 0) {
        return 0;
    }
    const balances = await Promise.all(days.map(async (day, i) => {
        return await (0, exports.getLoanProductBalance)(productId, (0, dayjs_1.default)(day).add(1, 'day').toDate());
    }));
    const totalBalance = balances.reduce((acc, balance) => new decimal_js_1.default(acc).add(balance.balance).toNumber(), 0);
    return new decimal_js_1.default(totalBalance).div(days.length).toNumber();
};
exports.getAverageActualBalance = getAverageActualBalance;
const useFloatingBalance = async (productId) => {
    const product = await (0, exports.getLoanProductById)(productId);
    return product.type === loan_types_enum_1.ELoanTypes.REVOLVER;
};
exports.useFloatingBalance = useFloatingBalance;
const getLoanProducts = async (borrowerId) => {
    return LoanProducts_model_1.LoanProduct.aggregate([
        {
            $match: {
                'borrowerId': new mongoose_1.default.Types.ObjectId(borrowerId),
            },
        }, {
            $sort: {
                'order': 1,
            },
        }, {
            $unset: exports.fieldsToUnset,
        }, {
            $addFields: {
                valid: true,
            },
        },
    ]);
};
exports.getLoanProducts = getLoanProducts;
