"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.LoanChargesService = void 0;
const mongoose_1 = __importDefault(require("mongoose"));
const dayjs_1 = __importDefault(require("dayjs"));
const loan_charge_type_enum_1 = require("../enums/loan-charge-type.enum");
const _models_1 = require("../models/_models");
const loan_products_db_1 = require("../db/loan-products.db");
const loan_types_enum_1 = require("../enums/loan-types.enum");
const loan_charges_db_1 = require("../db/loan-charges.db");
const LoanProducts_model_1 = require("../models/LoanProducts.model");
const LoanCharges_model_1 = require("../models/LoanCharges.model");
const LoanStatementTransaction_model_1 = require("../models/LoanStatementTransaction.model");
const collaterals_db_1 = require("../db/collaterals.db");
const updateChargesForProduct = false;
class LoanChargesService {
    borrowersDB;
    constructor(borrowersDB) {
        this.borrowersDB = borrowersDB;
    }
    validateCharge = (charge) => {
        const chargePLCode = charge.PLCode ? charge.PLCode.trim() : '';
        const chargeBorrowerCode = charge.code.trim().slice(3, 6);
        return chargePLCode.length > 0 && chargeBorrowerCode.length > 0;
    };
    async getProductByChargeId(chargeId) {
        const charge = await LoanCharges_model_1.LoanCharge.findById(chargeId).lean();
        if (!charge) {
            return null;
        }
        return LoanProducts_model_1.LoanProduct.findById(charge.productId).lean();
    }
    async getBorrowersLoanProducts(borrowerId) {
        return LoanProducts_model_1.LoanProduct.find({ borrowerId }).lean();
    }
    async getAllLoanProducts() {
        const borrowers = await this.borrowersDB.getActiveBorrowers();
        const borrowerIds = borrowers.map((borrower) => borrower._id);
        return LoanProducts_model_1.LoanProduct.aggregate([
            { $match: { borrowerId: { $in: borrowerIds } } },
            {
                $lookup: {
                    from: _models_1.MODEL_NAMES.borrowers,
                    localField: 'borrowerId',
                    foreignField: '_id',
                    as: 'borrower',
                },
            },
            { $unwind: '$borrower' },
            { $sort: { 'borrower.name': 1 } },
            { $project: { borrower: 0 } },
        ]);
    }
    async getLoanProducts(borrowerId) {
        return (0, loan_products_db_1.getLoanProducts)(borrowerId);
    }
    async getLoanProductsByIds(productIds) {
        const ids = productIds.map((productId) => new mongoose_1.default.Types.ObjectId(productId));
        return LoanProducts_model_1.LoanProduct.aggregate([
            {
                $match: {
                    '_id': { $in: ids },
                },
            }, {
                $sort: {
                    'order': 1,
                },
            }, {
                $unset: loan_products_db_1.fieldsToUnset,
            }, {
                $addFields: {
                    valid: true,
                },
            },
        ]);
    }
    async getLoanProductBalance(productId, forDate = new Date()) {
        return (0, loan_products_db_1.getLoanProductBalance)(productId, forDate);
    }
    async getParticipationBalance(code, end) {
        const masterProduct = await LoanProducts_model_1.LoanProduct.findOne({ code }).lean();
        if (!masterProduct) {
            return 0;
        }
        const participationProduct = await LoanProducts_model_1.LoanProduct.findOne({ masterLoanProductId: masterProduct._id }).lean();
        if (!participationProduct) {
            return 0;
        }
        const { balance } = await this.getLoanProductBalance(participationProduct._id.toString(), end);
        return balance;
    }
    ;
    async getLoanProductById(productId) {
        return (0, loan_products_db_1.getLoanProductById)(productId);
    }
    async getLoanProductIdByCode(code) {
        const foundProduct = await LoanProducts_model_1.LoanProduct.findOne({ code }).lean();
        if (foundProduct) {
            return foundProduct._id.toString();
        }
        return null;
    }
    async getLoanChargeIdByCode(code) {
        const foundCharge = await LoanCharges_model_1.LoanCharge.findOne({ code }).lean();
        if (foundCharge) {
            return foundCharge._id.toString();
        }
        return null;
    }
    async saveLoanProducts(borrowerId, loanProducts) {
        const mappedProducts = loanProducts.map((product, order) => {
            return { ...product, borrowerId: new mongoose_1.default.Types.ObjectId(borrowerId), order };
        });
        const allProducts = await this.getLoanProducts(borrowerId);
        const productIdsToRemove = allProducts.map((product) => product._id.toString());
        await Promise.all(mappedProducts.map(async (product) => {
            if (!product._id) {
                const newProduct = new LoanProducts_model_1.LoanProduct(product);
                const savedProduct = await newProduct.save();
                if (product.type === loan_types_enum_1.ELoanTypes.TERM) {
                    const chargeTypes = [
                        {
                            chargeType: loan_charge_type_enum_1.ELoanChargeType.INTEREST_FEE,
                            calculationBasis: loan_types_enum_1.EChargeCalculationBasis.DAILY_BALANCE,
                            frequency: loan_types_enum_1.EChargeFrequencies.DAILY,
                        },
                        {
                            chargeType: loan_charge_type_enum_1.ELoanChargeType.ADMIN_FEE,
                            calculationBasis: loan_types_enum_1.EChargeCalculationBasis.AVERAGE_MONTHLY_BALANCE,
                            frequency: loan_types_enum_1.EChargeFrequencies.MONTHLY,
                        },
                    ];
                    const charges = chargeTypes.map((charge) => {
                        const newCharge = {
                            PLCode: 'XXXXXX',
                            _id: null,
                            active: true,
                            applyFrom: product.startDate,
                            calculationBasis: charge.calculationBasis,
                            chargeType: charge.chargeType,
                            code: 'XXXXXX',
                            frequency: charge.frequency,
                            includeInYield: true,
                            minimumAmount: 0,
                            name: charge.chargeType,
                            percent: 0,
                            productId: new mongoose_1.default.Types.ObjectId(String(savedProduct._id)),
                        };
                        return newCharge;
                    });
                    await this.saveLoanCharges(borrowerId, charges, true);
                }
            }
            else {
                await LoanProducts_model_1.LoanProduct.findByIdAndUpdate(product._id, product);
                if (updateChargesForProduct) {
                    await this.updatedChargeCodes(product._id.toString());
                }
                const index = productIdsToRemove.indexOf(product._id.toString());
                if (index > -1) {
                    productIdsToRemove.splice(index, 1);
                }
            }
        }));
        await Promise.all(productIdsToRemove.map(async (id) => {
            await LoanProducts_model_1.LoanProduct.findByIdAndDelete(id);
            const foundLinkedCharges = await LoanCharges_model_1.LoanCharge.find({ productId: new mongoose_1.default.Types.ObjectId(id) });
            await Promise.all(foundLinkedCharges.map(async (foundLinked) => {
                await LoanCharges_model_1.LoanCharge.findByIdAndDelete(foundLinked._id);
            }));
        }));
    }
    async getLoanCharges(borrowerId) {
        return LoanCharges_model_1.LoanCharge.aggregate([
            {
                $match: {
                    'borrowerId': new mongoose_1.default.Types.ObjectId(borrowerId),
                    'deletedAt': { $exists: false },
                },
            }, {
                $sort: {
                    'productId': 1,
                    'order': 1,
                },
            }, {
                $unset: loan_products_db_1.fieldsToUnset,
            }, {
                $addFields: {
                    valid: true,
                },
            },
        ]);
    }
    async getLoanChargeForProduct(productId) {
        return (0, loan_charges_db_1.getLoanChargeForProduct)(productId);
    }
    validateLoanCharge(loanCharge) {
        const validationResult = LoanCharges_model_1.chargeViewValidationSchema.validate(loanCharge);
        if (validationResult.error) {
            console.error('Validation error for loan charge:', validationResult.error.details);
        }
        return !!validationResult.error;
    }
    async saveLoanCharges(borrowerId, loanCharges, quickCreate = false) {
        const mappedCharges = loanCharges.map((charge, order) => {
            return { ...charge, borrowerId: new mongoose_1.default.Types.ObjectId(borrowerId), order };
        });
        const allCharges = await this.getLoanCharges(borrowerId);
        const chargeIdsToRemove = allCharges.map((charge) => charge._id.toString());
        await Promise.all(mappedCharges.map(async (charge) => {
            if (!quickCreate) {
                if (!this.validateCharge(charge)) {
                    if (charge._id) {
                        const index = chargeIdsToRemove.indexOf(charge._id.toString());
                        if (index > -1) {
                            chargeIdsToRemove.splice(index, 1);
                        }
                    }
                    return;
                }
            }
            if (!charge._id || charge._id.toString().includes('new_')) {
                const { _id, ...rest } = charge;
                const newCharge = new LoanCharges_model_1.LoanCharge(rest);
                await newCharge.save();
            }
            else {
                await LoanCharges_model_1.LoanCharge.findByIdAndUpdate(charge._id, charge);
                const index = chargeIdsToRemove.indexOf(charge._id.toString());
                if (index > -1) {
                    chargeIdsToRemove.splice(index, 1);
                }
            }
        }));
        if (!quickCreate) {
            await Promise.all(chargeIdsToRemove.map(async (id) => {
                await LoanCharges_model_1.LoanCharge.findByIdAndUpdate(id, { deletedAt: new Date() });
            }));
        }
    }
    async getChargeIds(productIds, chargeType = null) {
        const loanChargesGrouped = await Promise.all(productIds.map(async (productId) => {
            return await this.getLoanChargeForProduct(productId);
        }));
        const loanCharges = loanChargesGrouped.reduce((acc, group) => [...acc, ...group], []);
        const filteredCharges = chargeType ? loanCharges.filter((charge) => charge.chargeType === chargeType) : loanCharges;
        return filteredCharges.map((charge) => charge._id);
    }
    async getLoanStatementsForProduct(filter, paginatorOptions = null, chargeType = null) {
        const chargesIds = await this.getChargeIds(filter.productIds, chargeType);
        return LoanStatementTransaction_model_1.LoanStatementTransactionModel.aggregate([
            {
                $match: {
                    $and: [
                        { 'date': { $gte: filter.start } },
                        { 'date': { $lte: (0, dayjs_1.default)(filter.end).endOf('day').toDate() } },
                        { 'chargeId': { '$in': chargesIds } },
                    ],
                },
            }, {
                $sort: {
                    'date': 1,
                    'order': 1,
                },
            },
            ...(0, collaterals_db_1.ITEMS_PAGINATION)(paginatorOptions),
        ]);
    }
    async getLoanStatementsForIdsAndCharge(filter, paginatorOptions = null, chargeType = null) {
        const chargesIds = await this.getChargeIds(filter.productIds, chargeType);
        return LoanStatementTransaction_model_1.LoanStatementTransactionModel.aggregate([
            {
                $match: {
                    $and: [
                        { '_id': { '$in': filter.transactionIds } },
                        { 'chargeId': { '$in': chargesIds } },
                    ],
                },
            }, {
                $sort: {
                    'date': 1,
                    'order': 1,
                },
            },
            ...(0, collaterals_db_1.ITEMS_PAGINATION)(paginatorOptions),
        ]);
    }
    async getLoanStatementsForForProductWithCharges(filter, paginatorOptions) {
        const loanChargesGrouped = await Promise.all(filter.productIds.map(async (productId) => {
            return await this.getLoanChargeForProduct(productId);
        }));
        const loanCharges = loanChargesGrouped.reduce((acc, group) => [...acc, ...group], []);
        const chargesIds = loanCharges.map((charge) => charge._id);
        return LoanStatementTransaction_model_1.LoanStatementTransactionModel.aggregate([
            {
                $match: {
                    $and: [
                        { 'date': { $gte: filter.start } },
                        { 'date': { $lte: filter.end } },
                        { 'chargeId': { '$in': chargesIds } },
                    ],
                },
            }, {
                $sort: {
                    'date': 1,
                    'order': 1,
                },
            }, {
                $lookup: {
                    from: 'loan_charges',
                    localField: 'chargeId',
                    foreignField: '_id',
                    as: 'charge',
                },
            }, {
                $unwind: {
                    path: '$charge',
                },
            },
            ...(0, collaterals_db_1.ITEMS_PAGINATION)(paginatorOptions),
        ]);
    }
    async getTotalLoanStatementsForProduct(filter) {
        const loanChargesGrouped = await Promise.all(filter.productIds.map(async (productId) => {
            return await this.getLoanChargeForProduct(productId);
        }));
        const loanCharges = loanChargesGrouped.reduce((acc, group) => [...acc, ...group], []);
        const chargesIds = loanCharges.map((charge) => charge._id);
        return LoanStatementTransaction_model_1.LoanStatementTransactionModel.countDocuments({
            $and: [
                { 'chargeId': { $in: chargesIds } },
                { 'date': { $gte: filter.start } },
                { 'date': { $lte: filter.end } },
            ],
        });
    }
    async updatedChargeCodes(productId) {
        const product = await LoanProducts_model_1.LoanProduct.findById(productId).lean();
        const borrowerProductCode = product.code.trim().slice(3, 6);
        const charges = await this.getLoanChargeForProduct(productId);
        await Promise.all(charges.map(async (charge) => {
            const newCode = charge.code.trim().slice(0, 3) + borrowerProductCode;
            await LoanCharges_model_1.LoanCharge.findByIdAndUpdate(charge._id, { code: newCode });
        }));
    }
}
exports.LoanChargesService = LoanChargesService;
