import { format } from 'date-fns';
import { dateToTimelessDateString, getMonthFromDescription, } from '../../../../shared/helpers/index.js';
import { AdminContextProvider } from '../../../admin-context/providers/admin-context.provider.js';
import { getChargeTransactionsMeta } from '../../../charges/helpers/common.helper.js';
import { ExchangeProvider } from '../../../exchange-rates/providers/exchange.provider.js';
import { getVatRecords } from '../../../reports/resolvers/get-vat-records.resolver.js';
import { TransactionsProvider } from '../../../transactions/providers/transactions.provider.js';
import { storeInitialGeneratedRecords } from '../../helpers/ledgrer-storage.helper.js';
import { generateMiscExpensesLedger } from '../../helpers/misc-expenses-ledger.helper.js';
import { getVatDataFromVatReportRecords } from '../../helpers/monthly-vat-ledger-generation.helper.js';
import { generatePartialLedgerEntry, getFinancialAccountTaxCategoryId, getLedgerBalanceInfo, LedgerError, ledgerProtoToRecordsConverter, updateLedgerBalanceByEntry, validateTransactionRequiredVariables, } from '../../helpers/utils.helper.js';
import { UnbalancedBusinessesProvider } from '../../providers/unbalanced-businesses.provider.js';
export const generateLedgerRecordsForMonthlyVat = async (charge, { insertLedgerRecordsIfNotExists }, { injector }, __) => {
    const chargeId = charge.id;
    const errors = new Set();
    try {
        const { defaultLocalCurrency, authorities: { vatBusinessId, inputVatTaxCategoryId, outputVatTaxCategoryId, propertyOutputVatTaxCategoryId, }, general: { taxCategories: { balanceCancellationTaxCategoryId }, }, } = await injector.get(AdminContextProvider).getVerifiedAdminContext();
        // figure out VAT month
        const { transactionsMinDebitDate, transactionsMinEventDate } = await getChargeTransactionsMeta(chargeId, injector);
        const transactionDate = transactionsMinDebitDate ?? transactionsMinEventDate ?? undefined;
        if (!charge.user_description) {
            errors.add(`Monthly VAT charge must have description that indicates it's month (ID="${chargeId}")`);
        }
        const vatDates = charge.user_description
            ? getMonthFromDescription(charge.user_description, transactionDate)
            : null;
        if (!vatDates?.length) {
            errors.add(`Cannot extract charge ID="${chargeId}" VAT month`);
        }
        // get VAT relevant records
        const vatRecordsPromises = (vatDates ?? []).map(async (vatDate) => {
            const [year, month] = (vatDate ?? format(new Date(), 'yyyy-MM')).split('-').map(Number);
            const ledgerDate = new Date(year, month, 0);
            const monthDate = dateToTimelessDateString(new Date(year, month - 1, 15));
            const { income, expenses } = await getVatRecords({ filters: { financialEntityId: charge.owner_id, monthDate } }, injector);
            return {
                income,
                expenses,
                ledgerDate,
                vatDate,
            };
        });
        const transactionsPromise = injector
            .get(TransactionsProvider)
            .transactionsByChargeIDLoader.load(chargeId);
        const unbalancedBusinessesPromise = injector
            .get(UnbalancedBusinessesProvider)
            .getChargeUnbalancedBusinessesByChargeIds.load(chargeId);
        const [vatRecords, transactions, unbalancedBusinesses] = await Promise.all([
            Promise.all(vatRecordsPromises),
            transactionsPromise,
            unbalancedBusinessesPromise,
        ]);
        const accountingLedgerEntries = [];
        const financialAccountLedgerEntries = [];
        const miscExpensesLedgerEntries = [];
        const ledgerBalance = new Map();
        // for each transaction, create a ledger record
        const mainTransactionsPromises = transactions.map(async (preValidatedTransaction) => {
            try {
                const transaction = validateTransactionRequiredVariables(preValidatedTransaction);
                if (transaction.currency !== defaultLocalCurrency) {
                    throw new LedgerError(`Monthly VAT currency supposed to be local, got ${transaction.currency}`);
                }
                // preparations for core ledger entries
                let exchangeRate = undefined;
                if (transaction.currency !== defaultLocalCurrency) {
                    // get exchange rate for currency
                    exchangeRate = await injector
                        .get(ExchangeProvider)
                        .getExchangeRates(transaction.currency, defaultLocalCurrency, transaction.debit_timestamp);
                }
                const partialEntry = generatePartialLedgerEntry(transaction, charge.owner_id, exchangeRate);
                const financialAccountTaxCategoryId = await getFinancialAccountTaxCategoryId(injector, transaction);
                const ledgerEntry = {
                    ...partialEntry,
                    ...(partialEntry.isCreditorCounterparty
                        ? {
                            creditAccountID1: vatBusinessId,
                            debitAccountID1: financialAccountTaxCategoryId,
                        }
                        : {
                            creditAccountID1: financialAccountTaxCategoryId,
                            debitAccountID1: vatBusinessId,
                        }),
                };
                financialAccountLedgerEntries.push(ledgerEntry);
                updateLedgerBalanceByEntry(ledgerEntry, ledgerBalance, defaultLocalCurrency);
            }
            catch (e) {
                if (e instanceof LedgerError) {
                    errors.add(e.message);
                }
                else {
                    throw e;
                }
            }
        });
        const miscExpensesLedgerPromise = generateMiscExpensesLedger(charge, injector).then(entries => {
            entries.map(entry => {
                entry.ownerId = charge.owner_id;
                miscExpensesLedgerEntries.push(entry);
                updateLedgerBalanceByEntry(entry, ledgerBalance, defaultLocalCurrency);
            });
        });
        const isProperty = charge.is_property;
        if (isProperty && !propertyOutputVatTaxCategoryId) {
            console.error(`Charge ID="${chargeId}" is marked as property but no Property Output VAT tax category is configured in admin context`);
            return {
                __typename: 'CommonError',
                message: `One of the charges is marked as property but no Property Output VAT tax category is configured in admin context`,
            };
        }
        await Promise.all([...mainTransactionsPromises, miscExpensesLedgerPromise]);
        for (const { income, expenses, ledgerDate, vatDate } of vatRecords) {
            const [incomeVat, roundedIncomeVat] = getVatDataFromVatReportRecords(income);
            const [expensesVat, roundedExpensesVat] = getVatDataFromVatReportRecords(expenses);
            const accountingLedgerMaterials = [];
            accountingLedgerMaterials.push({
                amount: roundedIncomeVat,
                taxCategoryId: inputVatTaxCategoryId,
                counterpartyId: vatBusinessId,
            }, {
                amount: roundedExpensesVat * -1,
                taxCategoryId: isProperty ? propertyOutputVatTaxCategoryId : outputVatTaxCategoryId,
                counterpartyId: vatBusinessId,
            });
            const roundedIncomeVatDiff = Math.round((roundedIncomeVat - incomeVat) * 100) / 100;
            if (roundedIncomeVatDiff) {
                accountingLedgerMaterials.push({
                    amount: roundedIncomeVatDiff * -1,
                    taxCategoryId: inputVatTaxCategoryId,
                    counterpartyId: balanceCancellationTaxCategoryId,
                });
            }
            const roundedExpensesVatDiff = Math.round((roundedExpensesVat - expensesVat) * 100) / 100;
            if (roundedExpensesVatDiff) {
                accountingLedgerMaterials.push({
                    amount: roundedExpensesVatDiff,
                    taxCategoryId: isProperty ? propertyOutputVatTaxCategoryId : outputVatTaxCategoryId,
                    counterpartyId: balanceCancellationTaxCategoryId,
                });
            }
            accountingLedgerMaterials.map(({ amount, taxCategoryId, counterpartyId }) => {
                if (amount === 0) {
                    return;
                }
                const isCreditorCounterparty = amount > 0;
                const ledgerProto = {
                    id: chargeId,
                    invoiceDate: ledgerDate,
                    valueDate: ledgerDate,
                    currency: defaultLocalCurrency,
                    creditAccountID1: isCreditorCounterparty ? counterpartyId : taxCategoryId,
                    localCurrencyCreditAmount1: Math.abs(amount),
                    debitAccountID1: isCreditorCounterparty ? taxCategoryId : counterpartyId,
                    localCurrencyDebitAmount1: Math.abs(amount),
                    description: counterpartyId === balanceCancellationTaxCategoryId
                        ? 'Balance cancellation'
                        : `VAT command ${vatDate}`,
                    isCreditorCounterparty,
                    ownerId: charge.owner_id,
                    chargeId,
                };
                accountingLedgerEntries.push(ledgerProto);
                updateLedgerBalanceByEntry(ledgerProto, ledgerBalance, defaultLocalCurrency);
            });
        }
        const allowedUnbalancedBusinesses = new Set(unbalancedBusinesses.map(({ business_id }) => business_id));
        const ledgerBalanceInfo = await getLedgerBalanceInfo(injector, ledgerBalance, errors, allowedUnbalancedBusinesses);
        const records = [
            ...financialAccountLedgerEntries,
            ...accountingLedgerEntries,
            ...miscExpensesLedgerEntries,
        ];
        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=monthly-vat-ledger-generation.resolver.js.map