import { GraphQLError } from 'graphql';
import { EMPTY_UUID } from '../../../shared/constants.js';
import { formatCurrency } from '../../../shared/helpers/index.js';
import { AdminContextProvider } from '../../admin-context/providers/admin-context.provider.js';
import { ScopeProvider } from '../../auth/providers/scope.provider.js';
import { deleteCharges } from '../../charges/helpers/delete-charges.helper.js';
import { ChargesProvider } from '../../charges/providers/charges.provider.js';
import { DocumentsProvider } from '../../documents/providers/documents.provider.js';
import { ExchangeProvider } from '../../exchange-rates/providers/exchange.provider.js';
import { effectiveDateSupplement } from '../helpers/effective-date.helper.js';
import { TransactionsProvider } from '../providers/transactions.provider.js';
import { commonChargeFields, commonTransactionFields } from './common.js';
export const transactionsResolvers = {
    Query: {
        transactionsByIDs: async (_, { transactionIDs }, { injector }) => {
            if (transactionIDs.length === 0) {
                return [];
            }
            const transactions = await injector
                .get(TransactionsProvider)
                .transactionByIdLoader.loadMany(transactionIDs);
            if (!transactions) {
                if (transactionIDs.length === 1) {
                    throw new GraphQLError(`Transaction ID="${transactionIDs[0]}" not found`);
                }
                else {
                    throw new GraphQLError(`Couldn't find any transactions`);
                }
            }
            transactionIDs.map(id => {
                const transaction = transactions.find(transaction => !(transaction instanceof Error) && transaction.id === id);
                if (!transaction) {
                    throw new GraphQLError(`Transaction ID="${id}" not found`);
                }
            });
            return transactionIDs;
        },
        transactionsByFinancialEntity: async (_, { financialEntityID }, { injector }) => {
            const ownerIDs = await injector.get(ScopeProvider).getReadScope();
            const transactions = await injector
                .get(TransactionsProvider)
                .getTransactionsByFilters({
                businessIDs: [financialEntityID],
                ownerIDs,
            })
                .then(res => res.map(t => t.id));
            return transactions;
        },
    },
    Mutation: {
        updateTransaction: async (_, { transactionId, fields }, { injector }) => {
            let postUpdateActions = async () => void 0;
            try {
                // let charge: ChargesTypes.IGetChargesByIdsResult | undefined;
                const existingChargePromise = async () => {
                    const charge = await injector
                        .get(ChargesProvider)
                        .getChargeByIdLoader.load(fields.chargeId ?? '');
                    if (!charge) {
                        throw new GraphQLError(`Charge ID="${chargeId}" not valid`);
                    }
                };
                const emptyChargePromise = async () => {
                    // case unlinked from charge
                    const transaction = await injector
                        .get(TransactionsProvider)
                        .transactionByIdLoader.load(transactionId);
                    if (!transaction) {
                        throw new GraphQLError(`Transaction ID="${transactionId}" not valid`);
                    }
                    if (transaction.charge_id) {
                        const [charge, transactions, documents] = await Promise.all([
                            injector.get(ChargesProvider).getChargeByIdLoader.load(transaction.charge_id),
                            injector
                                .get(TransactionsProvider)
                                .transactionsByChargeIDLoader.load(transaction.charge_id),
                            injector
                                .get(DocumentsProvider)
                                .getDocumentsByChargeIdLoader.load(transaction.charge_id),
                        ]);
                        if (!charge) {
                            throw new GraphQLError(`Former transaction's charge ID ("${chargeId}") not valid`);
                        }
                        // generate new charge
                        const newCharge = await injector.get(ChargesProvider).generateCharge({
                            ownerId: charge.owner_id,
                            userDescription: 'Transaction unlinked from charge',
                        });
                        if (!newCharge) {
                            throw new GraphQLError(`Failed to generate new charge for transaction ID="${transactionId}"`);
                        }
                        chargeId = newCharge.id;
                        if (documents.length === 0 && transactions.length === 1) {
                            postUpdateActions = async () => deleteCharges([charge.id], injector)
                                .catch(e => {
                                if (e instanceof GraphQLError) {
                                    throw e;
                                }
                                console.error(e);
                                throw new GraphQLError(`Failed to delete the empty former charge ID="${charge.id}"`);
                            })
                                .then(postUpdateActions);
                        }
                    }
                };
                let chargeId = fields.chargeId;
                const chargePromise = chargeId && chargeId !== EMPTY_UUID
                    ? existingChargePromise()
                    : chargeId === EMPTY_UUID
                        ? emptyChargePromise()
                        : Promise.resolve();
                await chargePromise;
                const adjustedFields = {
                    transactionIds: [transactionId],
                    businessId: fields.counterpartyId,
                    chargeId: chargeId ?? null,
                    debitDate: fields.effectiveDate ?? null,
                    isFee: fields.isFee,
                };
                const res = await injector
                    .get(TransactionsProvider)
                    .updateTransactions({ ...adjustedFields });
                if (!res[0]?.id) {
                    throw new GraphQLError('Transaction update failed');
                }
                await postUpdateActions();
                return res[0].id;
            }
            catch (e) {
                if (e instanceof GraphQLError) {
                    throw e;
                }
                console.error(e);
                return {
                    __typename: 'CommonError',
                    message: e?.message ?? 'Unknown error',
                };
            }
        },
        updateTransactions: async (_, { transactionIds, fields }, { injector }) => {
            let postUpdateActions = async () => void 0;
            try {
                const { ownerId } = await injector.get(AdminContextProvider).getVerifiedAdminContext();
                const existingChargePromise = async () => {
                    // verify charge exists
                    const charge = await injector
                        .get(ChargesProvider)
                        .getChargeByIdLoader.load(fields.chargeId ?? '');
                    if (!charge) {
                        throw new GraphQLError(`Charge ID="${chargeId}" not valid`);
                    }
                };
                const emptyChargePromise = async () => {
                    // case unlinked from charge
                    const transactions = await injector
                        .get(TransactionsProvider)
                        .transactionByIdLoader.loadMany(transactionIds)
                        .then(res => res.filter((t, i) => {
                        if (t instanceof Error) {
                            throw new GraphQLError(`Transaction ID="${transactionIds[i]}" not valid`);
                        }
                        return true;
                    }));
                    const transactionsWithChargeId = transactions.filter(t => t.charge_id);
                    if (transactionsWithChargeId.length > 0) {
                        const chargesToDelete = new Set();
                        await Promise.all(transactionsWithChargeId.map(async (transaction) => {
                            if (!transaction.charge_id) {
                                return;
                            }
                            const [charge, transactions, documents] = await Promise.all([
                                injector.get(ChargesProvider).getChargeByIdLoader.load(transaction.charge_id),
                                injector
                                    .get(TransactionsProvider)
                                    .transactionsByChargeIDLoader.load(transaction.charge_id),
                                injector
                                    .get(DocumentsProvider)
                                    .getDocumentsByChargeIdLoader.load(transaction.charge_id),
                            ]);
                            if (!charge) {
                                throw new GraphQLError(`Former transaction's charge ID ("${transaction.charge_id}") not valid`);
                            }
                            if (documents.length === 0 && transactions.length === 1) {
                                chargesToDelete.add(charge.id);
                            }
                        }));
                        if (chargesToDelete.size > 0) {
                            const oldPostUpdateActions = postUpdateActions;
                            postUpdateActions = async () => {
                                await oldPostUpdateActions();
                                await deleteCharges(Array.from(chargesToDelete), injector).catch(e => {
                                    if (e instanceof GraphQLError) {
                                        throw e;
                                    }
                                    console.error(e);
                                    throw new GraphQLError(`Failed to delete empty former charges: ${Array.from(chargesToDelete).join(', ')}`);
                                });
                            };
                        }
                        // generate new charge
                        const newCharge = await injector.get(ChargesProvider).generateCharge({
                            ownerId,
                            userDescription: 'Transactions unlinked from charge',
                        });
                        if (!newCharge) {
                            throw new GraphQLError(`Failed to generate new charge for transactions update`);
                        }
                        chargeId = newCharge.id;
                    }
                };
                let chargeId = fields.chargeId;
                const chargePromise = chargeId && chargeId !== EMPTY_UUID
                    ? existingChargePromise()
                    : chargeId === EMPTY_UUID
                        ? emptyChargePromise()
                        : Promise.resolve();
                await chargePromise;
                const adjustedFields = {
                    transactionIds,
                    businessId: fields.counterpartyId,
                    chargeId: chargeId ?? null,
                    debitDate: fields.effectiveDate ?? null,
                    isFee: fields.isFee,
                };
                const res = await injector.get(TransactionsProvider).updateTransactions(adjustedFields);
                if (!res[0]?.id) {
                    throw new GraphQLError('Transactions update failed');
                }
                await postUpdateActions();
                return { transactions: res.map(transaction => transaction.id) };
            }
            catch (e) {
                if (e instanceof GraphQLError) {
                    throw e;
                }
                console.error(e);
                return {
                    __typename: 'CommonError',
                    message: e?.message ?? 'Unknown error',
                };
            }
        },
    },
    UpdateTransactionResult: {
        __resolveType: async (raw, { injector }, _info) => {
            if (typeof raw === 'string') {
                const transaction = await injector
                    .get(TransactionsProvider)
                    .transactionByIdLoader.load(raw);
                const charge = await injector
                    .get(ChargesProvider)
                    .getChargeByIdLoader.load(transaction.charge_id);
                if (!charge) {
                    throw new GraphQLError(`Charge with ID "${transaction.charge_id}" not found`);
                }
                return charge.type === 'CONVERSION' ? 'ConversionTransaction' : 'CommonTransaction';
            }
            if (typeof raw === 'object' && '__typename' in raw && raw.__typename === 'CommonError') {
                return 'CommonError';
            }
            throw new GraphQLError('Unexpected type for UpdateTransactionResult');
        },
    },
    UpdateTransactionsResult: {
        __resolveType: (obj, _context, _info) => {
            if (('__typename' in obj && obj.__typename === 'CommonError') || 'message' in obj)
                return 'CommonError';
            return 'UpdatedTransactionsSuccessfulResult';
        },
    },
    BankDepositCharge: commonChargeFields,
    BusinessTripCharge: commonChargeFields,
    CommonCharge: commonChargeFields,
    ConversionCharge: commonChargeFields,
    CreditcardBankCharge: commonChargeFields,
    DividendCharge: commonChargeFields,
    FinancialCharge: commonChargeFields,
    ForeignSecuritiesCharge: commonChargeFields,
    InternalTransferCharge: commonChargeFields,
    MonthlyVatCharge: commonChargeFields,
    SalaryCharge: commonChargeFields,
    ConversionTransaction: {
        __isTypeOf: async (transactionId, { injector }) => {
            const transaction = await injector
                .get(TransactionsProvider)
                .transactionByIdLoader.load(transactionId);
            const charge = await injector
                .get(ChargesProvider)
                .getChargeByIdLoader.load(transaction.charge_id);
            if (!charge) {
                throw new GraphQLError(`Charge ID="${transaction.charge_id}" not found`);
            }
            return charge.type === 'CONVERSION';
        },
        ...commonTransactionFields,
        effectiveDate: async (transactionId, __dirname, { injector }) => {
            const transaction = await injector
                .get(TransactionsProvider)
                .transactionByIdLoader.load(transactionId);
            const date = effectiveDateSupplement(transaction);
            if (!date) {
                console.error(`Conversion transaction ID="${transactionId}" has no effective date`);
                throw new GraphQLError('Conversion transaction must have effective date');
            }
            return date;
        },
        type: async (transactionId, __dirname, { injector }) => {
            const transaction = await injector
                .get(TransactionsProvider)
                .transactionByIdLoader.load(transactionId);
            return Number(transaction.amount) > 0 ? 'QUOTE' : 'BASE';
        },
        bankRate: async (transactionId, __dirname, { injector }) => {
            const transaction = await injector
                .get(TransactionsProvider)
                .transactionByIdLoader.load(transactionId);
            return transaction.currency_rate;
        },
        officialRateToLocal: async (transactionId, _, { injector }) => {
            const { defaultLocalCurrency } = await injector
                .get(AdminContextProvider)
                .getVerifiedAdminContext();
            const transaction = await injector
                .get(TransactionsProvider)
                .transactionByIdLoader.load(transactionId);
            return injector
                .get(ExchangeProvider)
                .getExchangeRates(defaultLocalCurrency, formatCurrency(transaction.currency), transaction.event_date);
        },
    },
    CommonTransaction: {
        __isTypeOf: async (transactionId, { injector }) => {
            const transaction = await injector
                .get(TransactionsProvider)
                .transactionByIdLoader.load(transactionId);
            const charge = await injector
                .get(ChargesProvider)
                .getChargeByIdLoader.load(transaction.charge_id);
            if (!charge) {
                throw new GraphQLError(`Charge ID="${transaction.charge_id}" not found`);
            }
            if (charge instanceof GraphQLError) {
                console.error(charge);
                throw new GraphQLError(`Charge ID="${transaction.charge_id}" not found`);
            }
            return charge.type !== 'CONVERSION';
        },
        ...commonTransactionFields,
    },
};
//# sourceMappingURL=transactions.resolver.js.map