import { AdminContextProvider } from '../../../admin-context/providers/admin-context.provider.js';
import { ExchangeProvider } from '../../../exchange-rates/providers/exchange.provider.js';
import { TransactionsProvider } from '../../../transactions/providers/transactions.provider.js';
import { getEntriesFromFeeTransaction, splitFeeTransactions, } from '../../helpers/fee-transactions.js';
import { storeInitialGeneratedRecords } from '../../helpers/ledgrer-storage.helper.js';
import { generateMiscExpensesLedger } from '../../helpers/misc-expenses-ledger.helper.js';
import { getFinancialAccountTaxCategoryId, getLedgerBalanceInfo, isTransactionsOppositeSign, LedgerError, ledgerProtoToRecordsConverter, updateLedgerBalanceByEntry, validateTransactionBasicVariables, } from '../../helpers/utils.helper.js';
export const generateLedgerRecordsForInternalTransfer = async (charge, { insertLedgerRecordsIfNotExists }, { injector }) => {
    const { defaultLocalCurrency, general: { taxCategories: { exchangeRateTaxCategoryId }, }, } = await injector.get(AdminContextProvider).getVerifiedAdminContext();
    const chargeId = charge.id;
    const errors = new Set();
    try {
        // validate ledger records are balanced
        const ledgerBalance = new Map();
        const dates = new Set();
        const currencies = new Set();
        // generate ledger from transactions
        const mainFinancialAccountLedgerEntries = [];
        const feeFinancialAccountLedgerEntries = [];
        let originEntry = undefined;
        let destinationEntry = undefined;
        // Get all transactions
        const transactions = await injector
            .get(TransactionsProvider)
            .transactionsByChargeIDLoader.load(chargeId);
        const { mainTransactions, feeTransactions } = splitFeeTransactions(transactions);
        if (mainTransactions.length !== 2) {
            errors.add(`Internal transfer Charge must include two main transactions`);
        }
        try {
            if (!isTransactionsOppositeSign(mainTransactions)) {
                errors.add(`Internal transfer Charge must include two main transactions with opposite sign`);
            }
        }
        catch (e) {
            if (e instanceof LedgerError) {
                errors.add(e.message);
            }
            else {
                throw e;
            }
        }
        // create a ledger record for main transactions
        const mainFinancialAccountLedgerEntriesPromises = mainTransactions.map(async (transaction) => {
            try {
                const { currency, valueDate } = validateTransactionBasicVariables(transaction);
                let amount = Number(transaction.amount);
                let foreignAmount = undefined;
                if (currency !== defaultLocalCurrency) {
                    // get exchange rate for currency
                    const exchangeRate = await injector
                        .get(ExchangeProvider)
                        .getExchangeRates(currency, defaultLocalCurrency, valueDate);
                    foreignAmount = amount;
                    // calculate amounts in ILS
                    amount = exchangeRate * amount;
                }
                const financialAccountTaxCategoryId = await getFinancialAccountTaxCategoryId(injector, transaction);
                const isCreditorCounterparty = amount > 0;
                const ledgerEntry = {
                    id: transaction.id,
                    invoiceDate: transaction.event_date,
                    valueDate,
                    currency,
                    ...(isCreditorCounterparty
                        ? {
                            debitAccountID1: financialAccountTaxCategoryId,
                        }
                        : {
                            creditAccountID1: financialAccountTaxCategoryId,
                        }),
                    debitAmount1: foreignAmount ? Math.abs(foreignAmount) : undefined,
                    localCurrencyDebitAmount1: Math.abs(amount),
                    creditAmount1: foreignAmount ? Math.abs(foreignAmount) : undefined,
                    localCurrencyCreditAmount1: Math.abs(amount),
                    description: transaction.source_description ?? undefined,
                    reference: transaction.origin_key,
                    isCreditorCounterparty,
                    ownerId: charge.owner_id,
                    currencyRate: transaction.currency_rate ? Number(transaction.currency_rate) : undefined,
                    chargeId,
                };
                mainFinancialAccountLedgerEntries.push(ledgerEntry);
                updateLedgerBalanceByEntry(ledgerEntry, ledgerBalance, defaultLocalCurrency);
                dates.add(valueDate.getTime());
                currencies.add(currency);
            }
            catch (e) {
                if (e instanceof LedgerError) {
                    errors.add(e.message);
                }
                else {
                    throw e;
                }
            }
        });
        // create a ledger record for fee transactions
        const feeFinancialAccountLedgerEntriesPromises = feeTransactions.map(async (transaction) => {
            if (!transaction.is_fee) {
                return;
            }
            try {
                const ledgerEntries = await getEntriesFromFeeTransaction(transaction, charge, injector).catch(e => {
                    if (e instanceof LedgerError) {
                        errors.add(e.message);
                    }
                    else {
                        throw e;
                    }
                });
                if (!ledgerEntries) {
                    return;
                }
                feeFinancialAccountLedgerEntries.push(...ledgerEntries);
                ledgerEntries.map(ledgerEntry => {
                    updateLedgerBalanceByEntry(ledgerEntry, ledgerBalance, defaultLocalCurrency);
                    dates.add(ledgerEntry.valueDate.getTime());
                    currencies.add(ledgerEntry.currency);
                });
            }
            catch (e) {
                if (e instanceof LedgerError) {
                    errors.add(e.message);
                }
                else {
                    throw e;
                }
            }
        });
        // create ledger records for misc expenses
        const miscExpensesLedgerPromise = generateMiscExpensesLedger(charge, injector).then(entries => {
            entries.map(entry => {
                entry.ownerId = charge.owner_id;
                feeFinancialAccountLedgerEntries.push(entry);
                updateLedgerBalanceByEntry(entry, ledgerBalance, defaultLocalCurrency);
                dates.add(entry.valueDate.getTime());
                currencies.add(entry.currency);
            });
        });
        await Promise.all([
            ...feeFinancialAccountLedgerEntriesPromises,
            ...mainFinancialAccountLedgerEntriesPromises,
            miscExpensesLedgerPromise,
        ]);
        for (const ledgerEntry of mainFinancialAccountLedgerEntries) {
            if (ledgerEntry.isCreditorCounterparty) {
                destinationEntry = ledgerEntry;
            }
            else {
                originEntry = ledgerEntry;
            }
        }
        const { balanceSum } = await getLedgerBalanceInfo(injector, ledgerBalance);
        const miscLedgerEntries = [];
        if (!originEntry || !destinationEntry) {
            errors.add(`Internal transfer Charge must include two main transactions`);
        }
        else if (Math.abs(balanceSum) > 0.005) {
            // Add ledger completion entries
            const hasMultipleDates = dates.size > 1;
            const hasForeignCurrency = currencies.size > (currencies.has(defaultLocalCurrency) ? 1 : 0);
            if (hasMultipleDates && hasForeignCurrency) {
                const amount = Math.abs(balanceSum);
                const isCreditorCounterparty = balanceSum > 0;
                const latestDate = new Date(Math.max(originEntry.valueDate.getTime(), originEntry.invoiceDate.getTime(), destinationEntry.valueDate.getTime(), destinationEntry.invoiceDate.getTime()));
                const ledgerEntry = {
                    id: destinationEntry.id, // NOTE: this field is dummy
                    creditAccountID1: isCreditorCounterparty ? undefined : exchangeRateTaxCategoryId,
                    creditAmount1: undefined,
                    localCurrencyCreditAmount1: amount,
                    debitAccountID1: isCreditorCounterparty ? exchangeRateTaxCategoryId : undefined,
                    debitAmount1: undefined,
                    localCurrencyDebitAmount1: amount,
                    description: 'Exchange ledger record',
                    isCreditorCounterparty,
                    invoiceDate: latestDate,
                    valueDate: latestDate,
                    currency: defaultLocalCurrency,
                    ownerId: destinationEntry.ownerId,
                    chargeId,
                };
                miscLedgerEntries.push(ledgerEntry);
                updateLedgerBalanceByEntry(ledgerEntry, ledgerBalance, defaultLocalCurrency);
            }
        }
        const ledgerBalanceInfo = await getLedgerBalanceInfo(injector, ledgerBalance, errors);
        const records = [
            ...mainFinancialAccountLedgerEntries,
            ...feeFinancialAccountLedgerEntries,
            ...miscLedgerEntries,
        ];
        if (insertLedgerRecordsIfNotExists) {
            await storeInitialGeneratedRecords(charge.id, records, injector);
        }
        return {
            records: ledgerProtoToRecordsConverter(records),
            charge,
            balance: ledgerBalanceInfo,
            errors: Array.from(errors),
        };
    }
    catch (e) {
        return {
            __typename: 'CommonError',
            message: `Failed to generate ledger records for charge ID="${chargeId}"\n${e}`,
        };
    }
};
//# sourceMappingURL=internal-transfer-ledger-generation.resolver.js.map