import { GraphQLError, GraphQLResolveInfo } from 'graphql';
import { Injector } from 'graphql-modules';
import type {
  CommonError,
  Maybe,
  ResolverFn,
  ResolversParentTypes,
  ResolversTypes,
  ResolverTypeWrapper,
} from '../../../__generated__/types.js';
import { ChargeTypeEnum } from '../../../shared/enums.js';
import { LedgerRecordsProto } from '../../../shared/types/index.js';
import { AdminContextProvider } from '../../admin-context/providers/admin-context.provider.js';
import { BusinessTripAttendeesProvider } from '../../business-trips/providers/business-trips-attendees.provider.js';
import { getChargeType } from '../../charges/helpers/charge-type.js';
import type { IGetChargesByIdsResult } from '../../charges/types.js';
import { LedgerProvider } from '../providers/ledger.provider.js';
import { UnbalancedBusinessesProvider } from '../providers/unbalanced-businesses.provider.js';
import { generateLedgerRecordsForBankDeposit } from '../resolvers/ledger-generation/bank-deposit-ledger-generation.resolver.js';
import { generateLedgerRecordsForBusinessTrip } from '../resolvers/ledger-generation/business-trip-ledger-generation.resolver.js';
import { generateLedgerRecordsForCommonCharge } from '../resolvers/ledger-generation/common-ledger-generation.resolver.js';
import { generateLedgerRecordsForConversion } from '../resolvers/ledger-generation/conversion-ledger-generation.resolver.js';
import { generateLedgerRecordsForDividend } from '../resolvers/ledger-generation/dividend-ledger-generation.resolver.js';
import { generateLedgerRecordsForFinancialCharge } from '../resolvers/ledger-generation/financial-ledger-generation.resolver.js';
import { generateLedgerRecordsForForeignSecurities } from '../resolvers/ledger-generation/foreign-securities-ledger-generation.resolver.js';
import { generateLedgerRecordsForInternalTransfer } from '../resolvers/ledger-generation/internal-transfer-ledger-generation.resolver.js';
import { generateLedgerRecordsForMonthlyVat } from '../resolvers/ledger-generation/monthly-vat-ledger-generation.resolver.js';
import { generateLedgerRecordsForSalary } from '../resolvers/ledger-generation/salary-ledger-generation.resolver.js';
import { isChargeLocked } from './ledger-lock.js';

const resolveLockedCharge: ResolverFn<
  Maybe<ResolversTypes['GeneratedLedgerRecords']>,
  ResolversParentTypes['Charge'],
  GraphQLModules.Context,
  object
> = async (charge, _, context, __) => {
  try {
    const records = await context.injector
      .get(LedgerProvider)
      .getLedgerRecordsByChargesIdLoader.load(charge.id);

    return {
      records,
      charge,
      balance: {
        isBalanced: true,
        unbalancedEntities: [],
        balanceSum: 0,
        financialEntities: [],
      },
      errors: [],
    };
  } catch (e) {
    console.error(e);
    throw new GraphQLError(`Error loading ledger records for charge ID="${charge.id}"`);
  }
};

export async function ledgerGenerationByCharge(
  charge: IGetChargesByIdsResult,
  params: {
    insertLedgerRecordsIfNotExists: boolean;
  },
  context: GraphQLModules.ModuleContext,
  info: GraphQLResolveInfo,
): Promise<Maybe<ResolverTypeWrapper<CommonError | LedgerRecordsProto>>> {
  const { injector } = context;
  const { ledgerLock } = await injector.get(AdminContextProvider).getVerifiedAdminContext();
  if (await isChargeLocked(charge, injector, ledgerLock)) {
    return resolveLockedCharge(charge, params, context, info);
  }
  const chargeType = await getChargeType(charge, injector);
  type LedgerGenFunction = ResolverFn<
    Maybe<ResolverTypeWrapper<CommonError | LedgerRecordsProto>>,
    IGetChargesByIdsResult,
    GraphQLModules.ModuleContext,
    {
      insertLedgerRecordsIfNotExists: boolean;
    }
  >;

  const ledgerGenerationMap: Record<ChargeTypeEnum, LedgerGenFunction> = {
    [ChargeTypeEnum.Common]: generateLedgerRecordsForCommonCharge,
    [ChargeTypeEnum.Conversion]: generateLedgerRecordsForConversion,
    [ChargeTypeEnum.Salary]: generateLedgerRecordsForSalary,
    [ChargeTypeEnum.InternalTransfer]: generateLedgerRecordsForInternalTransfer,
    [ChargeTypeEnum.Dividend]: generateLedgerRecordsForDividend,
    [ChargeTypeEnum.BusinessTrip]: generateLedgerRecordsForBusinessTrip,
    [ChargeTypeEnum.MonthlyVat]: generateLedgerRecordsForMonthlyVat,
    [ChargeTypeEnum.BankDeposit]: generateLedgerRecordsForBankDeposit,
    [ChargeTypeEnum.ForeignSecurities]: generateLedgerRecordsForForeignSecurities,
    [ChargeTypeEnum.CreditcardBankCharge]: generateLedgerRecordsForCommonCharge,
    [ChargeTypeEnum.Financial]: generateLedgerRecordsForFinancialCharge,
  };

  const generationFunction = ledgerGenerationMap[chargeType];

  if (generationFunction) {
    return generationFunction(charge, params, context, info);
  }

  throw new Error(`Unknown charge type: ${chargeType}`);
}

export async function ledgerUnbalancedBusinessesByCharge(
  charge: IGetChargesByIdsResult,
  injector: Injector,
): Promise<Set<string> | undefined> {
  const chargeType = await getChargeType(charge, injector);
  switch (chargeType) {
    case 'CommonCharge': {
      const unbalancedBusinesses = await injector
        .get(UnbalancedBusinessesProvider)
        .getChargeUnbalancedBusinessesByChargeIds.load(charge.id);

      const allowedUnbalancedBusinesses = new Set(
        unbalancedBusinesses.map(({ business_id }) => business_id),
      );
      return allowedUnbalancedBusinesses;
    }
    case 'ConversionCharge':
      return undefined;
    case 'SalaryCharge': {
      const unbalancedBusinesses = await injector
        .get(UnbalancedBusinessesProvider)
        .getChargeUnbalancedBusinessesByChargeIds.load(charge.id);

      const allowedUnbalancedBusinesses = new Set(
        unbalancedBusinesses.map(({ business_id }) => business_id),
      );
      return allowedUnbalancedBusinesses;
    }
    case 'InternalTransferCharge':
      return undefined;
    case 'DividendCharge':
      ///////////
      // TODO //
      //////////
      return undefined;
    case 'BusinessTripCharge': {
      const businessTripAttendees = await injector
        .get(BusinessTripAttendeesProvider)
        .getBusinessTripsAttendeesByChargeIdLoader.load(charge.id);

      const allowedUnbalancedBusinesses = new Set(
        businessTripAttendees.map(attendee => attendee.id),
      );
      return allowedUnbalancedBusinesses;
    }
    case 'MonthlyVatCharge':
      return undefined;
    case 'BankDepositCharge':
      throw new Error('Bank Deposit Charge is not supported yet');
    case 'ForeignSecuritiesCharge':
      throw new Error('Foreign Securities Charge is not supported yet');
    case 'CreditcardBankCharge':
      throw new Error('Credit Card Bank Charge is not supported yet');
    case 'FinancialCharge':
      throw new Error('Financial Charge is not supported yet');
    default:
      throw new Error(`Unknown charge type: ${chargeType}`);
  }
}
