import { DIVIDEND_WITHHOLDING_TAX_PERCENTAGE } from '../../../../shared/constants.js';
import { AdminContextProvider } from '../../../admin-context/providers/admin-context.provider.js';
import { DividendsProvider } from '../../../dividends/providers/dividends.provider.js';
import { ExchangeProvider } from '../../../exchange-rates/providers/exchange.provider.js';
import { TransactionsProvider } from '../../../transactions/providers/transactions.provider.js';
import { ledgerEntryFromBalanceCancellation } from '../../helpers/common-charge-ledger.helper.js';
import { splitDividendTransactions } from '../../helpers/dividend-ledger.helper.js';
import { getEntriesFromFeeTransaction } from '../../helpers/fee-transactions.js';
import { storeInitialGeneratedRecords } from '../../helpers/ledgrer-storage.helper.js';
import { generateMiscExpensesLedger } from '../../helpers/misc-expenses-ledger.helper.js';
import { generatePartialLedgerEntry, getFinancialAccountTaxCategoryId, getLedgerBalanceInfo, LedgerError, ledgerProtoToRecordsConverter, updateLedgerBalanceByEntry, validateTransactionRequiredVariables, } from '../../helpers/utils.helper.js';
import { BalanceCancellationProvider } from '../../providers/balance-cancellation.provider.js';
export const generateLedgerRecordsForDividend = async (charge, { insertLedgerRecordsIfNotExists }, { injector }) => {
    const { defaultLocalCurrency, dividends } = await injector
        .get(AdminContextProvider)
        .getVerifiedAdminContext();
    const { dividendWithholdingTaxBusinessId, dividendTaxCategoryId } = dividends;
    if (!dividendWithholdingTaxBusinessId) {
        return {
            __typename: 'CommonError',
            message: `Dividend withholding tax business ID is not defined`,
        };
    }
    if (!dividendTaxCategoryId) {
        return {
            __typename: 'CommonError',
            message: `Dividend tax category ID is not defined`,
        };
    }
    const chargeId = charge.id;
    const errors = new Set();
    try {
        // validate ledger records are balanced
        const ledgerBalance = new Map();
        // generate ledger from transactions
        const coreLedgerEntries = [];
        const withholdingTaxLedgerEntries = [];
        const paymentLedgerEntries = [];
        const miscLedgerEntries = [];
        let mainAccountId = undefined;
        // Get all transactions
        const transactionsPromise = injector
            .get(TransactionsProvider)
            .transactionsByChargeIDLoader.load(chargeId);
        const chargeBallanceCancellationsPromise = injector
            .get(BalanceCancellationProvider)
            .getBalanceCancellationByChargesIdLoader.load(chargeId);
        const [transactions, balanceCancellations] = await Promise.all([
            transactionsPromise,
            chargeBallanceCancellationsPromise,
        ]);
        const { withholdingTaxTransactions, paymentsTransactions, feeTransactions, errors: splitErrors, } = splitDividendTransactions(transactions, dividends);
        splitErrors.map(errors.add);
        // create a ledger record for tax deduction origin
        const withholdingTaxTransactionsPromises = withholdingTaxTransactions.map(async (preValidatedTransaction) => {
            try {
                const transaction = validateTransactionRequiredVariables(preValidatedTransaction);
                if (transaction.currency !== defaultLocalCurrency) {
                    errors.add(`Withholding tax currency supposed to be local, got ${transaction.currency}`);
                }
                // get tax category
                const financialAccountTaxCategoryId = await getFinancialAccountTaxCategoryId(injector, transaction);
                // set main account for dividend
                mainAccountId ||= financialAccountTaxCategoryId;
                if (mainAccountId !== financialAccountTaxCategoryId) {
                    errors.add(`Tax category is not consistent`);
                }
                const partialEntry = generatePartialLedgerEntry(transaction, charge.owner_id, undefined);
                const ledgerEntry = {
                    ...partialEntry,
                    creditAccountID1: partialEntry.isCreditorCounterparty
                        ? transaction.business_id
                        : financialAccountTaxCategoryId,
                    debitAccountID1: partialEntry.isCreditorCounterparty
                        ? financialAccountTaxCategoryId
                        : transaction.business_id,
                };
                withholdingTaxLedgerEntries.push(ledgerEntry);
                updateLedgerBalanceByEntry(ledgerEntry, ledgerBalance, defaultLocalCurrency);
            }
            catch (e) {
                if (e instanceof LedgerError) {
                    errors.add(e.message);
                }
                else {
                    throw e;
                }
            }
        });
        const dividendRecordsPromise = injector
            .get(DividendsProvider)
            .getDividendsByChargeIdLoader.load(chargeId);
        const [dividendRecords] = await Promise.all([
            dividendRecordsPromise,
            ...withholdingTaxTransactionsPromises,
        ]);
        const allowedUnbalancedBusinesses = new Set();
        // create a ledger record for dividend payments
        const coreLedgerEntriesPromises = paymentsTransactions.map(async (preValidatedTransaction) => {
            try {
                const dividendRecord = dividendRecords.find(record => record.transaction_id === preValidatedTransaction.id);
                // run validations
                if (!dividendRecord) {
                    throw new LedgerError(`Transaction reference "${preValidatedTransaction.source_reference}" is missing matching dividend record`);
                }
                const transaction = validateTransactionRequiredVariables(preValidatedTransaction);
                if (Number(transaction.amount) >= 0) {
                    throw new LedgerError(`Dividend transaction amount cannot be positive (reference: ${transaction.source_reference})`);
                }
                if (Number(dividendRecord.amount) <= 0) {
                    throw new LedgerError(`Dividend amount is not positive (ID: ${dividendRecord.id})`);
                }
                if (charge.owner_id !== dividendRecord.owner_id) {
                    throw new LedgerError(`Transaction reference "${transaction.source_reference}" is not matching dividend record ID="${dividendRecord.id}"`);
                }
                const withholdingTaxPercentage = dividendRecord.withholding_tax_percentage_override
                    ? Number(dividendRecord.withholding_tax_percentage_override)
                    : DIVIDEND_WITHHOLDING_TAX_PERCENTAGE;
                const dividendRecordAbsAmount = Math.abs(Number(dividendRecord.amount));
                // generate core ledger entries
                const coreLedgerEntry = {
                    currency: defaultLocalCurrency,
                    ownerId: dividendRecord.owner_id,
                    id: dividendRecord.id,
                    chargeId,
                    invoiceDate: dividendRecord.date,
                    valueDate: dividendRecord.date,
                    isCreditorCounterparty: false,
                    description: 'Main dividend record',
                    debitAccountID1: dividendTaxCategoryId,
                    localCurrencyDebitAmount1: dividendRecordAbsAmount,
                    creditAccountID1: dividendRecord.business_id,
                    localCurrencyCreditAmount1: dividendRecordAbsAmount * (1 - withholdingTaxPercentage),
                    creditAccountID2: dividendWithholdingTaxBusinessId,
                    localCurrencyCreditAmount2: dividendRecordAbsAmount * withholdingTaxPercentage,
                };
                coreLedgerEntries.push(coreLedgerEntry);
                updateLedgerBalanceByEntry(coreLedgerEntry, ledgerBalance, defaultLocalCurrency);
                // preparations for payment ledger entries
                let exchangeRate = undefined;
                if (transaction.currency !== defaultLocalCurrency) {
                    // get exchange rate for currency
                    exchangeRate = await injector
                        .get(ExchangeProvider)
                        .getExchangeRates(transaction.currency, defaultLocalCurrency, dividendRecord.date);
                }
                const partialEntry = generatePartialLedgerEntry(transaction, charge.owner_id, exchangeRate);
                const isForeignCurrency = partialEntry.currency !== defaultLocalCurrency;
                const amountDiff = partialEntry.localCurrencyCreditAmount1 -
                    Number(dividendRecord.amount) * (1 - withholdingTaxPercentage);
                if (Math.abs(amountDiff) > 0.005 && !isForeignCurrency) {
                    throw new LedgerError(`Transaction reference "${transaction.source_reference}" and dividend record ID="${dividendRecord.id}" amounts mismatch`);
                }
                const foreignAccountTaxCategoryId = await getFinancialAccountTaxCategoryId(injector, transaction);
                // create payment ledger entries
                const paymentEntry = {
                    ...partialEntry,
                    isCreditorCounterparty: false,
                    creditAccountID1: foreignAccountTaxCategoryId,
                    debitAccountID1: dividendRecord.business_id,
                };
                paymentLedgerEntries.push(paymentEntry);
                updateLedgerBalanceByEntry(paymentEntry, ledgerBalance, defaultLocalCurrency);
            }
            catch (e) {
                if (e instanceof LedgerError) {
                    errors.add(e.message);
                }
                else {
                    throw e;
                }
            }
        });
        // create a ledger record for fee transactions
        const feeFinancialAccountLedgerEntries = [];
        const feeTransactionsPromises = feeTransactions.map(async (transaction) => {
            await getEntriesFromFeeTransaction(transaction, charge, injector)
                .then(ledgerEntries => {
                feeFinancialAccountLedgerEntries.push(...ledgerEntries);
                ledgerEntries.map(ledgerEntry => {
                    updateLedgerBalanceByEntry(ledgerEntry, ledgerBalance, defaultLocalCurrency);
                });
            })
                .catch(e => {
                if (e instanceof LedgerError) {
                    errors.add(e.message);
                }
                else {
                    throw e;
                }
            });
        });
        const entriesPromises = [...feeTransactionsPromises, ...coreLedgerEntriesPromises];
        // generate ledger from misc expenses
        const expensesLedgerPromise = generateMiscExpensesLedger(charge, injector)
            .then(entries => {
            entries.map(entry => {
                entry.ownerId = charge.owner_id;
                miscLedgerEntries.push(entry);
                updateLedgerBalanceByEntry(entry, ledgerBalance, defaultLocalCurrency);
            });
        })
            .catch(e => {
            if (e instanceof LedgerError) {
                errors.add(e.message);
            }
            else {
                errors.add(`Failed to generate misc expenses ledger: ${e.message}`);
            }
        });
        entriesPromises.push(expensesLedgerPromise);
        await Promise.all(entriesPromises);
        // create foreign currency balance ledger
        for (const entry of paymentLedgerEntries) {
            if (entry.currency !== defaultLocalCurrency) {
                const currencyBalanceEntry1 = {
                    id: entry.id + `|${entry.currency}-balance`, // NOTE: this field is dummy
                    creditAccountID1: entry.debitAccountID1,
                    localCurrencyCreditAmount1: entry.localCurrencyCreditAmount1,
                    localCurrencyDebitAmount1: entry.localCurrencyCreditAmount1,
                    creditAmount1: entry.creditAmount1,
                    description: `Foreign currency balance (${entry.currency})`,
                    isCreditorCounterparty: true,
                    invoiceDate: entry.invoiceDate,
                    valueDate: entry.valueDate,
                    currency: entry.currency,
                    ownerId: entry.ownerId,
                    chargeId: charge.id,
                };
                miscLedgerEntries.push(currencyBalanceEntry1);
                updateLedgerBalanceByEntry(currencyBalanceEntry1, ledgerBalance, defaultLocalCurrency);
                const currencyBalanceEntry2 = {
                    id: entry.id + `|${defaultLocalCurrency}-balance`, // NOTE: this field is dummy
                    debitAccountID1: entry.debitAccountID1,
                    localCurrencyCreditAmount1: entry.localCurrencyCreditAmount1,
                    localCurrencyDebitAmount1: entry.localCurrencyCreditAmount1,
                    description: `Foreign currency balance (${defaultLocalCurrency})`,
                    isCreditorCounterparty: false,
                    invoiceDate: entry.invoiceDate,
                    valueDate: entry.valueDate,
                    currency: defaultLocalCurrency,
                    ownerId: entry.ownerId,
                    chargeId: charge.id,
                };
                miscLedgerEntries.push(currencyBalanceEntry2);
                updateLedgerBalanceByEntry(currencyBalanceEntry2, ledgerBalance, defaultLocalCurrency);
            }
        }
        // generate ledger from balance cancellation
        for (const balanceCancellation of balanceCancellations) {
            try {
                const ledgerEntry = await ledgerEntryFromBalanceCancellation(balanceCancellation, ledgerBalance, coreLedgerEntries, chargeId, charge.owner_id, injector);
                miscLedgerEntries.push(ledgerEntry);
                updateLedgerBalanceByEntry(ledgerEntry, ledgerBalance, defaultLocalCurrency);
            }
            catch (e) {
                if (e instanceof LedgerError) {
                    errors.add(e.message);
                    continue;
                }
                else {
                    throw e;
                }
            }
        }
        const ledgerBalanceInfo = await getLedgerBalanceInfo(injector, ledgerBalance, errors, allowedUnbalancedBusinesses);
        const records = [
            ...withholdingTaxLedgerEntries,
            ...coreLedgerEntries,
            ...feeFinancialAccountLedgerEntries,
            ...paymentLedgerEntries,
            ...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=dividend-ledger-generation.resolver.js.map