"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getLedger = exports.EChargeType = void 0;
const lodash_1 = __importDefault(require("lodash"));
const decimal_js_1 = __importDefault(require("decimal.js"));
const LoanProducts_model_1 = require("../models/LoanProducts.model");
const loan_statement_db_1 = require("./loan-statement.db");
const mongoose_1 = __importDefault(require("mongoose"));
const Borrower_model_1 = require("../models/Borrower.model");
const LoanTransaction_model_1 = require("../models/LoanTransaction.model");
const loan_types_enum_1 = require("../enums/loan-types.enum");
const LoanStatementTransaction_model_1 = require("../models/LoanStatementTransaction.model");
const date_helper_1 = require("../helpers/date.helper");
const dayjs_1 = __importDefault(require("dayjs"));
const loan_charges_db_1 = require("./loan-charges.db");
const loan_products_db_1 = require("./loan-products.db");
const loan_payments_db_1 = require("./loan-payments.db");
const brokers_db_1 = require("./brokers.db");
const TermLoan_model_1 = require("../models/TermLoan.model");
const TermLoanCalculated_model_1 = require("../models/TermLoanCalculated.model");
const _models_1 = require("../models/_models");
var EChargeType;
(function (EChargeType) {
    EChargeType["INTEREST_FEE"] = "INTEREST";
    EChargeType["ADMIN_FEE"] = "ADMIN FEE";
    EChargeType["UNUSED_LINE_FEE"] = "UNUSED LINE FEE";
    EChargeType["ANNUAL_LINE_FEE"] = "ANNUAL LINE FEE";
    EChargeType["WIRE_FEE"] = "WIRE FEE";
    EChargeType["RECOVERABLE"] = "RECOVERABLE";
    EChargeType["OTHER"] = "OTHER";
    EChargeType["DISBURSEMENT"] = "DISBURSEMENT";
    EChargeType["PRINCIPAL"] = "PRINCIPAL";
    EChargeType["COLLECTION"] = "COLLECTION";
    EChargeType["ADJUSTMENT"] = "ADJUSTMENT";
    EChargeType["EXPECTED_PRINCIPAL"] = "EXPECTED PRINCIPAL ACCRUED";
    EChargeType["EXPECTED_PRINCIPAL_PAID"] = "EXPECTED PRINCIPAL PAID";
})(EChargeType || (exports.EChargeType = EChargeType = {}));
const defaultReportLedgerOptions = {
    showBalances: true,
    showFloatedBalance: true,
};
const getLedger = async (params, options = defaultReportLedgerOptions) => {
    const { productIds, reportType } = params;
    const fullTransactions = await getLedgerData(params, options);
    const products = await LoanProducts_model_1.LoanProduct
        .find({ _id: { $in: productIds.map((id) => new mongoose_1.default.Types.ObjectId(id)) } })
        .sort({ code: 1 })
        .lean();
    const header = loan_statement_db_1.ledgerHeadersMap[reportType];
    const headerKeys = Object.keys(header);
    const sumKeys = ['amount', 'statementAmount'];
    const groupKeys = headerKeys.filter((key) => !sumKeys.includes(key));
    return products.reduce((acc, product) => {
        const transactions = fullTransactions[product._id.toString()];
        const finalTransactions = reportType === loan_statement_db_1.ELedgerReportType.SHORT
            ? mergeLedgerData(transactions, groupKeys, sumKeys)
            : transactions;
        const sheetName = `${product.code} - ${product.name}`;
        return {
            ...acc,
            [sheetName]: lodash_1.default.map(finalTransactions, (obj) => lodash_1.default.pick(obj, headerKeys)),
        };
    }, {});
};
exports.getLedger = getLedger;
const getLedgerData = async (params, options = defaultReportLedgerOptions) => {
    const { productIds, startDate, endDate } = params;
    const addBrokers = true;
    const products = await LoanProducts_model_1.LoanProduct.find({ _id: { $in: productIds.map((id) => new mongoose_1.default.Types.ObjectId(id)) } }).lean();
    const borrowers = await Borrower_model_1.BorrowerModel.find({ _id: { $in: products.map((product) => product.borrowerId) } }).lean();
    const borrowerMap = borrowers.reduce((acc, borrower) => ({ ...acc, [borrower._id.toString()]: borrower.code }), {});
    const borrowerCodesMap = products.reduce((acc, product) => ({
        ...acc,
        [product._id.toString()]: borrowerMap[product.borrowerId.toString()],
    }), {});
    const groupedTransactions = await LoanTransaction_model_1.LoanTransaction.aggregate([
        {
            $match: {
                $and: [
                    { 'date': { $gte: startDate } },
                    { 'date': { $lte: (0, dayjs_1.default)(endDate).endOf('day').toDate() } },
                    {
                        productId: {
                            $in: products.map((product) => product._id),
                        },
                    },
                ],
            },
        }, {
            $lookup: {
                from: _models_1.MODEL_NAMES.loanProducts,
                localField: 'productId',
                foreignField: '_id',
                as: 'product',
            },
        }, {
            $unwind: {
                path: '$product',
                preserveNullAndEmptyArrays: true,
            },
        }, {
            $sort: {
                'date': 1,
                'order': 1,
            },
        },
        {
            $group: {
                _id: '$productId',
                transactions: { $push: '$$ROOT' },
                product: { $first: '$product' },
            },
        },
        {
            $project: {
                _id: 0,
                productId: '$_id',
                product: 1,
                transactions: 1,
            },
        },
    ]);
    const convertTypes = (transactionType, product) => {
        if (product.type === loan_types_enum_1.ELoanTypes.TERM && transactionType === LoanTransaction_model_1.ELoanTransactionTypes.COLLECTION) {
            return EChargeType.PRINCIPAL;
        }
        return EChargeType[transactionType];
    };
    const groupedProductsWithTransactions = products.map((p) => {
        const productTransactions = groupedTransactions.find((group) => group.productId.toString() === p._id.toString());
        return {
            product: p,
            productId: p._id,
            transactions: productTransactions ? productTransactions.transactions : [],
        };
    });
    const mappedGroups = groupedProductsWithTransactions.reduce((acc, group) => {
        return {
            ...acc,
            [group.productId.toString()]: {
                product: group.product,
                transactions: group.transactions.map((transaction) => {
                    return {
                        typeOrder: 0,
                        order: transaction.order,
                        date: transaction.date,
                        type: convertTypes(transaction.transactionType, group.product),
                        amount: transaction.amount,
                        statementAmount: null,
                        title: convertTypes(transaction.transactionType, group.product),
                        balance: transaction.balance,
                        floatedBalance: transaction.floatedBalance,
                        chargeCode: group.product.code,
                        PLCode: null,
                        productCode: group.product.code,
                        productName: group.product.name,
                        createdAt: transaction['createdAt'],
                        info: transaction.reference,
                        paymentDate: null,
                    };
                }),
            },
        };
    }, {});
    const allData = {};
    await Promise.all(Object.entries(mappedGroups).map(async ([productId, group]) => {
        const loanCharges = await (0, loan_charges_db_1.getLoanChargeForProduct)(productId);
        const chargesIds = loanCharges.map((charge) => charge._id);
        const statementTransactions = await LoanStatementTransaction_model_1.LoanStatementTransactionModel.aggregate([
            {
                $match: {
                    $and: [
                        { 'date': { $gte: startDate } },
                        { 'date': { $lte: (0, dayjs_1.default)(endDate).endOf('day').toDate() } },
                        { 'chargeId': { '$in': chargesIds } },
                    ],
                },
            }, {
                $lookup: {
                    from: _models_1.MODEL_NAMES.loanCharges,
                    localField: 'chargeId',
                    foreignField: '_id',
                    as: 'charge',
                },
            }, {
                $unwind: {
                    path: '$charge',
                },
            }, {
                $sort: {
                    'date': 1,
                    'createdAt': 1,
                    'order': 1,
                },
            },
        ]);
        const statementTransactionsWithBalance = options.showBalances
            ? await Promise.all(statementTransactions.map(async (statement) => {
                const balances = await (0, loan_products_db_1.getLoanProductBalance)(productId, statement.date);
                return {
                    ...statement,
                    balance: balances.balance,
                    floatedBalance: balances.floatedBalance,
                };
            }))
            : statementTransactions.slice();
        const isParticipant = group.product.masterLoanProductId != null;
        const groupedMappedStatementTransactions = await Promise.all(statementTransactionsWithBalance.map(async (statement) => {
            const transactions = [];
            if (statement.amountPaid > 0) {
                const payments = await (0, loan_payments_db_1.getPaymentForTransaction)(productId, statement._id.toString());
                for (const payment of payments) {
                    transactions.push({
                        typeOrder: 1,
                        order: statement.order,
                        date: statement.date,
                        type: statement.charge.chargeType,
                        amount: null,
                        statementAmount: isParticipant ? statement.amountPaid : -statement.amountPaid,
                        title: `${statement.charge.name} PAID`,
                        chargeCode: statement.charge.code,
                        PLCode: statement.charge.PLCode,
                        productCode: group.product.code,
                        productName: group.product.name,
                        balance: statement.balance,
                        floatedBalance: statement.floatedBalance,
                        createdAt: statement['createdAt'],
                        info: statement.memo,
                        paymentDate: (0, date_helper_1.convertDateToFormat)(payment.date),
                    });
                }
            }
            return [...transactions, {
                    typeOrder: 1,
                    order: statement.order,
                    date: statement.date,
                    type: statement.charge.chargeType,
                    amount: null,
                    statementAmount: isParticipant ? -statement.amount : statement.amount,
                    title: `${statement.charge.name} ACCRUED`,
                    chargeCode: statement.charge.code,
                    PLCode: statement.charge.PLCode,
                    productCode: group.product.code,
                    productName: group.product.name,
                    balance: statement.balance,
                    floatedBalance: statement.floatedBalance,
                    createdAt: statement['createdAt'],
                    info: statement.memo,
                    paymentDate: null,
                }];
        }));
        const mappedStatementTransactions = groupedMappedStatementTransactions
            .reduce((acc, group) => [...acc, ...group], []);
        const statementTransactionsWithBrokers = addBrokers
            ? await (0, brokers_db_1.enrichWithBrokers)(mappedStatementTransactions, group.product)
            : mappedStatementTransactions;
        const sortedTransactions = [...statementTransactionsWithBrokers, ...group.transactions]
            .sort((a, b) => {
            if (a.date.valueOf() !== b.date.valueOf()) {
                return b.date.valueOf() - a.date.valueOf();
            }
            if (a.typeOrder !== b.typeOrder) {
                return b.typeOrder - a.typeOrder;
            }
            return b.createdAt.valueOf() - a.createdAt.valueOf();
        });
        const fullTransactions = sortedTransactions
            .map((row) => ({
            borrowerCode: borrowerCodesMap[productId],
            date: (0, date_helper_1.convertDateToFormat)(row.date, 'MM/DD/YYYY'),
            type: row.isBrokerFee ? `${EChargeType[row.type]} BROKER` : EChargeType[row.type],
            chargeCode: row.chargeCode,
            PLCode: row.PLCode,
            productCode: row.productCode,
            productName: row.productName,
            amount: row.amount,
            statementAmount: row.statementAmount,
            title: row.title,
            balance: row.balance,
            floatedBalance: row.floatedBalance,
            info: row.info,
            paymentDate: row.paymentDate ? (0, date_helper_1.convertDateToFormat)(row.paymentDate) : '',
        }));
        const termLoan = await TermLoan_model_1.TermLoanModel.findOne({ productId, actual: true });
        if (termLoan) {
            const calculatedTermLoan = await TermLoanCalculated_model_1.TermLoanCalculatedModel.findOne({
                termLoanId: termLoan._id.toString(),
                relevantStatement: (0, date_helper_1.convertDateToFormat)(endDate),
            });
            if (calculatedTermLoan) {
                const expectedPayment = {
                    borrowerCode: borrowerCodesMap[productId],
                    date: (0, date_helper_1.convertDateToFormat)(endDate, 'MM/DD/YYYY'),
                    type: EChargeType.EXPECTED_PRINCIPAL,
                    chargeCode: null,
                    PLCode: null,
                    productCode: group.product.code,
                    amount: isParticipant ? -calculatedTermLoan.monthlyPrincipal : calculatedTermLoan.monthlyPrincipal,
                    statementAmount: null,
                    title: EChargeType.EXPECTED_PRINCIPAL,
                    balance: null,
                    floatedBalance: null,
                    info: EChargeType.EXPECTED_PRINCIPAL,
                    paymentDate: null,
                };
                fullTransactions.unshift(expectedPayment);
                if (calculatedTermLoan.payments && calculatedTermLoan.payments.length > 0) {
                    for (const payment of calculatedTermLoan.payments) {
                        const paymentDoc = await LoanTransaction_model_1.LoanTransaction.findById(payment.paymentId).lean();
                        if (paymentDoc) {
                            const expectedPayment = {
                                borrowerCode: borrowerCodesMap[productId],
                                date: (0, date_helper_1.convertDateToFormat)(endDate, 'MM/DD/YYYY'),
                                type: EChargeType.EXPECTED_PRINCIPAL_PAID,
                                chargeCode: null,
                                PLCode: null,
                                productCode: group.product.code,
                                amount: isParticipant ? -payment.amount : payment.amount,
                                statementAmount: null,
                                title: EChargeType.EXPECTED_PRINCIPAL_PAID,
                                balance: null,
                                floatedBalance: null,
                                info: EChargeType.EXPECTED_PRINCIPAL_PAID,
                                paymentDate: (0, date_helper_1.convertDateToFormat)(paymentDoc.date, 'MM/DD/YYYY'),
                            };
                            fullTransactions.unshift(expectedPayment);
                        }
                    }
                }
            }
        }
        allData[productId] = fullTransactions;
    }));
    return allData;
};
const mergeLedgerData = (transactions, groupKeys, sumKeys) => {
    const groupedObjects = lodash_1.default.groupBy(transactions, (obj) => groupKeys.reduce((acc, key) => `${acc}${obj[key]}`, ''));
    const mergedObjects = lodash_1.default.mapValues(groupedObjects, group => lodash_1.default.reduce(group, (result, obj) => {
        sumKeys.forEach((key) => {
            result[key] = new decimal_js_1.default(result[key] || 0).plus(obj[key] || 0).toNumber();
        });
        groupKeys.forEach((key) => {
            result[key] = obj[key];
        });
        return result;
    }, {}));
    return lodash_1.default.values(mergedObjects);
};
