import { GraphQLError } from 'graphql';
import { EMPTY_UUID } from '../../../shared/constants.js';
import { DocumentType } from '../../../shared/enums.js';
import { AdminContextProvider } from '../../admin-context/providers/admin-context.provider.js';
import { GoogleDriveProvider } from '../../app-providers/google-drive/google-drive.provider.js';
import { GreenInvoiceClientProvider } from '../../app-providers/green-invoice-client.js';
import { deleteCharges } from '../../charges/helpers/delete-charges.helper.js';
import { ChargesProvider } from '../../charges/providers/charges.provider.js';
import { TransactionsProvider } from '../../transactions/providers/transactions.provider.js';
import { getDocumentFromFile } from '../helpers/upload.helper.js';
import { DocumentsProvider } from '../providers/documents.provider.js';
import { IssuedDocumentsProvider } from '../providers/issued-documents.provider.js';
import { commonChargeFields, commonDocumentsFields, commonFinancialDocumentsFields, } from './common.js';
export const documentsResolvers = {
    Query: {
        documents: async (_, __, { injector }) => {
            const dbDocs = await injector.get(DocumentsProvider).getAllDocuments();
            return dbDocs;
        },
        documentsByFilters: async (_, { filters }, { injector }) => {
            const dbDocs = await injector.get(DocumentsProvider).getDocumentsByExtendedFilters(filters);
            return dbDocs;
        },
        documentById: async (_, { documentId }, { injector }) => {
            const doc = await injector.get(DocumentsProvider).getDocumentsByIdLoader.load(documentId);
            return doc ?? null;
        },
        recentDocumentsByBusiness: async (_, { businessId, limit }, { injector }) => {
            const businessDocs = await injector
                .get(DocumentsProvider)
                .getDocumentsByBusinessIdLoader.load(businessId);
            if (!businessDocs?.length) {
                return [];
            }
            const sortedDocs = [...businessDocs].sort((a, b) => (b.date ?? b.created_at).getTime() - (a.date ?? a.created_at).getTime());
            return sortedDocs.slice(0, limit ?? 7);
        },
        recentDocumentsByClient: async (_, { clientId, limit }, { injector }) => {
            const clientDocs = await injector
                .get(IssuedDocumentsProvider)
                .getIssuedDocumentsByClientIdLoader.load(clientId);
            if (!clientDocs?.length) {
                return [];
            }
            const sortedDocs = [...clientDocs].sort((a, b) => (b.date ?? b.created_at).getTime() - (a.date ?? a.created_at).getTime());
            return sortedDocs.slice(0, limit ?? 7);
        },
        recentIssuedDocumentsByType: async (_, { documentType, limit = 3 }, { injector }) => {
            const docs = await injector
                .get(IssuedDocumentsProvider)
                .getIssuedDocumentsByType({ type: documentType, limit });
            return docs;
        },
    },
    Mutation: {
        uploadDocument: async (_, { file, chargeId }, { injector }) => {
            try {
                const newDocument = await getDocumentFromFile(injector, file, chargeId);
                const [document] = await injector
                    .get(DocumentsProvider)
                    .insertDocuments({ documents: [newDocument] });
                return { document };
            }
            catch (e) {
                const message = 'Error uploading document';
                console.error(`${message}: ${e}`);
                return {
                    __typename: 'CommonError',
                    message,
                };
            }
        },
        batchUploadDocuments: async (_, { documents, isSensitive, chargeId }, { injector }) => {
            if (!chargeId) {
                const { ownerId } = await injector.get(AdminContextProvider).getVerifiedAdminContext();
                // generate new charge
                const newCharge = await injector.get(ChargesProvider).generateCharge({
                    ownerId,
                    userDescription: 'New uploaded documents',
                });
                if (!newCharge) {
                    throw new GraphQLError(`Failed to generate new charge for new document`);
                }
                chargeId = newCharge.id;
            }
            const newDocuments = [];
            await Promise.all(documents.map(async (document) => {
                // get new document data
                const newDocument = await getDocumentFromFile(injector, document, chargeId, isSensitive);
                newDocuments.push(newDocument);
            }));
            const res = await injector
                .get(DocumentsProvider)
                .insertDocuments({ documents: newDocuments });
            return res.map(document => ({ document: document }));
        },
        batchUploadDocumentsFromGoogleDrive: async (_, { sharedFolderUrl, chargeId, isSensitive }, { injector }) => {
            const isValidGoogleDriveUrl = (url) => {
                try {
                    const parsedUrl = new URL(url);
                    return (parsedUrl.hostname === 'drive.google.com' && parsedUrl.pathname.includes('/folders/'));
                }
                catch {
                    return false;
                }
            };
            if (!isValidGoogleDriveUrl(sharedFolderUrl)) {
                throw new GraphQLError('Invalid Google Drive folder URL');
            }
            const files = await injector
                .get(GoogleDriveProvider)
                .fetchFilesFromSharedFolder(sharedFolderUrl);
            if (!chargeId) {
                const { ownerId } = await injector.get(AdminContextProvider).getVerifiedAdminContext();
                // generate new charge
                const newCharge = await injector.get(ChargesProvider).generateCharge({
                    ownerId,
                    userDescription: 'New uploaded documents',
                });
                if (!newCharge) {
                    throw new GraphQLError(`Failed to generate new charge for new document`);
                }
                chargeId = newCharge.id;
            }
            const newDocuments = [];
            await Promise.all(files.map(async (file) => {
                // get new document data
                try {
                    const newDocument = await getDocumentFromFile(injector, file, chargeId, isSensitive);
                    newDocuments.push(newDocument);
                }
                catch (error) {
                    // Skip this file and continue with other files
                    console.error(error);
                }
            }));
            const res = await injector
                .get(DocumentsProvider)
                .insertDocuments({ documents: newDocuments });
            return res.map(document => ({ document: document }));
        },
        updateDocument: async (_, { fields, documentId }, { injector }) => {
            let postUpdateActions = async () => void 0;
            try {
                let chargeId = undefined;
                if (fields.chargeId === EMPTY_UUID) {
                    // case unlinked from charge
                    const document = await injector
                        .get(DocumentsProvider)
                        .getDocumentsByIdLoader.load(documentId);
                    if (!document) {
                        throw new GraphQLError(`Document ID="${documentId}" not valid`);
                    }
                    if (document.charge_id) {
                        const [charge, transactions, documents] = await Promise.all([
                            injector.get(ChargesProvider).getChargeByIdLoader.load(document.charge_id),
                            injector
                                .get(TransactionsProvider)
                                .transactionsByChargeIDLoader.load(document.charge_id),
                            injector.get(DocumentsProvider).getDocumentsByChargeIdLoader.load(document.charge_id),
                        ]);
                        if (!charge) {
                            throw new GraphQLError(`Former document's charge ID ("${fields.chargeId}") not valid`);
                        }
                        // generate new charge
                        const newCharge = await injector.get(ChargesProvider).generateCharge({
                            ownerId: charge.owner_id,
                            userDescription: 'Document unlinked from charge',
                        });
                        if (!newCharge) {
                            throw new GraphQLError(`Failed to generate new charge for document ID="${documentId}"`);
                        }
                        chargeId = newCharge.id;
                        if (documents.length === 1 && transactions.length === 0) {
                            postUpdateActions = async () => {
                                try {
                                    await deleteCharges([charge.id], injector);
                                }
                                catch (e) {
                                    if (e instanceof GraphQLError) {
                                        throw e;
                                    }
                                    console.error(e);
                                    throw new GraphQLError(`Failed to delete the empty former charge ID="${charge.id}"`);
                                }
                                return postUpdateActions();
                            };
                        }
                    }
                }
                else if (fields.chargeId) {
                    // case new charge ID
                    const charge = await injector
                        .get(ChargesProvider)
                        .getChargeByIdLoader.load(fields.chargeId);
                    if (!charge) {
                        throw new GraphQLError(`Charge ID="${fields.chargeId}" not valid`);
                    }
                    chargeId = charge.id;
                }
                const adjustedFields = {
                    documentId,
                    chargeId: chargeId ?? null,
                    currencyCode: fields.amount?.currency ?? null,
                    creditorId: fields.creditorId ?? null,
                    debtorId: fields.debtorId ?? null,
                    date: fields.date ? new Date(fields.date) : null,
                    fileUrl: fields.file ? fields.file.toString() : null,
                    imageUrl: fields.image ? fields.image.toString() : null,
                    serialNumber: fields.serialNumber ?? null,
                    totalAmount: fields.amount?.raw ?? null,
                    type: fields.documentType ?? null,
                    vatAmount: fields.vat?.raw ?? null,
                    vatReportDateOverride: fields.vatReportDateOverride
                        ? new Date(fields.vatReportDateOverride)
                        : null,
                    noVatAmount: fields.noVatAmount ?? null,
                    isReviewed: true,
                    allocationNumber: fields.allocationNumber ?? null,
                    exchangeRateOverride: fields.exchangeRateOverride ?? null,
                    description: fields.description ?? null,
                    remarks: fields.remarks ?? null,
                };
                const res = await injector.get(DocumentsProvider).updateDocument({ ...adjustedFields });
                if (!res || res.length === 0) {
                    throw new Error(`Document ID="${documentId}" not found`);
                }
                await postUpdateActions();
                return {
                    document: res[0],
                };
            }
            catch (e) {
                if (e instanceof GraphQLError) {
                    throw e;
                }
                return {
                    __typename: 'CommonError',
                    message: e?.message ?? 'Unknown error',
                };
            }
        },
        deleteDocument: async (_, { documentId }, { injector }) => {
            try {
                const document = await injector
                    .get(DocumentsProvider)
                    .getDocumentsByIdLoader.load(documentId);
                if (!document) {
                    throw new GraphQLError(`Document ID="${documentId}" not found`);
                }
                const res = await injector.get(DocumentsProvider).deleteDocument({ documentId });
                if (res.length === 1) {
                    if (document.charge_id) {
                        const [charge, transactions, documents] = await Promise.all([
                            injector.get(ChargesProvider).getChargeByIdLoader.load(document.charge_id),
                            injector
                                .get(TransactionsProvider)
                                .transactionsByChargeIDLoader.load(document.charge_id),
                            injector.get(DocumentsProvider).getDocumentsByChargeIdLoader.load(document.charge_id),
                        ]);
                        if (charge && documents.length === 0 && transactions.length === 0) {
                            await deleteCharges([charge.id], injector);
                        }
                    }
                    return true;
                }
                throw new GraphQLError(res.length === 0
                    ? 'Document not found'
                    : `More than one document found and deleted: ${res}`);
            }
            catch (e) {
                if (e instanceof GraphQLError) {
                    throw e;
                }
                throw new GraphQLError(`Failed to delete document ID="${documentId}": ${e}`);
            }
        },
        insertDocument: async (_, { record }, { injector }) => {
            let charge;
            try {
                if (record.chargeId) {
                    charge = await injector.get(ChargesProvider).getChargeByIdLoader.load(record.chargeId);
                    if (!charge) {
                        throw new Error(`Charge ID='${record.chargeId}' not found`);
                    }
                }
                const { ownerId } = await injector.get(AdminContextProvider).getVerifiedAdminContext();
                const newDocument = {
                    ownerId: charge?.owner_id ?? ownerId,
                    image: record.image ? record.image.toString() : null,
                    file: record.file ? record.file.toString() : null,
                    documentType: record.documentType ?? DocumentType.Unprocessed,
                    serialNumber: record.serialNumber ?? null,
                    date: record.date ? new Date(record.date) : null,
                    amount: record.amount?.raw ?? null,
                    currencyCode: record.amount?.currency ?? null,
                    vat: record.vat?.raw ?? null,
                    chargeId: record.chargeId ?? null,
                    vatReportDateOverride: record.vatReportDateOverride
                        ? new Date(record.vatReportDateOverride)
                        : null,
                    noVatAmount: record.noVatAmount ?? null,
                    creditorId: record.creditorId ?? null,
                    debtorId: record.debtorId ?? null,
                    allocationNumber: record.allocationNumber ?? null,
                    exchangeRateOverride: record.exchangeRateOverride ?? null,
                    fileHash: null,
                    description: record.description ?? null,
                    remarks: record.remarks ?? null,
                };
                const res = await injector
                    .get(DocumentsProvider)
                    .insertDocuments({ documents: [newDocument] });
                if (!res || res.length === 0) {
                    throw new Error(`Failed to insert ledger record to charge ID='${record.chargeId}'`);
                }
                return { document: res[0] };
            }
            catch (e) {
                return {
                    __typename: 'CommonError',
                    message: `Error inserting new ledger record:\n  ${e?.message ?? 'Unknown error'}`,
                };
            }
        },
        closeDocument: async (_, { id }, { injector }) => {
            try {
                const issuedDocument = await injector
                    .get(IssuedDocumentsProvider)
                    .getIssuedDocumentsByIdLoader.load(id);
                if (!issuedDocument) {
                    throw new GraphQLError(`Issued document ID="${id}" not found. Are you sure it exists?`);
                }
                if (!issuedDocument.external_id) {
                    throw new GraphQLError(`Issued document ID="${id}" has no external ID. Cannot close it.`);
                }
                const res = await injector
                    .get(GreenInvoiceClientProvider)
                    .closeDocument(issuedDocument.external_id);
                if (res) {
                    // update document's status
                    await injector
                        .get(IssuedDocumentsProvider)
                        .updateIssuedDocument({
                        documentId: id,
                        status: 'CLOSED',
                    })
                        .catch(e => {
                        const message = `Failed to update document's status (ID ${id})`;
                        console.error(`${message}: ${e}`);
                        if (e instanceof GraphQLError) {
                            throw e;
                        }
                        throw new GraphQLError(message);
                    });
                    return true;
                }
                throw new GraphQLError(`Failed to close document ID="${id}"`);
            }
            catch (e) {
                console.error(`Failed to close document ID="${id}":`, e);
                throw new GraphQLError(`Failed to close document ID="${id}"`);
            }
        },
    },
    Document: {
        __resolveType: (documentRoot, _context, _info) => {
            switch (documentRoot?.type) {
                case DocumentType.Invoice: {
                    return 'Invoice';
                }
                case DocumentType.Receipt: {
                    return 'Receipt';
                }
                case DocumentType.InvoiceReceipt: {
                    return 'InvoiceReceipt';
                }
                case DocumentType.CreditInvoice: {
                    return 'CreditInvoice';
                }
                case DocumentType.Proforma: {
                    return 'Proforma';
                }
                default: {
                    return 'Unprocessed';
                }
            }
        },
    },
    FinancialDocument: {
        __resolveType: (documentRoot, _context, _info) => {
            switch (documentRoot?.type) {
                case DocumentType.Invoice: {
                    return 'Invoice';
                }
                case DocumentType.Receipt: {
                    return 'Receipt';
                }
                case DocumentType.InvoiceReceipt: {
                    return 'InvoiceReceipt';
                }
                case DocumentType.CreditInvoice: {
                    return 'CreditInvoice';
                }
                case DocumentType.Proforma: {
                    return 'Proforma';
                }
                default: {
                    throw new GraphQLError(`FinancialDocument type "${documentRoot?.type}" is not supported`);
                }
            }
        },
    },
    UpdateDocumentResult: {
        __resolveType: (obj, _context, _info) => {
            if ('__typename' in obj && obj.__typename === 'CommonError')
                return 'CommonError';
            return 'UpdateDocumentSuccessfulResult';
        },
    },
    InsertDocumentResult: {
        __resolveType: (obj, _context, _info) => {
            if ('__typename' in obj && obj.__typename === 'CommonError')
                return 'CommonError';
            return 'InsertDocumentSuccessfulResult';
        },
    },
    UploadDocumentResult: {
        __resolveType: (obj, _context, _info) => {
            if ('__typename' in obj && obj.__typename === 'CommonError')
                return 'CommonError';
            return 'UploadDocumentSuccessfulResult';
        },
    },
    Invoice: {
        ...commonDocumentsFields,
        ...commonFinancialDocumentsFields,
    },
    InvoiceReceipt: {
        ...commonDocumentsFields,
        ...commonFinancialDocumentsFields,
    },
    CreditInvoice: {
        ...commonDocumentsFields,
        ...commonFinancialDocumentsFields,
    },
    Proforma: {
        ...commonDocumentsFields,
        ...commonFinancialDocumentsFields,
    },
    Unprocessed: {
        ...commonDocumentsFields,
    },
    OtherDocument: {
        ...commonDocumentsFields,
    },
    Receipt: {
        ...commonDocumentsFields,
        ...commonFinancialDocumentsFields,
    },
    CommonCharge: commonChargeFields,
    FinancialCharge: commonChargeFields,
    ConversionCharge: commonChargeFields,
    SalaryCharge: commonChargeFields,
    InternalTransferCharge: commonChargeFields,
    DividendCharge: commonChargeFields,
    BusinessTripCharge: commonChargeFields,
    MonthlyVatCharge: commonChargeFields,
    BankDepositCharge: commonChargeFields,
    ForeignSecuritiesCharge: commonChargeFields,
    CreditcardBankCharge: commonChargeFields,
};
//# sourceMappingURL=documents.resolver.js.map