import { __decorate, __metadata, __param } from "tslib";
import DataLoader from 'dataloader';
import { GraphQLError } from 'graphql';
import { Inject, Injectable, Scope } from 'graphql-modules';
import { init, } from '../../../../green-invoice-graphql/src/index.js';
import { dateToTimelessDateString } from '../../shared/helpers/index.js';
import { ENVIRONMENT } from '../../shared/tokens.js';
import { AdminContextProvider } from '../admin-context/providers/admin-context.provider.js';
import { ProviderCredentialsProvider } from '../provider-credentials/providers/provider-credentials.provider.js';
let GreenInvoiceClientProvider = class GreenInvoiceClientProvider {
    adminContextProvider;
    env;
    credentialsProvider;
    initPromise = null;
    constructor(adminContextProvider, env, credentialsProvider) {
        this.adminContextProvider = adminContextProvider;
        this.env = env;
        this.credentialsProvider = credentialsProvider;
    }
    authToken = null;
    init() {
        this.initPromise ??= this._doInit();
        return this.initPromise;
    }
    async _doInit() {
        const fromDb = await this.credentialsProvider.getGreenInvoiceCredentials();
        const creds = fromDb ?? null;
        if (!creds) {
            throw new GraphQLError('Green Invoice credentials not configured for this tenant', {
                extensions: { code: 'PROVIDER_NOT_CONFIGURED' },
            });
        }
        return init(creds.id, creds.secret);
    }
    async getSDK() {
        try {
            const { sdk, authToken } = await this.init();
            this.authToken = authToken;
            return sdk;
        }
        catch (e) {
            const message = 'Green Invoice SDK error';
            console.error(`${message}: ${e}`);
            throw new Error(message, { cause: e });
        }
    }
    /**
     * Expenses
     * */
    async addExpenseDraftByFile(file) {
        try {
            const { defaultLocalCurrency } = await this.adminContextProvider.getVerifiedAdminContext();
            // convert what-wg file to nodejs file
            const newFile = new File([Buffer.from(await file.arrayBuffer())], 'name' in file ? file.name : 'new-file', {
                type: file.type,
            });
            // get file upload url + params
            const sdk = await this.getSDK();
            const { getFileUploadUrl: fileUploadParams } = await sdk.getFileUploadUrl_query({
                context: 'expense',
                data: { source: 5 },
            });
            if (!fileUploadParams) {
                throw new Error('No file upload params returned');
            }
            const form = new FormData();
            for (const [field, value] of Object.entries(fileUploadParams.fields)) {
                if (value) {
                    // replace field names underscores with dashes (originally dashes, Mesh converted to underscores to match GraphQL spec)
                    form.append(field.replaceAll('_', '-'), value);
                }
            }
            form.append('file', newFile, newFile.name);
            const uploadTime = new Date().getTime();
            // upload the file
            await fetch(fileUploadParams.url, {
                method: 'POST',
                body: form,
            }).then(async (res) => {
                if (![200, 201, 204].includes(res.status)) {
                    throw new Error('Failed to upload file');
                }
                return;
            });
            // TODO: next section is temporary fix until decent webhook will be set up
            let retryCounter = 15; // 30 seconds
            while (retryCounter > 0) {
                await new Promise(resolve => setTimeout(resolve, 2000)); // 2 second delay to allow Green Invoice to process the file.
                const res = await sdk.searchExpenseDrafts_query({
                    input: {
                        fromDate: dateToTimelessDateString(new Date()),
                        toDate: dateToTimelessDateString(new Date()),
                    },
                });
                if (res.searchExpenseDrafts &&
                    'items' in res.searchExpenseDrafts &&
                    res.searchExpenseDrafts.items.length) {
                    const items = res.searchExpenseDrafts.items.filter(item => {
                        return !!item && item?.creationDate > uploadTime / 1000;
                    });
                    if (items.length > 1) {
                        // currently we cannot differentiate between drafts
                        throw new Error('Multiple drafts found');
                    }
                    if (items.length === 1) {
                        const { expense } = items[0];
                        // check if any actual OCR fields exists
                        if (expense.amount !== null ||
                            expense.currency !== defaultLocalCurrency ||
                            expense.date !== null ||
                            expense.documentType !== '_20' ||
                            expense.number !== null ||
                            expense.vat !== 0) {
                            console.log(`Finally, it took only ${(15 - retryCounter) * 2} seconds...`);
                            return items[0];
                        }
                    }
                }
                retryCounter -= 1;
            }
            console.log('Failed to fetch OCR data, returning empty draft');
            const emptyDraft = {
                expense: {
                    documentType: '_405', // "receipt on donation" which is not supported, resulting in unprocessed document
                    number: undefined,
                    date: undefined,
                    amount: 0,
                    currency: defaultLocalCurrency,
                    vat: 0,
                },
            };
            return emptyDraft;
        }
        catch (e) {
            const message = 'Green Invoice Add Expense Draft error';
            console.error(`${message}: ${e}`);
            throw new Error(message, { cause: e });
        }
    }
    /**
     * Documents
     * */
    async searchDocuments(...params) {
        const sdk = await this.getSDK();
        return sdk.searchDocuments_query(...params).then(res => {
            for (const doc of res.searchDocuments?.items ?? []) {
                if (doc)
                    this.documentLoader.prime(doc.id, doc);
            }
            return res.searchDocuments;
        });
    }
    async addDocuments(...params) {
        const sdk = await this.getSDK();
        return sdk.addDocument_mutation(...params).then(res => res.addDocument);
    }
    async previewDocuments(...params) {
        const sdk = await this.getSDK();
        return sdk.previewDocument_query(...params).then(res => res.previewDocument);
    }
    async getLinkedDocuments(documentId) {
        const sdk = await this.getSDK();
        return sdk.getLinkedDocuments_query({ id: documentId }).then(res => res.getLinkedDocuments);
    }
    async getDocumentLinks(documentId) {
        const sdk = await this.getSDK();
        return sdk
            .getDocumentsDownloadLinks_query({ id: documentId })
            .then(res => res.getDocumentsDownloadLinks);
    }
    async _batchLoadByIds(ids, fetcher, primingFunction) {
        const sdk = await this.getSDK();
        const uniqueIds = Array.from(new Set(ids));
        const items = await Promise.all(uniqueIds.map(async (id) => fetcher(sdk, id).then(item => {
            if (item?.id) {
                primingFunction(item);
            }
            return item ?? undefined;
        })));
        const itemsMap = new Map(items.filter((i) => !!i).map(i => [i.id, i]));
        return ids.map(id => itemsMap.get(id) ?? null);
    }
    async batchDocumentsByIds(ids) {
        return this._batchLoadByIds(ids, (sdk, id) => sdk.getDocument_query({ id }).then(res => res.getDocument), item => this.documentLoader.prime(item.id, item));
    }
    documentLoader = new DataLoader((ids) => this.batchDocumentsByIds(ids));
    async closeDocument(documentId) {
        this.invalidateDocument(documentId);
        const sdk = await this.getSDK();
        return sdk.closeDocument_mutation({ id: documentId }).then(res => res.closeDocument);
    }
    invalidateDocument(documentId) {
        this.documentLoader.clear(documentId);
    }
    /**
     * Clients
     * */
    async batchClientsByIds(ids) {
        return this._batchLoadByIds(ids, (sdk, id) => sdk.getClient_query({ id }).then(res => res.getClient), item => this.clientLoader.prime(item.id, item));
    }
    clientLoader = new DataLoader((ids) => this.batchClientsByIds(ids));
    async createClient(input) {
        try {
            const sdk = await this.getSDK();
            const client = await sdk.addClient_mutation({ input }).then(res => res.addClient);
            if (!client) {
                throw new Error('Failed to create Green Invoice client');
            }
            if ('errorMessage' in client) {
                throw new Error(`Failed to create Green Invoice client: ${client.errorMessage}`);
            }
            const { id } = client;
            if (id) {
                this.clientLoader.prime(id, client);
            }
            return client;
        }
        catch (error) {
            const message = 'Green Invoice Create Client error';
            console.error(`${message}: ${error}`);
            throw new Error(message, { cause: error });
        }
    }
    async updateClient(args) {
        try {
            const sdk = await this.getSDK();
            const client = await sdk.updateClient_mutation(args).then(res => res.updateClient);
            if (!client) {
                throw new Error('Failed to update Green Invoice client');
            }
            if ('errorMessage' in client) {
                throw new Error(`Failed to update Green Invoice client: ${client.errorMessage}`);
            }
            const { id } = client;
            if (id) {
                this.clientLoader.prime(id, client);
            }
            return client;
        }
        catch (error) {
            this.invalidateClient(args.id);
            const message = 'Green Invoice Update Client error';
            console.error(`${message}: ${error}`);
            throw new Error(message, { cause: error });
        }
    }
    async deleteClient(clientId) {
        try {
            const sdk = await this.getSDK();
            const client = await sdk
                .deleteClient_mutation({ id: clientId })
                .then(res => res.deleteClient);
            if (!client) {
                throw new Error('Failed to delete Green Invoice client');
            }
            if ('errorMessage' in client) {
                throw new Error(`Failed to delete Green Invoice client: ${client.errorMessage}`);
            }
            this.invalidateClient(clientId);
            return client;
        }
        catch (error) {
            this.invalidateClient(clientId);
            const message = 'Green Invoice Delete Client error';
            console.error(`${message}: ${error}`);
            throw new Error(message, { cause: error });
        }
    }
    invalidateClient(clientId) {
        this.clientLoader.clear(clientId);
    }
};
GreenInvoiceClientProvider = __decorate([
    Injectable({
        scope: Scope.Operation,
        global: true,
    }),
    __param(1, Inject(ENVIRONMENT)),
    __metadata("design:paramtypes", [AdminContextProvider, Object, ProviderCredentialsProvider])
], GreenInvoiceClientProvider);
export { GreenInvoiceClientProvider };
//# sourceMappingURL=green-invoice-client.js.map