import { GraphQLError } from 'graphql';
import { UUID_REGEX } from '../../../shared/constants.js';
import { AdminContextProvider } from '../../admin-context/providers/admin-context.provider.js';
import { updateGreenInvoiceClient } from '../../green-invoice/helpers/green-invoice-clients.helper.js';
import { SortCodesProvider } from '../../sort-codes/providers/sort-codes.provider.js';
import { TagsProvider } from '../../tags/providers/tags.provider.js';
import { TransactionsProvider } from '../../transactions/providers/transactions.provider.js';
import { suggestionDataSchema, } from '../helpers/business-suggestion-data-schema.helper.js';
import { updateSuggestions } from '../helpers/businesses.helper.js';
import { hasFinancialEntitiesCoreProperties } from '../helpers/financial-entities.helper.js';
import { filterBusinessByName } from '../helpers/utils.helper.js';
import { BusinessesProvider } from '../providers/businesses.provider.js';
import { FinancialEntitiesProvider } from '../providers/financial-entities.provider.js';
import { TaxCategoriesProvider } from '../providers/tax-categories.provider.js';
import { commonChargeFields, commonFinancialEntityFields } from './common.js';
export const businessesResolvers = {
    Query: {
        business: async (_, { id }, { injector }) => {
            const dbFe = await injector.get(BusinessesProvider).getBusinessByIdLoader.load(id);
            if (!dbFe) {
                throw new Error(`Business ID="${id}" not found`);
            }
            return dbFe;
        },
        businesses: async (_, { ids }, { injector }) => {
            const businesses = await injector.get(BusinessesProvider).getBusinessByIdLoader.loadMany(ids);
            if (businesses.some(business => !business || business instanceof Error)) {
                throw new Error(`Error fetching some of the businesses`);
            }
            return businesses;
        },
        allBusinesses: async (_, { page, limit, name }, { injector }) => {
            const businesses = await injector.get(BusinessesProvider).getAllBusinesses();
            const filteredBusinesses = businesses.filter(business => filterBusinessByName(business, name));
            page ??= 0;
            let pageBusinesses = filteredBusinesses.sort((a, b) => a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1);
            // handle pagination
            if (limit) {
                pageBusinesses = filteredBusinesses.slice(page * limit, (page + 1) * limit);
            }
            return {
                __typename: 'PaginatedBusinesses',
                nodes: pageBusinesses,
                pageInfo: {
                    totalPages: limit ? Math.ceil(filteredBusinesses.length / limit) : 1,
                    totalRecords: filteredBusinesses.length,
                    currentPage: page,
                    pageSize: limit,
                },
            };
        },
    },
    Mutation: {
        updateBusiness: async (_, { businessId, ownerId, fields }, { injector }) => {
            if (hasFinancialEntitiesCoreProperties(fields)) {
                await injector.get(FinancialEntitiesProvider).updateFinancialEntity({
                    ...fields,
                    financialEntityId: businessId,
                    irsCode: fields.irsCode ?? null,
                    isActive: fields.isActive ?? null,
                });
            }
            let suggestionData = null;
            if (fields.suggestions) {
                const currentBusiness = await injector
                    .get(BusinessesProvider)
                    .getBusinessByIdLoader.load(businessId);
                if (!currentBusiness) {
                    return {
                        __typename: 'CommonError',
                        message: `Business ID="${businessId}" not found`,
                    };
                }
                suggestionData = updateSuggestions(fields.suggestions, currentBusiness.suggestion_data);
            }
            const adjustedFields = {
                address: fields.address,
                city: fields.city,
                zipCode: fields.zipCode,
                email: fields.email,
                exemptDealer: fields.exemptDealer,
                vatNumber: fields.governmentId,
                hebrewName: fields.hebrewName,
                phoneNumber: fields.phoneNumber,
                website: fields.website,
                optionalVat: fields.optionalVAT,
                isReceiptEnough: fields.isReceiptEnough ?? null,
                isDocumentsOptional: fields.isDocumentsOptional ?? null,
                country: fields.country,
                suggestionData,
                pcn874RecordTypeOverride: fields.pcn874RecordType,
            };
            let updatedBusiness;
            try {
                if (Object.values(adjustedFields).some(field => field != null)) {
                    await injector
                        .get(BusinessesProvider)
                        .updateBusiness({ ...adjustedFields, businessId })
                        .catch((e) => {
                        const message = `Error updating business ID="${businessId}"`;
                        console.error(`${message}: ${e}`);
                        if (e instanceof GraphQLError) {
                            throw e;
                        }
                        throw new Error(message);
                    });
                }
                if (fields.taxCategory) {
                    const texCategoryParams = {
                        businessId,
                        ownerId,
                        taxCategoryId: fields.taxCategory,
                    };
                    try {
                        await injector.get(TaxCategoriesProvider).insertBusinessTaxCategory(texCategoryParams);
                    }
                    catch (error) {
                        console.error(error);
                        await injector
                            .get(TaxCategoriesProvider)
                            .updateBusinessTaxCategory(texCategoryParams)
                            .catch((e) => {
                            const message = `Error updating tax category for business ID="${businessId}"`;
                            console.error(`${message}: ${e}`);
                            throw new Error(message);
                        });
                    }
                }
                updatedBusiness = await injector
                    .get(BusinessesProvider)
                    .getBusinessByIdLoader.load(businessId);
                if (!updatedBusiness) {
                    throw new Error(`Updated business not found`);
                }
            }
            catch (e) {
                return {
                    __typename: 'CommonError',
                    message: `Failed to update business ID="${businessId}": ${e.message}`,
                };
            }
            // update green invoice client if needed
            await updateGreenInvoiceClient(businessId, injector, fields);
            return updatedBusiness;
        },
        insertNewBusiness: async (_, { fields }, { injector }) => {
            try {
                if (!fields.country) {
                    throw new GraphQLError(`Country is required to create a new business`);
                }
                const { ownerId } = await injector.get(AdminContextProvider).getVerifiedAdminContext();
                let irsCode = fields.irsCode ?? null;
                if (!irsCode && fields.sortCode) {
                    const sortCode = await injector
                        .get(SortCodesProvider)
                        .getSortCodesByIdLoader.load({ key: fields.sortCode, ownerId });
                    if (sortCode?.default_irs_code) {
                        irsCode = sortCode.default_irs_code;
                    }
                }
                const [financialEntity] = await injector
                    .get(FinancialEntitiesProvider)
                    .insertFinancialEntity({
                    ownerId,
                    name: fields.name,
                    sortCode: fields.sortCode,
                    type: 'business',
                    irsCode,
                    isActive: fields.isActive ?? true,
                })
                    .catch((e) => {
                    console.error(e);
                    return [];
                });
                if (!financialEntity) {
                    return {
                        __typename: 'CommonError',
                        message: `Failed to create core financial entity`,
                    };
                }
                const suggestions = fields.suggestions
                    ? {
                        tags: fields.suggestions.tags?.map(tag => tag.id),
                        phrases: fields.suggestions.phrases?.map(phrase => phrase),
                        description: fields.suggestions.description ?? undefined,
                        emails: fields.suggestions.emails ? [...fields.suggestions.emails] : undefined,
                        emailListener: fields.suggestions.emailListener
                            ? {
                                ...fields.suggestions.emailListener,
                                internalEmailLinks: fields.suggestions.emailListener.internalEmailLinks
                                    ? [...fields.suggestions.emailListener.internalEmailLinks]
                                    : undefined,
                                emailBody: fields.suggestions.emailListener.emailBody ?? undefined,
                                attachments: fields.suggestions.emailListener.attachments
                                    ? [...fields.suggestions.emailListener.attachments]
                                    : undefined,
                            }
                            : undefined,
                        priority: fields.suggestions.priority ?? undefined,
                    }
                    : undefined;
                const business = await injector.get(BusinessesProvider).insertBusinessLoader.load({
                    id: financialEntity.id,
                    ownerId: financialEntity.owner_id,
                    address: fields.address,
                    city: fields.city,
                    zipCode: fields.zipCode,
                    email: fields.email,
                    exemptDealer: fields.exemptDealer ?? false,
                    governmentId: fields.governmentId,
                    hebrewName: fields.hebrewName,
                    phoneNumber: fields.phoneNumber,
                    website: fields.website,
                    optionalVat: fields.optionalVAT ?? false,
                    isReceiptEnough: fields.isReceiptEnough ?? false,
                    isDocumentsOptional: fields.isDocumentsOptional ?? false,
                    country: fields.country,
                    suggestions,
                    pcn874RecordTypeOverride: fields.pcn874RecordType,
                });
                if (!business) {
                    throw new Error(`Failed to create core business`);
                }
                if (fields.taxCategory) {
                    const taxCategoryParams = {
                        businessId: financialEntity.id,
                        ownerId,
                        taxCategoryId: fields.taxCategory,
                    };
                    await injector
                        .get(TaxCategoriesProvider)
                        .insertBusinessTaxCategory(taxCategoryParams)
                        .then(res => res[0])
                        .catch((e) => {
                        const message = `Error updating tax category for business ID="${financialEntity.id}"`;
                        console.error(`${message}: ${e}`);
                        throw new Error(message);
                    });
                }
                return {
                    ...financialEntity,
                    ...business,
                };
            }
            catch (e) {
                console.error(e);
                return {
                    __typename: 'CommonError',
                    message: `Failed to create business`,
                };
            }
        },
        mergeBusinesses: async (_, { targetBusinessId, businessIdsToMerge }, { injector }) => {
            try {
                const [targetBusiness, ...businessesToMerge] = await injector
                    .get(BusinessesProvider)
                    .getBusinessByIdLoader.loadMany([targetBusinessId, ...businessIdsToMerge]);
                if (!targetBusiness || targetBusiness instanceof Error) {
                    throw new GraphQLError(`Target business not found`);
                }
                const mergeBusinessPromises = businessIdsToMerge.map(businessIdToMerge => injector
                    .get(FinancialEntitiesProvider)
                    .replaceFinancialEntity(targetBusinessId, businessIdToMerge, true));
                await Promise.all(mergeBusinessPromises);
                // handle suggestions
                let suggestionData = null;
                const { data: targetSuggestionData, error, success, } = suggestionDataSchema.safeParse(targetBusiness.suggestion_data);
                if (success) {
                    suggestionData = targetSuggestionData;
                }
                else {
                    console.error('Failed to parse suggestion data for business', {
                        businessId: targetBusiness.id,
                        error,
                    });
                    throw new GraphQLError('Failed to parse suggestion data for target business');
                }
                for (const businessToMerge of businessesToMerge) {
                    if (!businessToMerge || businessToMerge instanceof Error) {
                        continue;
                    }
                    const { data: mergedBusinessSuggestionData, success } = suggestionDataSchema.safeParse(businessToMerge.suggestion_data);
                    if (success && mergedBusinessSuggestionData.phrases) {
                        suggestionData = updateSuggestions({ phrases: mergedBusinessSuggestionData.phrases }, suggestionData, true);
                    }
                }
                if (suggestionData) {
                    await injector.get(BusinessesProvider).updateBusiness({
                        businessId: targetBusinessId,
                        suggestionData,
                    });
                }
                // refetch updated target business
                const updatedBusiness = await injector
                    .get(BusinessesProvider)
                    .getBusinessByIdLoader.load(targetBusinessId);
                if (!updatedBusiness || updatedBusiness instanceof Error) {
                    throw new GraphQLError(`Target business not found`);
                }
                return updatedBusiness;
            }
            catch (e) {
                console.error(e);
                if (e instanceof GraphQLError) {
                    throw e;
                }
                throw new GraphQLError(`Failed to merge businesses`);
            }
        },
        batchGenerateBusinessesOutOfTransactions: async (_, __, { injector }) => {
            const { ownerId, locality } = await injector
                .get(AdminContextProvider)
                .getVerifiedAdminContext();
            const transactionsPromise = injector.get(TransactionsProvider).getTransactionsByFilters({
                ownerIDs: [ownerId],
            });
            const businessesPromise = injector.get(BusinessesProvider).getAllBusinesses();
            const [transactions, businesses] = await Promise.all([
                transactionsPromise,
                businessesPromise,
            ]);
            const businessIdsPerDescription = new Map();
            transactions.map(transaction => {
                if (!transaction.source_description) {
                    return;
                }
                const description = transaction.source_description.toLowerCase();
                if (!businessIdsPerDescription.has(description)) {
                    businessIdsPerDescription.set(description, new Set());
                }
                if (transaction.business_id) {
                    businessIdsPerDescription.get(description)?.add(transaction.business_id);
                }
            });
            businesses.map(business => {
                const { data: suggestionData, success } = suggestionDataSchema.safeParse(business.suggestion_data);
                if (!success || !suggestionData.phrases) {
                    return;
                }
                suggestionData.phrases.map(phrase => {
                    const description = phrase.toLowerCase();
                    if (!businessIdsPerDescription.has(description)) {
                        businessIdsPerDescription.set(description, new Set());
                    }
                    businessIdsPerDescription.get(description)?.add(business.id);
                });
            });
            const descriptionBusinessPairs = [];
            const multipleMatchesDescriptions = [];
            const nonMatchesDescriptions = [];
            Array.from(businessIdsPerDescription.entries()).map(async ([description, businessIds]) => {
                if (businessIds.size === 1) {
                    descriptionBusinessPairs.push([description, businessIds.values().next().value]);
                    return;
                }
                if (businessIds.size > 1) {
                    multipleMatchesDescriptions.push([description, Array.from(businessIds.values())]);
                    return;
                }
                nonMatchesDescriptions.push(description);
            });
            console.log('Multiple matches descriptions:', multipleMatchesDescriptions
                .map(([description, businessIDs]) => `\n  "${description}": ${businessIDs.join(', ')}`)
                .join(''));
            const uniqueDescriptions = Array.from(new Set(nonMatchesDescriptions)).sort();
            const businessTransactions = new Map();
            transactions.map(t => {
                if (!t.business_id && t.source_description) {
                    const businessId = descriptionBusinessPairs.find(pair => pair[0] === t.source_description);
                    if (businessId) {
                        if (!businessTransactions.has(businessId[1])) {
                            businessTransactions.set(businessId[1], []);
                        }
                        businessTransactions.get(businessId[1])?.push(t.id);
                    }
                }
            });
            Array.from(businessTransactions.entries())
                .map(([businessId, transactionIds]) => transactionIds.map(transactionId => injector.get(TransactionsProvider).updateTransactions({
                businessId,
                transactionIds: [transactionId],
            })))
                .flat();
            const newBusinessesIds = await Promise.all(uniqueDescriptions.map(async (description) => {
                const financialEntity = await injector
                    .get(FinancialEntitiesProvider)
                    .insertFinancialEntitiesLoader.load({
                    ownerId,
                    name: description,
                    type: 'business',
                    sortCode: null,
                    irsCode: null,
                    isActive: true,
                })
                    .catch((e) => {
                    console.error(e);
                    return null;
                });
                if (!financialEntity) {
                    throw new Error(`Failed to create core financial entity for description "${description}"`);
                }
                const business = await injector.get(BusinessesProvider).insertBusinessLoader.load({
                    id: financialEntity.id,
                    ownerId: financialEntity.owner_id,
                    country: locality,
                    hebrewName: description,
                    suggestions: {
                        phrases: [description],
                    },
                    address: undefined,
                    city: undefined,
                    zipCode: undefined,
                    email: undefined,
                    website: undefined,
                    phoneNumber: undefined,
                    governmentId: undefined,
                    exemptDealer: false,
                    optionalVat: false,
                    isReceiptEnough: false,
                    isDocumentsOptional: false,
                    pcn874RecordTypeOverride: null,
                });
                if (!business) {
                    throw new Error(`Failed to create business for description "${description}"`);
                }
                return business.id;
            }));
            return injector
                .get(BusinessesProvider)
                .getBusinessByIdLoader.loadMany(newBusinessesIds);
        },
    },
    Business: {
        __resolveType: async (parent, { injector }) => {
            if (!parent.country) {
                const business = await injector
                    .get(BusinessesProvider)
                    .getBusinessByIdLoader.load(parent.id);
                if (business) {
                    Object.assign(parent, business);
                }
            }
            return 'LtdFinancialEntity';
        },
    },
    LtdFinancialEntity: {
        ...commonFinancialEntityFields,
        country: DbBusiness => DbBusiness.country,
        governmentId: DbBusiness => DbBusiness.vat_number,
        address: DbBusiness => DbBusiness.address ?? DbBusiness.address_hebrew,
        city: DbBusiness => DbBusiness.city,
        zipCode: DbBusiness => DbBusiness.zip_code,
        hebrewName: DbBusiness => DbBusiness.hebrew_name,
        email: DbBusiness => DbBusiness.email,
        exemptDealer: DbBusiness => DbBusiness.exempt_dealer,
        website: DbBusiness => DbBusiness.website,
        phoneNumber: DbBusiness => DbBusiness.phone_number,
        suggestions: (DbBusiness, _, { injector }) => {
            const { data: suggestionData, success } = suggestionDataSchema.safeParse(DbBusiness.suggestion_data);
            if (success) {
                return {
                    tags: suggestionData.tags
                        ? suggestionData.tags.map(suggestedTag => (UUID_REGEX.test(suggestedTag)
                            ? injector.get(TagsProvider).getTagByIDLoader.load(suggestedTag)
                            : injector.get(TagsProvider).getTagByNameLoader.load(suggestedTag))
                            .then(tag => {
                            if (!tag) {
                                throw new GraphQLError(`Tag "${suggestedTag}" not found`);
                            }
                            return tag;
                        })
                            .catch(e => {
                            console.error(e);
                            throw new GraphQLError(`Failed to load tag "${suggestedTag}"`);
                        }))
                        : [],
                    phrases: suggestionData.phrases ?? [],
                    description: suggestionData.description ?? null,
                    emails: suggestionData.emails ?? [],
                    emailListener: suggestionData.emailListener ?? null,
                    priority: suggestionData.priority ?? null,
                };
            }
            return null;
        },
        optionalVAT: DbBusiness => DbBusiness.optional_vat,
        pcn874RecordType: DbBusiness => DbBusiness.pcn874_record_type_override,
        isReceiptEnough: DbBusiness => DbBusiness.can_settle_with_receipt,
        isDocumentsOptional: DbBusiness => DbBusiness.no_invoices_required,
    },
    PersonalFinancialEntity: {
        ...commonFinancialEntityFields,
        email: DbBusiness => DbBusiness.email ?? '', // TODO: remove alternative ''
        pcn874RecordType: DbBusiness => DbBusiness.pcn874_record_type_override,
    },
    UpdateBusinessResponse: {
        __resolveType: (obj, _context, _info) => {
            if ('__typename' in obj && obj.__typename === 'CommonError')
                return 'CommonError';
            return 'LtdFinancialEntity';
        },
    },
    CommonCharge: commonChargeFields,
    FinancialCharge: commonChargeFields,
    ConversionCharge: commonChargeFields,
    SalaryCharge: commonChargeFields,
    InternalTransferCharge: commonChargeFields,
    DividendCharge: commonChargeFields,
    BusinessTripCharge: commonChargeFields,
    MonthlyVatCharge: commonChargeFields,
    BankDepositCharge: commonChargeFields,
    ForeignSecuritiesCharge: commonChargeFields,
    CreditcardBankCharge: commonChargeFields,
};
//# sourceMappingURL=businesses.resolver.js.map