import { GraphQLError } from 'graphql';
import { AdminContextProvider } from '../../admin-context/providers/admin-context.provider.js';
import { deleteCharges } from '../../charges/helpers/delete-charges.helper.js';
import { ChargesProvider } from '../../charges/providers/charges.provider.js';
import { IGetChargesByIdsResult } from '../../charges/types.js';
import { DocumentsProvider } from '../../documents/providers/documents.provider.js';
import { TransactionsProvider } from '../../transactions/providers/transactions.provider.js';
import {
  createDeelInvoiceMatchFromUnmatchedInvoice,
  fetchAndFilterInvoices,
  fetchReceipts,
  getChargeMatchesForPayments,
  getContractsFromPaymentBreakdowns,
  getDeelChargeDescription,
  insertDeelInvoiceRecord,
  matchInvoicesWithPayments,
  validateContracts,
} from '../helpers/deel.helper.js';
import { DeelContractsProvider } from '../providers/deel-contracts.provider.js';
import type { DeelModule } from '../types.js';

export const deelResolvers: DeelModule.Resolvers = {
  Mutation: {
    addDeelContract: async (
      _,
      { contractId, contractorId, contractorName, contractStartDate, businessId },
      { injector },
    ) => {
      try {
        await injector.get(DeelContractsProvider).insertDeelContract({
          contractId,
          contractorId,
          contractorName,
          contractStartDate,
          businessId,
        });
        return true;
      } catch (error) {
        const message = `Error adding Deel contract [${contractId}]`;
        console.error(message, error);
        throw new GraphQLError(message);
      }
    },
    fetchDeelDocuments: async (_, __, { injector }) => {
      try {
        const invoices = await fetchAndFilterInvoices(injector);
        if (invoices.length === 0) {
          return [];
        }

        const receipts = await fetchReceipts(injector);

        const { matches, unmatched } = await matchInvoicesWithPayments(
          injector,
          invoices,
          receipts,
        );

        if (matches.length + unmatched.length <= 0) {
          return [];
        }

        // fetch contacts and validate
        const contractsInfo = getContractsFromPaymentBreakdowns(matches);
        await validateContracts(contractsInfo, injector);

        const { receiptChargeMap, invoiceChargeMap, newReceipts } =
          await getChargeMatchesForPayments(injector, receipts);

        const updatedChargeIdsSet = new Set<string>();

        const { ownerId } = await injector.get(AdminContextProvider).getVerifiedAdminContext();

        // insert/update unmatched Deel invoice records
        for (const invoice of unmatched) {
          if (invoiceChargeMap.has(invoice.id)) {
            console.log('Found missing match for invoice via invoiceChargeMap:', invoice.id);
          }

          const charge = await injector.get(ChargesProvider).generateCharge({
            ownerId,
            userDescription: `Deel invoice ${invoice.label}`,
          });

          updatedChargeIdsSet.add(charge.id);

          const match = createDeelInvoiceMatchFromUnmatchedInvoice(invoice);

          await insertDeelInvoiceRecord(injector, match, charge.id);
        }

        // insert/update matched Deel invoice records
        for (const match of matches) {
          let chargeId =
            invoiceChargeMap.get(match.id) ?? receiptChargeMap.get(match.breakdown_receipt_id);

          // if no charge found, create one from receipt
          if (!chargeId) {
            const receipt = newReceipts.find(r => r.id === match.breakdown_receipt_id);
            if (receipt) {
              const description = await getDeelChargeDescription(injector, receipt.workers);
              const charge = await injector.get(ChargesProvider).generateCharge({
                ownerId,
                userDescription: description,
              });
              chargeId = charge.id;

              // TODO: upload receipt file (currently not available from Deel API)
            }
          }

          if (!chargeId) {
            throw new Error('Charge not found for invoice');
          }

          updatedChargeIdsSet.add(chargeId);

          await insertDeelInvoiceRecord(injector, match, chargeId);
        }

        // fetch charges, clean empty ones
        const charges: IGetChargesByIdsResult[] = [];
        await Promise.all(
          Array.from(updatedChargeIdsSet).map(async chargeId => {
            const [charge, transactions, documents] = await Promise.all([
              injector.get(ChargesProvider).getChargeByIdLoader.load(chargeId),
              injector.get(TransactionsProvider).transactionsByChargeIDLoader.load(chargeId),
              injector.get(DocumentsProvider).getDocumentsByChargeIdLoader.load(chargeId),
            ]);
            if (!charge) {
              return;
            }
            if (documents.length === 0 && transactions.length === 0) {
              await deleteCharges([charge.id], injector);
            } else {
              charges.push(charge);
            }
          }),
        );
        return charges;
      } catch (error) {
        const message = 'Error fetching Deel documents';
        console.error(message, error);
        throw new GraphQLError(message);
      }
    },
  },
};
