import type {
  QueryBusinessTransactionsSumFromLedgerRecordsArgs,
  ResolverFn,
  ResolversTypes,
} from '../../../__generated__/types.js';
import { Currency } from '../../../shared/enums.js';
import { dateToTimelessDateString } from '../../../shared/helpers/index.js';
import { RawBusinessTransactionsSum } from '../../../shared/types/index.js';
import { AdminContextProvider } from '../../admin-context/providers/admin-context.provider.js';
import { ChargesProvider } from '../../charges/providers/charges.provider.js';
import { LedgerProvider } from '../../ledger/providers/ledger.provider.js';
import { BANK_DEPOSITS_REVALUATION_LEDGER_DESCRIPTION } from '../../ledger/resolvers/ledger-generation/financial-ledger-generation/bank-deposits-revaluation-ledger-generation.resolver.js';
import { REVALUATION_LEDGER_DESCRIPTION } from '../../ledger/resolvers/ledger-generation/financial-ledger-generation/revaluation-ledger-generation.resolver.js';
import { handleBusinessLedgerRecord } from '../helpers/business-transactions.helper.js';
import { FinancialEntitiesProvider } from '../providers/financial-entities.provider.js';
import type { IGetFinancialEntitiesByIdsResult } from '../types.js';

export const businessTransactionsSumFromLedgerRecords: ResolverFn<
  ResolversTypes['BusinessTransactionsSumFromLedgerRecordsResult'],
  object,
  GraphQLModules.Context,
  Partial<QueryBusinessTransactionsSumFromLedgerRecordsArgs>
> = async (_, { filters }, { injector }, _info) => {
  const {
    ownerIds,
    businessIDs,
    fromDate,
    toDate,
    type,
    includeRevaluation = true,
  } = filters || {};
  const isFilteredByFinancialEntities = !!businessIDs?.length || type;

  try {
    const financialEntitiesIDsPromise = async () => {
      try {
        const shouldFetchAllFinancialEntities = !businessIDs?.length && !!type;
        const financialEntities = await (shouldFetchAllFinancialEntities
          ? injector.get(FinancialEntitiesProvider).getAllFinancialEntities()
          : injector
              .get(FinancialEntitiesProvider)
              .getFinancialEntityByIdLoader.loadMany(businessIDs ?? []));

        const financialEntitiesIDs = financialEntities
          ?.filter(fe => {
            if (!fe || !('id' in fe)) {
              return false;
            }
            if (type) {
              return type.toLocaleLowerCase() === fe.type;
            }
            return true;
          })
          .map(fe => (fe as IGetFinancialEntitiesByIdsResult).id);

        return financialEntitiesIDs;
      } catch (e) {
        const message = 'Error fetching financial entities';
        console.error(`${message}: ${e}`);
        throw new Error(message, { cause: e });
      }
    };

    const ledgerRecordsPromise = async () => {
      try {
        const charges = await injector.get(ChargesProvider).getChargesByFilters({
          ownerIds: ownerIds ?? undefined,
          businessIds: businessIDs,
          fromAnyDate: fromDate,
          toAnyDate: toDate,
        });
        const ledgerRecordSets = await Promise.all(
          charges.map(charge =>
            injector.get(LedgerProvider).getLedgerRecordsByChargesIdLoader.load(charge.id),
          ),
        );

        const ledgerRecords = ledgerRecordSets.flat();

        return ledgerRecords;
      } catch (e) {
        const message = 'Error fetching ledger records';
        console.error(`${message}: ${e}`);
        throw new Error(message, { cause: e });
      }
    };

    const [{ defaultLocalCurrency }, financialEntitiesIDs, ledgerRecords] = await Promise.all([
      injector.get(AdminContextProvider).getVerifiedAdminContext(),
      financialEntitiesIDsPromise(),
      ledgerRecordsPromise(),
    ]);

    const rawRes: Record<string, RawBusinessTransactionsSum> = {};

    for (const ledger of ledgerRecords) {
      // re-filter ledger records by date (to prevent charge's out-of-range dates from affecting the sum)
      if (!!fromDate && dateToTimelessDateString(ledger.invoice_date) < fromDate) {
        continue;
      }
      if (!!toDate && dateToTimelessDateString(ledger.invoice_date) > toDate) {
        continue;
      }

      // if shouldn't include revaluation ledger, skip records of toDate filter (/today)
      if (
        !includeRevaluation &&
        (ledger.description?.includes(REVALUATION_LEDGER_DESCRIPTION) ||
          ledger.description?.includes(BANK_DEPOSITS_REVALUATION_LEDGER_DESCRIPTION)) &&
        (toDate || dateToTimelessDateString(new Date())) ===
          dateToTimelessDateString(ledger.invoice_date)
      ) {
        continue;
      }

      if (
        ledger.credit_entity1 &&
        (!isFilteredByFinancialEntities || financialEntitiesIDs.includes(ledger.credit_entity1))
      ) {
        handleBusinessLedgerRecord(
          rawRes,
          ledger.credit_entity1,
          ledger.currency as Currency,
          true,
          ledger.credit_local_amount1,
          ledger.credit_foreign_amount1,
          defaultLocalCurrency,
        );
      }

      if (
        ledger.credit_entity2 &&
        (!isFilteredByFinancialEntities || financialEntitiesIDs.includes(ledger.credit_entity2))
      ) {
        handleBusinessLedgerRecord(
          rawRes,
          ledger.credit_entity2,
          ledger.currency as Currency,
          true,
          ledger.credit_local_amount2,
          ledger.credit_foreign_amount2,
          defaultLocalCurrency,
        );
      }

      if (
        ledger.debit_entity1 &&
        (!isFilteredByFinancialEntities || financialEntitiesIDs.includes(ledger.debit_entity1))
      ) {
        handleBusinessLedgerRecord(
          rawRes,
          ledger.debit_entity1,
          ledger.currency as Currency,
          false,
          ledger.debit_local_amount1,
          ledger.debit_foreign_amount1,
          defaultLocalCurrency,
        );
      }

      if (
        ledger.debit_entity2 &&
        (!isFilteredByFinancialEntities || financialEntitiesIDs.includes(ledger.debit_entity2))
      ) {
        handleBusinessLedgerRecord(
          rawRes,
          ledger.debit_entity2,
          ledger.currency as Currency,
          false,
          ledger.debit_local_amount2,
          ledger.debit_foreign_amount2,
          defaultLocalCurrency,
        );
      }
    }

    return {
      __typename: 'BusinessTransactionsSumFromLedgerRecordsSuccessfulResult',
      businessTransactionsSum: Object.values(rawRes),
    };
  } catch (e) {
    console.error(e);
    return {
      __typename: 'CommonError',
      message: 'Error fetching business transactions summary from ledger records',
    };
  }
};
