import { endOfDay } from 'date-fns';
import { GraphQLError } from 'graphql';
import { EMPTY_UUID } from '../../../../../shared/constants.js';
import { dateToTimelessDateString, formatCurrency } from '../../../../../shared/helpers/index.js';
import { AdminContextProvider } from '../../../../admin-context/providers/admin-context.provider.js';
import { ExchangeProvider } from '../../../../exchange-rates/providers/exchange.provider.js';
import { TaxCategoriesProvider } from '../../../../financial-entities/providers/tax-categories.provider.js';
import { businessTransactionsSumFromLedgerRecords } from '../../../../financial-entities/resolvers/business-transactions-sum-from-ledger-records.resolver.js';
import { storeInitialGeneratedRecords } from '../../../helpers/ledgrer-storage.helper.js';
import { generateMiscExpensesLedger } from '../../../helpers/misc-expenses-ledger.helper.js';
import { ledgerProtoToRecordsConverter } from '../../../helpers/utils.helper.js';
export const REVALUATION_LEDGER_DESCRIPTION = 'Revaluation of account';
export const generateLedgerRecordsForExchangeRevaluation = async (charge, { insertLedgerRecordsIfNotExists }, context, info) => {
    try {
        const { injector } = context;
        if (!charge.user_description) {
            return {
                __typename: 'CommonError',
                message: `Revaluation charge must include user description with designated date`,
            };
        }
        // get revaluation date - search for "yyyy-mm-dd" in description
        const dateRegex = /\b(\d{4})-(\d{2})-(\d{2})\b/;
        const matches = charge.user_description.match(dateRegex);
        if (!matches?.length) {
            return {
                __typename: 'CommonError',
                message: `Revaluation charge description must include designated date in yyyy-mm-dd format`,
            };
        }
        const revaluationDate = matches[0];
        const foreignAccountsPromise = injector
            .get(TaxCategoriesProvider)
            .taxCategoryByFinancialAccountOwnerIdsLoader.load(charge.owner_id);
        const [accountTaxCategories] = await Promise.all([foreignAccountsPromise]);
        if (accountTaxCategories.length === 0) {
            throw new Error('No accounts found');
        }
        const { defaultLocalCurrency, general: { taxCategories: { exchangeRevaluationTaxCategoryId }, }, } = await injector.get(AdminContextProvider).getVerifiedAdminContext();
        const foreignAccounts = accountTaxCategories.filter(({ currency }) => currency !== defaultLocalCurrency);
        if (foreignAccounts.length === 0) {
            throw new Error('No foreign accounts found');
        }
        const currencies = new Set(foreignAccounts.map(({ currency }) => currency));
        const cumulativeDate = dateToTimelessDateString(endOfDay(new Date(revaluationDate)));
        const accountsCumulativeBalancePromise = businessTransactionsSumFromLedgerRecords({}, {
            filters: {
                ownerIds: [charge.owner_id],
                businessIDs: foreignAccounts.map(account => account.id),
                toDate: cumulativeDate,
                includeRevaluation: false,
            },
        }, context, info);
        const exchangeRates = new Map();
        const [accountsCumulativeBalanceData] = await Promise.all([
            accountsCumulativeBalancePromise,
            ...Array.from(currencies).map(async (currency) => {
                const rates = await injector
                    .get(ExchangeProvider)
                    .getExchangeRates(currency, defaultLocalCurrency, new Date(revaluationDate));
                exchangeRates.set(currency, rates);
            }),
        ]);
        if (!accountsCumulativeBalanceData || 'message' in accountsCumulativeBalanceData) {
            throw new GraphQLError('Failed to get accounts transaction-based balance');
        }
        const accountsCumulativeBalance = await Promise.all(accountsCumulativeBalanceData.businessTransactionsSum);
        const errors = new Set();
        const entriesPromises = [];
        const ledgerEntries = [];
        const foreignAccountsLedger = foreignAccounts.map(async (account) => {
            const rate = exchangeRates.get(formatCurrency(account.currency));
            if (!rate) {
                errors.add(`No exchange rate found for date ${revaluationDate}, currency "${account.currency}"`);
                return;
            }
            const cumulativeSums = accountsCumulativeBalance.find(balance => balance.businessId === account.id);
            if (!cumulativeSums ||
                (cumulativeSums[account.currency].total === 0 &&
                    cumulativeSums[defaultLocalCurrency].total === 0)) {
                return;
            }
            const cumulativeLocalBalance = cumulativeSums[defaultLocalCurrency].total;
            const cumulativeForeignBalance = cumulativeSums[account.currency].total;
            const revaluationDiff = cumulativeLocalBalance - cumulativeForeignBalance * rate;
            if (Math.abs(revaluationDiff) < 0.005) {
                return;
            }
            const isCreditorCounterparty = revaluationDiff < 0;
            const ledgerEntry = {
                id: EMPTY_UUID,
                invoiceDate: new Date(revaluationDate),
                valueDate: new Date(revaluationDate),
                currency: defaultLocalCurrency,
                isCreditorCounterparty,
                ...(isCreditorCounterparty
                    ? {
                        creditAccountID1: account.id,
                        debitAccountID1: exchangeRevaluationTaxCategoryId,
                    }
                    : {
                        creditAccountID1: exchangeRevaluationTaxCategoryId,
                        debitAccountID1: account.id,
                    }),
                localCurrencyCreditAmount1: Math.abs(revaluationDiff),
                localCurrencyDebitAmount1: Math.abs(revaluationDiff),
                description: `${REVALUATION_LEDGER_DESCRIPTION} "${account.name}"`,
                ownerId: charge.owner_id,
                chargeId: charge.id,
                currencyRate: rate,
            };
            ledgerEntries.push(ledgerEntry);
        });
        entriesPromises.push(...foreignAccountsLedger);
        // generate ledger from misc expenses
        const expensesLedgerPromise = generateMiscExpensesLedger(charge, injector).then(entries => {
            entries.map(entry => {
                entry.ownerId = charge.owner_id;
                ledgerEntries.push(entry);
            });
        });
        entriesPromises.push(expensesLedgerPromise);
        await Promise.all(entriesPromises);
        if (insertLedgerRecordsIfNotExists) {
            await storeInitialGeneratedRecords(charge.id, ledgerEntries, injector);
        }
        return {
            records: ledgerProtoToRecordsConverter(ledgerEntries),
            charge,
            balance: {
                isBalanced: true,
                unbalancedEntities: [],
                balanceSum: 0,
                financialEntities: [],
            },
            errors: Array.from(errors),
        };
    }
    catch (e) {
        return {
            __typename: 'CommonError',
            message: `Failed to generate ledger records for charge ID="${charge.id}"\n${e}`,
        };
    }
};
//# sourceMappingURL=revaluation-ledger-generation.resolver.js.map