import { addMonths, endOfMonth, format, startOfMonth, subMonths } from 'date-fns';
import { GraphQLError } from 'graphql';
import { DocumentType } from '../../../shared/enums.js';
import { dateToTimelessDateString } from '../../../shared/helpers/index.js';
import { GreenInvoiceClientProvider } from '../../app-providers/green-invoice-client.js';
import { ChargesProvider } from '../../charges/providers/charges.provider.js';
import { getProductName, getSubscriptionPlanName, normalizeProduct, normalizeSubscriptionPlan, } from '../../contracts/helpers/contracts.helper.js';
import { FinancialAccountsProvider } from '../../financial-accounts/providers/financial-accounts.provider.js';
import { FinancialBankAccountsProvider } from '../../financial-accounts/providers/financial-bank-accounts.provider.js';
import { validateClientIntegrations } from '../../financial-entities/helpers/clients.helper.js';
import { BusinessesProvider } from '../../financial-entities/providers/businesses.provider.js';
import { ClientsProvider } from '../../financial-entities/providers/clients.provider.js';
import { convertDocumentInputIntoGreenInvoiceInput, getTypeFromGreenInvoiceDocument, getVatTypeFromGreenInvoiceDocument, insertNewDocumentFromGreenInvoice, } from '../../green-invoice/helpers/green-invoice.helper.js';
import { IssuedDocumentsProvider } from '../providers/issued-documents.provider.js';
import { normalizeDocumentType } from '../resolvers/common.js';
export async function getPaymentsFromTransactions(injector, transactions) {
    const payments = await Promise.all(transactions.map(async (transaction) => {
        // get account
        const account = await injector
            .get(FinancialAccountsProvider)
            .getFinancialAccountByAccountIDLoader.load(transaction.account_id);
        if (!account) {
            throw new Error(`Account with ID ${transaction.account_id} not found`);
        }
        // get account type
        let type = 'OTHER';
        switch (account.type) {
            case 'BANK_ACCOUNT':
                type = 'WIRE_TRANSFER';
                break;
            case 'CREDIT_CARD':
                type = 'CREDIT_CARD';
                break;
        }
        let bankAccount = undefined;
        if (account.type === 'BANK_ACCOUNT') {
            bankAccount = await injector
                .get(FinancialBankAccountsProvider)
                .getFinancialBankAccountByIdLoader.load(account.id);
            if (!bankAccount) {
                throw new GraphQLError(`Bank account details not found for financial account ID "${account.id}"`);
            }
        }
        // get further fields
        let paymentTypeSpecificAttributes = {};
        switch (type) {
            case 'CHEQUE':
                paymentTypeSpecificAttributes = {
                //   chequeNum: _____,
                };
                break;
            case 'CREDIT_CARD':
                paymentTypeSpecificAttributes = {
                    cardType: 'MASTERCARD', // TODO: add logic to support other card types
                    cardNum: account.account_number,
                    numPayments: 1,
                    firstPayment: transaction.event_date.getTime() / 1000, // assuming first payment is the transaction date
                };
                break;
            case 'WIRE_TRANSFER':
                paymentTypeSpecificAttributes = {
                    bankName: bankAccount?.bank_number?.toString(),
                    bankBranch: bankAccount?.branch_number?.toString(),
                    bankAccount: account.account_number?.toString(),
                };
                break;
            case 'PAYMENT_APP':
                paymentTypeSpecificAttributes = {
                //   appType: _____,
                //   accountId: _____,
                //   transactionId: _____,
                };
                break;
            case 'OTHER':
                paymentTypeSpecificAttributes = {
                //   subType: _____,
                //   accountId: _____,
                //   transactionId: _____,
                };
                break;
            case 'PAYPAL':
                paymentTypeSpecificAttributes = {
                //   accountId: _____,
                //   transactionId: _____,
                };
                break;
            case 'CASH':
            case 'TAX_DEDUCTION':
            case 'OTHER_DEDUCTION':
                break;
        }
        const payment = {
            currency: transaction.currency,
            currencyRate: undefined,
            date: dateToTimelessDateString(transaction.debit_date ?? transaction.event_date),
            price: Number(transaction.amount),
            type,
            transactionId: transaction.id,
            accountId: account.id,
            ...paymentTypeSpecificAttributes,
        };
        return payment;
    }));
    return payments;
}
export function getIncomeRecordsFromDocuments(greenInvoiceDocuments) {
    const incomes = greenInvoiceDocuments
        .map(greenInvoiceDocument => {
        return greenInvoiceDocument.income.filter(Boolean).map(originIncome => {
            if (!originIncome?.currency) {
                throw new Error('Income currency is missing');
            }
            const income = {
                description: originIncome.description,
                quantity: originIncome.quantity,
                price: originIncome.price,
                currency: originIncome.currency,
                currencyRate: undefined,
                vatRate: originIncome.vatRate,
                itemId: originIncome.itemId,
                vatType: getVatTypeFromGreenInvoiceDocument(greenInvoiceDocument.vatType),
            };
            return income;
        });
    })
        .flat();
    return incomes;
}
export function getTypeFromDocumentsAndTransactions(documents, transactions) {
    if (!documents.length) {
        if (transactions.length) {
            return DocumentType.InvoiceReceipt;
        }
        return DocumentType.Proforma;
    }
    const documentsTypes = new Set(documents.map(doc => doc.type));
    if (documentsTypes.size === 1) {
        switch (Array.from(documentsTypes)[0]) {
            case DocumentType.Invoice:
                return DocumentType.Receipt;
            case DocumentType.Proforma:
                return DocumentType.InvoiceReceipt;
        }
    }
    return DocumentType.Proforma;
}
export function filterAndHandleSwiftTransactions(originTransactions, swiftBusinessId) {
    const swiftTransactions = [];
    const incomeTransactions = [];
    for (const transaction of originTransactions) {
        if (swiftBusinessId && transaction.business_id === swiftBusinessId) {
            swiftTransactions.push(transaction);
        }
        else if (Number(transaction.amount) > 0) {
            incomeTransactions.push(transaction);
        }
    }
    if (swiftTransactions.length === 0) {
        return incomeTransactions;
    }
    if (swiftTransactions.length === 1 && incomeTransactions.length === 1) {
        const incomeTransaction = incomeTransactions[0];
        return [
            {
                ...incomeTransaction,
                amount: (Number(incomeTransaction.amount) - Number(swiftTransactions[0].amount)).toFixed(2),
            },
        ];
    }
    throw new GraphQLError("Unable to process transactions. Couldn't match swift fees to transactions");
}
export function getLinkedDocumentsAttributes(issuedDocuments, shouldCancel = false) {
    const linkedDocumentIds = issuedDocuments.map(doc => doc.external_id);
    let linkType = undefined;
    if (linkedDocumentIds.length) {
        if (shouldCancel) {
            linkType = 'CANCEL';
        }
        else {
            linkType = 'LINK';
        }
    }
    return {
        linkedDocumentIds,
        linkType,
    };
}
export function getDocumentDateOutOfTransactions(transactions) {
    const debitDates = transactions.map(tx => tx.debit_date).filter(Boolean);
    // if no debit dates, use current date
    if (!debitDates.length)
        return dateToTimelessDateString(new Date());
    // Sort dates and take the first one
    const sortedDates = [...debitDates].sort((a, b) => b.getTime() - a.getTime());
    const firstDate = sortedDates[0];
    // Return the date in the required format
    return dateToTimelessDateString(firstDate);
}
export async function deduceVatTypeFromBusiness(injector, locality, businessId) {
    if (!businessId)
        return 'DEFAULT';
    const business = await injector.get(BusinessesProvider).getBusinessByIdLoader.load(businessId);
    if (!business)
        return 'DEFAULT';
    // Deduce VAT type based on business information
    if (business.country === locality && !business.exempt_dealer) {
        return 'MIXED';
    }
    return 'EXEMPT';
}
export function createRemarks(contract) {
    const remarks = [];
    if (contract.purchase_orders?.length) {
        const lastPo = contract.purchase_orders[contract.purchase_orders.length - 1];
        if (lastPo) {
            remarks.push(`PO: ${lastPo}`);
        }
    }
    if (contract.remarks) {
        remarks.push(contract.remarks);
    }
    return remarks.join(', ');
}
export const convertContractToDraft = async (injector, contract, locality, issueMonth) => {
    const businessPromise = injector
        .get(BusinessesProvider)
        .getBusinessByIdLoader.load(contract.client_id);
    const clientPromise = injector.get(ClientsProvider).getClientByIdLoader.load(contract.client_id);
    const [business, client] = await Promise.all([businessPromise, clientPromise]);
    if (!business) {
        throw new GraphQLError(`Business ID="${contract.client_id}" not found`);
    }
    if (!client) {
        throw new GraphQLError(`Client not found for business ID="${contract.client_id}"`);
    }
    const greenInvoiceId = validateClientIntegrations(client.integrations)?.greenInvoiceId;
    if (!greenInvoiceId) {
        throw new GraphQLError(`Green invoice match not found for business ID="${contract.client_id}"`);
    }
    const today = issueMonth ? addMonths(new Date(issueMonth), 1) : new Date();
    const monthStart = dateToTimelessDateString(startOfMonth(today));
    const monthEnd = dateToTimelessDateString(endOfMonth(today));
    const year = today.getFullYear() + (today.getMonth() === 0 ? -1 : 0);
    const month = format(subMonths(today, 1), 'MMMM');
    const vatType = await deduceVatTypeFromBusiness(injector, locality, contract.client_id);
    const documentInput = {
        remarks: createRemarks(contract),
        description: `${getProductName(normalizeProduct(contract.product ?? ''))} ${getSubscriptionPlanName(normalizeSubscriptionPlan(contract.plan ?? ''))} - ${month} ${year}`,
        type: normalizeDocumentType(contract.document_type),
        date: monthStart,
        dueDate: monthEnd,
        language: 'ENGLISH',
        currency: contract.currency,
        vatType,
        rounding: false,
        signed: true,
        client,
        income: [
            {
                description: `${getProductName(normalizeProduct(contract.product ?? ''))} ${getSubscriptionPlanName(normalizeSubscriptionPlan(contract.plan ?? ''))} - ${month} ${year}`,
                quantity: 1,
                price: contract.amount,
                currency: contract.currency,
                vatType: 'EXEMPT',
            },
        ],
    };
    return documentInput;
};
export async function executeDocumentIssue(injector, adminBusinessId, initialInput, emailContent, attachment = true, chargeId, sendEmail = false) {
    try {
        const coreInput = await convertDocumentInputIntoGreenInvoiceInput(initialInput, injector);
        const input = {
            ...coreInput,
            client: !coreInput.client || sendEmail
                ? coreInput.client
                : {
                    ...coreInput.client,
                    emails: [], // if client exists, and sendEmail is false, we don't want to send emails
                },
            emailContent,
            attachment,
        };
        const document = await injector.get(GreenInvoiceClientProvider).addDocuments({ input });
        if (!document) {
            throw new GraphQLError('Failed to issue new document');
        }
        // validate all linked documents are closed
        if (input.linkedDocumentIds?.length) {
            await Promise.all(input.linkedDocumentIds.map(async (id) => {
                if (id) {
                    await injector
                        .get(GreenInvoiceClientProvider)
                        .closeDocument(id)
                        .catch(e => {
                        const message = `ERROR: Failed to close Green Invoice linked document with ID ${id}`;
                        console.error(`${message}: ${e}`);
                    });
                }
            }));
        }
        if ('id' in document && document.id) {
            const greenInvoiceDocument = await injector
                .get(GreenInvoiceClientProvider)
                .documentLoader.load(document.id);
            if (!greenInvoiceDocument) {
                console.error('Failed to fetch issued document from Green Invoice', document);
                throw new GraphQLError('Failed to issue new document');
            }
            // Insert new issued document to DB
            const newDocument = await insertNewDocumentFromGreenInvoice(injector, greenInvoiceDocument, adminBusinessId, chargeId);
            if (!newDocument.charge_id) {
                console.error('New document does not have a charge ID', newDocument);
                throw new GraphQLError('Failed to issue new document');
            }
            // Close linked documents
            if (coreInput.linkedDocumentIds?.length) {
                await Promise.all(coreInput.linkedDocumentIds.map(async (id) => {
                    if (id) {
                        await injector
                            .get(IssuedDocumentsProvider)
                            .updateIssuedDocumentByExternalId({ externalId: id, status: 'CLOSED' })
                            .catch(e => {
                            const message = `Failed to close linked document with ID ${id}`;
                            console.error(`${message}: ${e}`);
                            throw new Error(message);
                        });
                    }
                }));
            }
            if (getTypeFromGreenInvoiceDocument(coreInput.type) === DocumentType.CreditInvoice) {
                // Close origin
            }
            return injector.get(ChargesProvider).getChargeByIdLoader.load(newDocument.charge_id);
        }
        console.error('Document issue failed', document);
        throw new GraphQLError('Document issue failed');
    }
    catch (error) {
        console.error('Error issuing document:', error);
        throw new GraphQLError('Error issuing document');
    }
}
//# sourceMappingURL=issue-document.helper.js.map