import { GraphQLError } from 'graphql';
import { AdminContextProvider } from '../../../admin-context/providers/admin-context.provider.js';
import { getChargeBusinesses } from '../../../charges/helpers/common.helper.js';
import { ExchangeProvider } from '../../../exchange-rates/providers/exchange.provider.js';
import { FinancialAccountsProvider } from '../../../financial-accounts/providers/financial-accounts.provider.js';
import { TransactionsProvider } from '../../../transactions/providers/transactions.provider.js';
import { ledgerEntryFromMainTransaction } from '../../helpers/common-charge-ledger.helper.js';
import { 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, LedgerError, ledgerProtoToRecordsConverter, updateLedgerBalanceByEntry, validateTransactionBasicVariables, } from '../../helpers/utils.helper.js';
export const generateLedgerRecordsForForeignSecurities = async (charge, { insertLedgerRecordsIfNotExists }, { injector }) => {
    const chargeId = charge.id;
    const { ownerId, defaultLocalCurrency, foreignSecurities: { foreignSecuritiesFeesCategoryId }, } = await injector.get(AdminContextProvider).getVerifiedAdminContext();
    const errors = new Set();
    try {
        // validate ledger records are balanced
        const ledgerBalance = new Map();
        const transactionsPromise = injector
            .get(TransactionsProvider)
            .transactionsByChargeIDLoader.load(chargeId);
        const financialAccountsPromise = injector
            .get(FinancialAccountsProvider)
            .getFinancialAccountsByOwnerIdLoader.load(ownerId);
        const [transactions, financialAccounts, { mainBusinessId }] = await Promise.all([
            transactionsPromise,
            financialAccountsPromise,
            getChargeBusinesses(chargeId, injector),
        ]);
        const foreignSecuritiesAccountId = financialAccounts.find(account => account.type === 'FOREIGN_SECURITIES')?.id;
        if (!foreignSecuritiesAccountId) {
            throw new GraphQLError('Foreign securities account is missing');
        }
        const entriesPromises = [];
        const financialAccountLedgerEntries = [];
        const feeFinancialAccountLedgerEntries = [];
        const miscExpensesLedgerEntries = [];
        // generate ledger from transactions
        const { mainTransactions, feeTransactions } = splitFeeTransactions(transactions);
        const mainTransactionsPromises = mainTransactions.map(async (transaction) => await ledgerEntryFromMainTransaction({ ...transaction, source_reference: 'foreign_securities' }, injector, chargeId, charge.owner_id, mainBusinessId ?? undefined)
            .then(ledgerEntry => {
            financialAccountLedgerEntries.push(ledgerEntry);
            updateLedgerBalanceByEntry(ledgerEntry, ledgerBalance, defaultLocalCurrency);
        })
            .catch(e => {
            if (e instanceof LedgerError) {
                errors.add(e.message);
            }
            else {
                throw e;
            }
        }));
        // create a ledger record for fee transactions
        const feeTransactionsPromises = feeTransactions.map(async (transaction) => {
            if (!foreignSecuritiesFeesCategoryId) {
                errors.add(`Foreign securities fees category ID is missing`);
                return;
            }
            // for each transaction, create a ledger record
            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 accountTaxCategoryId = await getFinancialAccountTaxCategoryId(injector, transaction);
            const isCreditorCounterparty = amount > 0;
            const ledgerEntry = {
                id: transaction.id,
                invoiceDate: transaction.event_date,
                valueDate,
                currency,
                creditAccountID1: isCreditorCounterparty
                    ? foreignSecuritiesFeesCategoryId
                    : accountTaxCategoryId,
                creditAmount1: foreignAmount ? Math.abs(foreignAmount) : undefined,
                localCurrencyCreditAmount1: Math.abs(amount),
                debitAccountID1: isCreditorCounterparty
                    ? accountTaxCategoryId
                    : foreignSecuritiesFeesCategoryId,
                debitAmount1: foreignAmount ? Math.abs(foreignAmount) : undefined,
                localCurrencyDebitAmount1: 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,
            };
            feeFinancialAccountLedgerEntries.push(ledgerEntry);
            updateLedgerBalanceByEntry(ledgerEntry, ledgerBalance, defaultLocalCurrency);
        });
        // generate ledger from misc expenses
        const expensesLedgerPromise = generateMiscExpensesLedger(charge, injector).then(entries => {
            entries.map(entry => {
                entry.ownerId = charge.owner_id;
                miscExpensesLedgerEntries.push(entry);
                updateLedgerBalanceByEntry(entry, ledgerBalance, defaultLocalCurrency);
            });
        });
        entriesPromises.push(...mainTransactionsPromises, ...feeTransactionsPromises, expensesLedgerPromise);
        await Promise.all(entriesPromises);
        const records = [
            ...financialAccountLedgerEntries,
            ...feeFinancialAccountLedgerEntries,
            ...miscExpensesLedgerEntries,
        ];
        if (insertLedgerRecordsIfNotExists) {
            await storeInitialGeneratedRecords(charge.id, records, injector);
        }
        const allowedUnbalancedBusinesses = new Set();
        const mainLedgerEntry = financialAccountLedgerEntries[0];
        if (!mainLedgerEntry.isCreditorCounterparty) {
            allowedUnbalancedBusinesses.add(mainLedgerEntry.debitAccountID1);
        }
        const ledgerBalanceInfo = await getLedgerBalanceInfo(injector, ledgerBalance, errors, allowedUnbalancedBusinesses);
        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=foreign-securities-ledger-generation.resolver.js.map