import { differenceInDays } from 'date-fns';
import { GraphQLError } from 'graphql';
import { Currency } from '../../../shared/enums.js';
import { dateToTimelessDateString } 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 { EmployeesProvider } from '../../salaries/providers/employees.provider.js';
import { getAttendeeTravelAndSubsistenceMaxTax, getExpenseCoreData, } from '../helpers/business-trip-report.helper.js';
import { createTravelAndSubsistenceExpense } from '../helpers/business-trips-expenses.helper.js';
import { BusinessTripAttendeesProvider } from '../providers/business-trips-attendees.provider.js';
import { BusinessTripAccommodationsExpensesProvider } from '../providers/business-trips-expenses-accommodations.provider.js';
import { BusinessTripTravelAndSubsistenceExpensesProvider } from '../providers/business-trips-expenses-travel-and-subsistence.provider.js';
import { BusinessTripTaxVariablesProvider } from '../providers/business-trips-tax-variables.provider.js';
import { BusinessTripsProvider } from '../providers/business-trips.provider.js';
import { businessTripSummary } from './business-trip-summary.resolver.js';
export const creditShareholdersBusinessTripTravelAndSubsistence = async (_, { businessTripId }, context) => {
    const { injector } = context;
    async function summaryDataPromise() {
        const businessTrip = await injector
            .get(BusinessTripsProvider)
            .getBusinessTripsByIdLoader.load(businessTripId);
        if (!businessTrip) {
            throw new GraphQLError(`Business trip with id ${businessTripId} not found`);
        }
        const attendees = await injector
            .get(BusinessTripAttendeesProvider)
            .getBusinessTripsAttendeesByBusinessTripIdLoader.load(businessTripId);
        if (!attendees?.length) {
            throw new GraphQLError(`Business trip with id ${businessTripId} is missing attendees`);
        }
        let toDate;
        attendees.map(({ departure }) => {
            if (departure && (!toDate || departure > toDate)) {
                toDate = departure;
            }
        });
        if (!toDate) {
            throw new GraphQLError(`Business trip with id ${businessTripId} is missing end date`);
        }
        const { ledgerLock } = await injector.get(AdminContextProvider).getVerifiedAdminContext();
        if (ledgerLock && dateToTimelessDateString(toDate) < ledgerLock) {
            throw new GraphQLError(`Cannot credit shareholders for business trip ${businessTrip.name}, because ledger is locked`);
        }
        const summaryData = await businessTripSummary(injector, businessTrip);
        return { businessTrip: { ...businessTrip, toDate }, summaryData };
    }
    const accommodationExpensesPromise = injector
        .get(BusinessTripAccommodationsExpensesProvider)
        .getBusinessTripsAccommodationsExpensesByBusinessTripIdLoader.load(businessTripId);
    const attendeePayedTnSExpensesPromise = injector
        .get(BusinessTripTravelAndSubsistenceExpensesProvider)
        .getBusinessTripsTravelAndSubsistenceExpensesByBusinessTripIdLoader.load(businessTripId)
        .then(expenses => expenses.filter(expense => expense.payed_by_employee));
    const [{ businessTrip, summaryData }, accommodationExpenses, attendeePayedTnSExpenses, { shareholdersMap, attendeesMap },] = await Promise.all([
        summaryDataPromise(),
        accommodationExpensesPromise,
        attendeePayedTnSExpensesPromise,
        shareholdersMapPromise(injector, businessTripId),
    ]);
    if (!businessTrip.toDate) {
        throw new GraphQLError(`Business trip with id ${businessTripId} is missing end date`);
    }
    if (shareholdersMap.size === 0) {
        return []; // No shareholders to distribute the credit to
    }
    async function availableAmountToDistributePromise() {
        const travelAndSubsistenceRow = await summaryData.rows.find(row => 'type' in row && row.type === 'TRAVEL_AND_SUBSISTENCE');
        if (!travelAndSubsistenceRow) {
            throw new GraphQLError(`Travel and subsistence row not found`);
        }
        const [{ raw: taxableAmount }, { raw: maxTaxableAmount }] = await Promise.all([
            travelAndSubsistenceRow.maxTaxableForeignCurrency,
            travelAndSubsistenceRow.taxableForeignCurrency,
        ]);
        if (Number.isNaN(taxableAmount) || Number.isNaN(maxTaxableAmount)) {
            throw new GraphQLError(`Taxable amounts are missing`);
        }
        const availableAmountToDistribute = Math.max(maxTaxableAmount - taxableAmount, 0);
        return availableAmountToDistribute;
    }
    const [availableAmountToDistribute, shareholdersPotentialAmountToDistribute] = await Promise.all([
        availableAmountToDistributePromise(),
        shareholdersPotentialAmountToDistributePromise(injector, accommodationExpenses, shareholdersMap, attendeesMap, attendeePayedTnSExpenses, businessTrip),
    ]);
    if (availableAmountToDistribute <= 0) {
        return []; // No available amount to distribute
    }
    const commonFields = {
        businessTripId,
        currency: Currency.Usd,
        date: dateToTimelessDateString(businessTrip.toDate),
        valueDate: dateToTimelessDateString(businessTrip.toDate),
    };
    const totalPotentialAmountToDistribute = Object.values(shareholdersPotentialAmountToDistribute).reduce((acc, amount) => acc + amount, 0);
    const creditRatio = totalPotentialAmountToDistribute <= availableAmountToDistribute
        ? 1
        : availableAmountToDistribute / totalPotentialAmountToDistribute;
    return Object.entries(shareholdersPotentialAmountToDistribute).map(([id, amount]) => {
        const shareholder = shareholdersMap.get(id);
        return createTravelAndSubsistenceExpense(context, {
            ...commonFields,
            amount: amount * creditRatio,
            expenseType: `${shareholder.first_name}'s travel and subsistence expenses, no invoice`,
            employeeBusinessId: id,
        });
    });
};
async function shareholdersMapPromise(injector, businessTripId) {
    const shareholdersMap = new Map();
    const attendeesMap = new Map();
    const attendees = await injector
        .get(BusinessTripAttendeesProvider)
        .getBusinessTripsAttendeesByBusinessTripIdLoader.load(businessTripId);
    const shareholders = await injector
        .get(EmployeesProvider)
        .getEmployeesByIdLoader.loadMany(attendees.map(a => a.id))
        .then(res => res.filter(employee => {
        if (!employee) {
            return false;
        }
        if (employee instanceof Error) {
            throw employee;
        }
        if (employee.role_status !== 'Controlling shareholder') {
            return false;
        }
        return true;
    }));
    shareholders.map(shareholder => shareholdersMap.set(shareholder.business_id, shareholder));
    attendees.map(attendee => {
        if (shareholdersMap.has(attendee.id)) {
            attendeesMap.set(attendee.id, attendee);
        }
    });
    return { shareholdersMap, attendeesMap };
}
async function shareholdersPotentialAmountToDistributePromise(injector, accommodationExpenses, shareholdersMap, attendeesMap, attendeePayedTnSExpenses, businessTrip) {
    const shareholdersPotentialAmountToDistribute = {};
    const shareholdersAccommodatedNightsMap = new Map();
    const { defaultCryptoConversionFiatCurrency } = await injector
        .get(AdminContextProvider)
        .getVerifiedAdminContext();
    // collect shareholders accommodated data
    accommodationExpenses.map(expense => {
        expense.attendees_stay.filter(Boolean).map(({ attendeeId, nightsCount }) => {
            if (shareholdersMap.has(attendeeId)) {
                if (shareholdersAccommodatedNightsMap.has(attendeeId)) {
                    shareholdersAccommodatedNightsMap.set(attendeeId, shareholdersAccommodatedNightsMap.get(attendeeId) + nightsCount);
                }
                else {
                    shareholdersAccommodatedNightsMap.set(attendeeId, nightsCount);
                }
            }
        });
    });
    await Promise.all(Array.from(shareholdersMap.entries()).map(async ([id, shareholder]) => {
        const attendee = attendeesMap.get(id);
        if (!attendee) {
            throw new Error(`Attendee with id ${id} not found, but is a shareholder`);
        }
        if (!attendee.arrival || !attendee.departure) {
            console.log(`Shareholder ${shareholder.first_name} is missing arrival or departure date`);
            return;
        }
        const taxVariables = await injector
            .get(BusinessTripTaxVariablesProvider)
            .getTaxVariablesByDateLoader.load(businessTrip.toDate);
        if (!taxVariables) {
            throw new GraphQLError(`Tax variables are not set for date ${businessTrip.toDate}`);
        }
        const totalDays = differenceInDays(attendee.departure, attendee.arrival) + 1;
        const accommodatedNights = shareholdersAccommodatedNightsMap.get(id) ?? 0;
        const unAccommodatedDays = accommodatedNights
            ? totalDays - accommodatedNights - 1
            : totalDays;
        const maxTaxableAmount = getAttendeeTravelAndSubsistenceMaxTax(totalDays, unAccommodatedDays, businessTrip.destination, taxVariables) * -1;
        const shareholderPayedTnSExpenses = attendeePayedTnSExpenses.filter(expense => expense.employee_business_id === id);
        let payedAmount = 0;
        if (shareholderPayedTnSExpenses.length) {
            await Promise.all(shareholderPayedTnSExpenses.map(async (expense) => {
                const { amount, currency, date } = getExpenseCoreData(expense);
                if (expense.currency === defaultCryptoConversionFiatCurrency) {
                    payedAmount += amount;
                }
                else {
                    const rate = await injector
                        .get(ExchangeProvider)
                        .getExchangeRates(currency, defaultCryptoConversionFiatCurrency, new Date(date));
                    payedAmount += amount * rate;
                }
            }));
        }
        if (payedAmount > maxTaxableAmount) {
            return; // Shareholder already got payed more than the maximum taxable amount
        }
        shareholdersPotentialAmountToDistribute[id] = maxTaxableAmount - payedAmount;
    }));
    return shareholdersPotentialAmountToDistribute;
}
//# sourceMappingURL=credit-shareholders-business-trip-travel-and-subsistence.resolver.js.map