import { GraphQLError } from 'graphql';
import { AdminContextProvider } from '../../admin-context/providers/admin-context.provider.js';
import { ChargesProvider } from '../../charges/providers/charges.provider.js';
import { isSupplementalFeeTransaction } from '../../ledger/helpers/fee-transactions.js';
import { LedgerError } from '../../ledger/helpers/utils.helper.js';
import { generateLedgerRecordsForBusinessTrip } from '../../ledger/resolvers/ledger-generation/business-trip-ledger-generation.resolver.js';
import { ChargeTagsProvider } from '../../tags/providers/charge-tags.provider.js';
import { BusinessTripEmployeePaymentsProvider } from '../providers/business-trips-employee-payments.provider.js';
import { BusinessTripAccommodationsExpensesProvider } from '../providers/business-trips-expenses-accommodations.provider.js';
import { BusinessTripCarRentalExpensesProvider } from '../providers/business-trips-expenses-car-rental.provider.js';
import { BusinessTripFlightsExpensesProvider } from '../providers/business-trips-expenses-flights.provider.js';
import { BusinessTripOtherExpensesProvider } from '../providers/business-trips-expenses-other.provider.js';
import { BusinessTripExpensesTransactionsMatchProvider } from '../providers/business-trips-expenses-transactions-match.provider.js';
import { BusinessTripTravelAndSubsistenceExpensesProvider } from '../providers/business-trips-expenses-travel-and-subsistence.provider.js';
import { BusinessTripExpensesProvider } from '../providers/business-trips-expenses.provider.js';
import { BusinessTripsProvider } from '../providers/business-trips.provider.js';
function validateTransactionAgainstBusinessTripsExpenses(transaction, transactionMatchingExpenses, miscExpenses, financialAccounts) {
    if (!transactionMatchingExpenses?.length && !miscExpenses?.length) {
        throw new LedgerError(`Transaction reference "${transaction.source_reference}" is not part of a business trip`);
    }
    const totalAmount = transactionMatchingExpenses.reduce((acc, expense) => acc + Number(expense.amount), 0);
    const direction = isSupplementalFeeTransaction(transaction, financialAccounts) ? -1 : 1;
    const miscExpensesAmount = miscExpenses.reduce((acc, expense) => Number(expense.amount) * direction + acc, 0);
    if (Math.abs(Number(transaction.amount) - miscExpensesAmount - totalAmount) > 0.005) {
        throw new LedgerError(`Transaction reference "${transaction.source_reference}" amount does not match the business trip expenses total amount`);
    }
    return true;
}
export const validateTransactionAgainstBusinessTrips = async (injector, transaction) => {
    const transactionMatchingExpenses = await injector
        .get(BusinessTripExpensesTransactionsMatchProvider)
        .getBusinessTripsExpenseMatchesByTransactionIdLoader.load(transaction.id);
    const { financialAccounts } = await injector.get(AdminContextProvider).getVerifiedAdminContext();
    return validateTransactionAgainstBusinessTripsExpenses(transaction, transactionMatchingExpenses, [], financialAccounts);
};
export const getTransactionMatchedAmount = async (injector, transaction) => {
    const transactionMatchingExpensesPromise = injector
        .get(BusinessTripExpensesTransactionsMatchProvider)
        .getBusinessTripsExpenseMatchesByTransactionIdLoader.load(transaction.id);
    const [transactionMatchingExpenses] = await Promise.all([transactionMatchingExpensesPromise]);
    const expensesSum = transactionMatchingExpenses.reduce((acc, expense) => (expense.amount ? acc + Number(expense.amount) : acc), 0);
    const { financialAccounts } = await injector.get(AdminContextProvider).getVerifiedAdminContext();
    try {
        validateTransactionAgainstBusinessTripsExpenses(transaction, transactionMatchingExpenses, [], financialAccounts);
    }
    catch (e) {
        const errors = [];
        if (e instanceof LedgerError) {
            errors.push(e.message);
        }
        return {
            isFullyMatched: false,
            amount: expensesSum,
            errors,
        };
    }
    const isFullyMatched = Math.abs(Number(transaction.amount) - expensesSum) < 0.005;
    return {
        isFullyMatched,
        amount: Number(transaction.amount),
    };
};
export async function coreExpenseUpdate(injector, fields, categoryToValidate) {
    const { id, businessTripId, date, valueDate, amount, currency, employeeBusinessId, chargeId } = fields;
    const currentExpense = await injector
        .get(BusinessTripExpensesProvider)
        .getBusinessTripsExpensesByIdLoader.load(id);
    if (!currentExpense) {
        throw new GraphQLError(`Business trip expense with id ${id} not found`);
    }
    if (categoryToValidate && currentExpense.category !== categoryToValidate) {
        throw new GraphQLError(`Business trip expense with id ${id} is not a ${categoryToValidate} expense`);
    }
    const updateCoreExpensePromise = async () => {
        return businessTripId
            ? injector.get(BusinessTripExpensesProvider).updateBusinessTripExpense({
                businessTripExpenseId: id,
                businessTripId,
                ...(currentExpense.payed_by_employee
                    ? {
                        date,
                        valueDate,
                        currency,
                        employeeBusinessId,
                    }
                    : {}),
            })
            : Promise.resolve();
    };
    const updateEmployeePaymentPromise = async () => {
        const hasCommonFieldsToUpdate = date || valueDate || amount || currency || employeeBusinessId || chargeId;
        return hasCommonFieldsToUpdate
            ? injector.get(BusinessTripEmployeePaymentsProvider).updateBusinessTripEmployeePayment({
                businessTripExpenseId: id,
                chargeId,
                date,
                valueDate,
                currency,
                employeeBusinessId,
            })
            : Promise.resolve();
    };
    const [updatedTripExpense] = await Promise.all([
        updateCoreExpensePromise(),
        updateEmployeePaymentPromise(),
    ]);
    return updatedTripExpense;
}
export async function generateChargeForEmployeePayment(injector, businessTripId, description) {
    const { ownerId, businessTrips: { businessTripTaxCategoryId }, } = await injector.get(AdminContextProvider).getVerifiedAdminContext();
    if (!businessTripTaxCategoryId) {
        throw new GraphQLError('Business trip tax category not set');
    }
    try {
        const { id: chargeId } = await injector.get(ChargesProvider).generateCharge({
            ownerId,
            taxCategoryId: businessTripTaxCategoryId,
            userDescription: description || 'Employee payment charge',
            type: 'BUSINESS_TRIP',
        });
        if (!chargeId) {
            throw new GraphQLError('Failed to generate initial charge');
        }
        await injector.get(BusinessTripsProvider).updateChargeBusinessTrip(chargeId, businessTripId);
        return chargeId;
    }
    catch (e) {
        console.error('Failed to generate charge for employee payment', e);
        throw new GraphQLError('Failed to generate charge for employee payment');
    }
}
export const updateExistingTripExpense = async (injector, businessTripExpenseId, transactionId, category, amount) => {
    const updateTransactionMatchPromise = injector
        .get(BusinessTripExpensesTransactionsMatchProvider)
        .insertBusinessTripExpenseMatch({
        businessTripExpenseId,
        transactionId,
        amount,
    });
    const insertToCategoryPromise = async () => {
        switch (category) {
            case 'FLIGHT':
                return injector.get(BusinessTripFlightsExpensesProvider).insertBusinessTripFlightsExpense({
                    id: businessTripExpenseId,
                });
            case 'ACCOMMODATION':
                return injector
                    .get(BusinessTripAccommodationsExpensesProvider)
                    .insertBusinessTripAccommodationsExpense({
                    id: businessTripExpenseId,
                });
            case 'TRAVEL_AND_SUBSISTENCE':
                return injector
                    .get(BusinessTripTravelAndSubsistenceExpensesProvider)
                    .insertBusinessTripTravelAndSubsistenceExpense({ id: businessTripExpenseId });
            case 'OTHER':
                return injector.get(BusinessTripOtherExpensesProvider).insertBusinessTripOtherExpense({
                    id: businessTripExpenseId,
                });
            case 'CAR_RENTAL':
                return injector
                    .get(BusinessTripCarRentalExpensesProvider)
                    .insertBusinessTripCarRentalExpense({
                    id: businessTripExpenseId,
                    days: 0,
                    isFuelExpense: false,
                });
            default:
                throw new GraphQLError(`Invalid category ${category}`);
        }
    };
    await Promise.all([updateTransactionMatchPromise, insertToCategoryPromise()]);
};
export async function createTravelAndSubsistenceExpense(context, fields) {
    const { injector } = context;
    const adminContext = await injector.get(AdminContextProvider).getVerifiedAdminContext();
    const { businessTripTagId } = adminContext.businessTrips;
    try {
        const coreExpensePromise = injector
            .get(BusinessTripExpensesProvider)
            .insertBusinessTripExpense({
            businessTripId: fields.businessTripId,
            category: 'TRAVEL_AND_SUBSISTENCE',
        })
            .then(res => res[0]);
        const chargeGenerationPromise = generateChargeForEmployeePayment(injector, fields.businessTripId, fields.expenseType ?? undefined);
        const [coreExpense, chargeId] = await Promise.all([
            coreExpensePromise,
            chargeGenerationPromise,
        ]);
        const [charge] = await Promise.all([
            injector.get(ChargesProvider).getChargeByIdLoader.load(chargeId),
            injector
                .get(BusinessTripTravelAndSubsistenceExpensesProvider)
                .insertBusinessTripTravelAndSubsistenceExpense({
                id: coreExpense.id,
                expenseType: fields.expenseType,
            }),
            injector.get(BusinessTripEmployeePaymentsProvider).insertBusinessTripEmployeePayment({
                businessTripExpenseId: coreExpense.id,
                chargeId,
                date: fields.date,
                valueDate: fields.valueDate,
                amount: fields.amount,
                currency: fields.currency,
                employeeBusinessId: fields.employeeBusinessId,
            }),
            businessTripTagId
                ? injector.get(ChargeTagsProvider).insertChargeTag({ chargeId, tagId: businessTripTagId })
                : Promise.resolve(),
        ]);
        if (!charge) {
            throw new GraphQLError('Failed to generate charge for employee payment');
        }
        // generate ledger records
        await generateLedgerRecordsForBusinessTrip(charge, { insertLedgerRecordsIfNotExists: true }, context, {});
        return coreExpense.id;
    }
    catch (e) {
        console.error(`Error adding new business trip travel & subsistence expense`, e);
        throw new GraphQLError('Error adding new business trip travel & subsistence expense');
    }
}
//# sourceMappingURL=business-trips-expenses.helper.js.map