"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.BorrowerService = exports.borrowerDataModels = void 0;
const mongoose_1 = __importDefault(require("mongoose"));
const BorrowerSettings_model_1 = require("../models/BorrowerSettings.model");
const Borrower_model_1 = require("../models/Borrower.model");
const BorrowerData_model_1 = require("../models/BorrowerData.model");
const BorrowerDataContact_model_1 = require("../models/BorrowerDataContact.model");
const BorrowerDataGuarantee_model_1 = require("../models/BorrowerDataGuarantee.model");
const BorrowerDataLocation_model_1 = require("../models/BorrowerDataLocation.model");
const BorrowerDataBank_model_1 = require("../models/BorrowerDataBank.model");
const BorrowerDataInsurance_model_1 = require("../models/BorrowerDataInsurance.model");
const BorrowerDataReceivable_model_1 = require("../models/BorrowerDataReceivable.model");
const BorrowerDataInventory_model_1 = require("../models/BorrowerDataInventory.model");
const BorrowerDataTerm_model_1 = require("../models/BorrowerDataTerm.model");
const BorrowerDataBootCollateral_model_1 = require("../models/BorrowerDataBootCollateral.model");
const collaterals_enum_1 = require("../enums/collaterals.enum");
exports.borrowerDataModels = {
    receivables: BorrowerDataReceivable_model_1.BorrowerDataReceivableModel,
    inventories: BorrowerDataInventory_model_1.BorrowerDataInventoryModel,
    terms: BorrowerDataTerm_model_1.BorrowerDataTermModel,
    bootCollaterals: BorrowerDataBootCollateral_model_1.BorrowerDataBootCollateralModel,
    contacts: BorrowerDataContact_model_1.BorrowerDataContactModel,
    guarantees: BorrowerDataGuarantee_model_1.BorrowerDataGuaranteeModel,
    locations: BorrowerDataLocation_model_1.BorrowerDataLocationModel,
    banks: BorrowerDataBank_model_1.BorrowerDataBankModel,
    insurance: BorrowerDataInsurance_model_1.BorrowerDataInsuranceModel,
};
class BorrowerService {
    getCollateralsService;
    getLoanStatementStatusService;
    getLoanTransactionsService;
    getSignsService;
    constructor(getCollateralsService, getLoanStatementStatusService, getLoanTransactionsService, getSignsService) {
        this.getCollateralsService = getCollateralsService;
        this.getLoanStatementStatusService = getLoanStatementStatusService;
        this.getLoanTransactionsService = getLoanTransactionsService;
        this.getSignsService = getSignsService;
    }
    isBorrowerAllowed(userAccess, requestedBorrowerId) {
        if (!userAccess) {
            return false;
        }
        if (userAccess.allBorrowers) {
            return true;
        }
        return userAccess.borrowersAccess?.includes(requestedBorrowerId);
    }
    checkBorrower(user, requestedBorrowerId) {
        const allowedBorrower = this.isBorrowerAllowed(user, requestedBorrowerId);
        if (!allowedBorrower) {
            throw new Error('Non allowed borrower');
        }
        return true;
    }
    async getBorrowerById(borrowerId) {
        return Borrower_model_1.BorrowerModel.findById(borrowerId).lean();
    }
    async isNameCodeAllowed(borrower) {
        const existingUserName = await Borrower_model_1.BorrowerModel.findOne({ name: borrower.name });
        const existingUserCode = await Borrower_model_1.BorrowerModel.findOne({ code: borrower.code });
        if (existingUserName && existingUserName.id !== borrower._id) {
            return { message: 'There already exists a borrower with this name' };
        }
        if (existingUserCode && existingUserCode.id !== borrower._id) {
            return { message: 'There already exists a borrower with this code' };
        }
        return null;
    }
    async getDefaultSettings(borrowerId) {
        const options = await BorrowerData_model_1.BorrowerDataOptionModel.find().lean();
        const dataOptions = options.reduce((acc, option) => {
            return [
                ...acc,
                { dataOption: option._id, dataValue: option.type === 'boolean' ? false : '' },
            ];
        }, []);
        const defaultSettings = {
            borrower: new mongoose_1.default.Types.ObjectId(borrowerId),
            queries: {
                [collaterals_enum_1.ECollaterals.INVENTORY]: {},
                [collaterals_enum_1.ECollaterals.RECEIVABLE]: {},
                [collaterals_enum_1.ECollaterals.OTHER]: {},
                [collaterals_enum_1.ECollaterals.CASH]: {},
                [collaterals_enum_1.ECollaterals.SALES]: {},
            },
            dataTypes: {
                upload: [],
            },
            data: {
                options: dataOptions,
                receivables: [],
                inventories: [],
                contacts: [],
                guarantees: [],
                banks: [],
                insurance: [],
                locations: [],
                terms: [],
                bootCollaterals: [],
            },
            dataAvailability: {
                receivables: false,
                inventories: false,
            },
        };
        return defaultSettings;
    }
    async addOptionNames(unmappedSettings) {
        const borrowerDataOptions = await BorrowerData_model_1.BorrowerDataOptionModel.find().lean();
        const borrowerDataOptionsMap = new Map();
        borrowerDataOptions.forEach((option) => borrowerDataOptionsMap.set(option._id.toString(), option.name));
        unmappedSettings.data.options = unmappedSettings.data.options.map((item) => {
            const dataName = borrowerDataOptionsMap.get(item.dataOption.toString());
            return { ...item, dataName };
        });
        return unmappedSettings;
    }
    async getBorrowerOption(borrowerId, optionName) {
        const borrowerSettings = await this.getBorrowerSettings(borrowerId);
        if (!borrowerSettings) {
            return null;
        }
        const foundOption = borrowerSettings.data.options.find((option) => option.dataName === optionName);
        if (!foundOption) {
            return null;
        }
        return foundOption;
    }
    async getBorrowerSettings(borrowerId) {
        const borrowerSettings = await BorrowerSettings_model_1.BorrowerSettingsModel
            .findOne({ borrower: borrowerId })
            .populate('data.options')
            .populate('data.receivables')
            .populate('data.inventories')
            .populate('data.terms')
            .populate('data.bootCollaterals')
            .populate('data.contacts')
            .populate('data.guarantees')
            .populate('data.locations')
            .populate('data.banks')
            .populate('data.insurance')
            .lean();
        const options = await BorrowerData_model_1.BorrowerDataOptionModel.find({ isActive: true }).lean();
        const optionsMap = new Map(options.map((opt) => [opt._id.toString(), opt]));
        if (borrowerSettings?.data?.options) {
            borrowerSettings.data.options = borrowerSettings.data.options
                .filter((item) => optionsMap.has(item.dataOption.toString()))
                .sort((a, b) => {
                const foundA = optionsMap.get(a.dataOption.toString());
                const foundB = optionsMap.get(b.dataOption.toString());
                if (!foundA || !foundB)
                    return 0;
                return foundA.order - foundB.order;
            });
        }
        const unmappedSettings = borrowerSettings ? borrowerSettings : await this.getDefaultSettings(borrowerId);
        return await this.addOptionNames(unmappedSettings);
    }
    async getBorrowerByCode(code) {
        return Borrower_model_1.BorrowerModel.findOne({ code });
    }
    async getExtraInfo(borrowerId) {
        const loanTransactionsService = this.getLoanTransactionsService();
        const collateralsService = this.getCollateralsService();
        const signsService = this.getSignsService();
        const extraInfo = await loanTransactionsService.getLoanBalanceForBorrowerId(borrowerId);
        const { lastBBCDate, lastBBCId } = await collateralsService.getLatestBBCDate(borrowerId);
        const lastSignedBBCDate = await signsService.getLatestSignedBBCDate(borrowerId);
        return {
            extraInfo,
            lastBBCDate,
            lastBBCId,
            lastSignedBBCDate: lastSignedBBCDate?.bbcDate || null,
            lastSignedBBCId: lastSignedBBCDate?._id.toString() || null,
        };
    }
    async updateBorrowerData(borrowerSettings) {
        const results = await Promise.all(Object.entries(exports.borrowerDataModels).map(async ([modelName, model]) => {
            const newIds = await Promise.all(borrowerSettings.data[modelName].map(async (contact) => {
                if (!contact._id) {
                    delete contact._id;
                    const newContact = new model(contact);
                    const savedContact = await newContact.save();
                    return savedContact._id;
                }
                await model.findByIdAndUpdate(contact._id, contact);
                return new mongoose_1.default.Types.ObjectId(contact._id);
            }));
            return { [modelName]: newIds };
        }));
        return results.reduce((acc, modelCollection) => {
            const collections = Object.entries(modelCollection).reduce((innerAcc, [modelName, ids]) => {
                return { ...innerAcc, [modelName]: ids };
            }, {});
            return { ...acc, ...collections };
        }, {});
    }
    async updateBorrowerSettings(borrowerId, borrowerSettings) {
        const newOptions = [];
        await Promise.all(borrowerSettings.data.options.map(async (option) => {
            if (option._id) {
                await BorrowerData_model_1.BorrowerDataModel.findByIdAndUpdate(option._id, { dataValue: option.dataValue });
            }
            else {
                const newOption = await BorrowerData_model_1.BorrowerDataModel.create(option);
                await newOption.save();
                newOptions.push(newOption._id);
            }
        }));
        const oldSettings = await BorrowerSettings_model_1.BorrowerSettingsModel.findOne({ borrower: borrowerId }).lean();
        const updatedData = await this.updateBorrowerData(borrowerSettings);
        await Promise.all(Object.keys(exports.borrowerDataModels).map(async (modelName) => {
            borrowerSettings.data[modelName] = updatedData[modelName];
            if (oldSettings) {
                const oldIds = oldSettings.data[modelName].map((id) => id.toString());
                const updatedIds = updatedData[modelName].map((id) => id.toString());
                const idsToRemoved = oldIds.reduce((acc, oldId) => {
                    if (updatedIds.find((id) => id === oldId)) {
                        return acc;
                    }
                    return [...acc, oldId];
                }, []);
                await Promise.all(idsToRemoved.map(async (id) => {
                    await exports.borrowerDataModels[modelName].findByIdAndDelete(id);
                }));
            }
        }));
        const foundSettings = await BorrowerSettings_model_1.BorrowerSettingsModel.findOne({ borrower: borrowerId });
        if (foundSettings) {
            await BorrowerSettings_model_1.BorrowerSettingsModel.findOneAndUpdate({ borrower: borrowerId }, { borrower: borrowerId, ...borrowerSettings }, { upsert: true });
        }
        else {
            const newSettings = await BorrowerSettings_model_1.BorrowerSettingsModel.create({ borrower: borrowerId, ...borrowerSettings, data: { ...borrowerSettings.data, options: newOptions } });
            await newSettings.save();
        }
    }
    async getBorrowerCodesMap() {
        const borrowers = await Borrower_model_1.BorrowerModel.find().lean();
        const codesMap = new Map();
        borrowers.forEach((borrower) => {
            codesMap.set(borrower.code.toLowerCase(), borrower._id.toString());
        });
        return codesMap;
    }
    async getBorrowerStatementDetails(borrowers) {
        const allBorrowersWithExtraInfo = await Promise.all(borrowers.map(async (borrower) => {
            const borrowerId = borrower._id.toString();
            const loanStatementStatusService = this.getLoanStatementStatusService();
            const statementStatuses = await loanStatementStatusService.getBorrowerStatement(borrowerId);
            return { [borrowerId]: statementStatuses };
        }));
        return allBorrowersWithExtraInfo.reduce((acc, borrower) => {
            const borrowerRes = Object.entries(borrower).reduce((innerAcc, [borrowerId, values]) => ({
                ...innerAcc,
                [borrowerId]: values,
            }), {});
            return { ...acc, ...borrowerRes };
        }, {});
    }
}
exports.BorrowerService = BorrowerService;
