import type { Injector } from 'graphql-modules';
import type { Pool } from 'pg';
import { AdminContextProvider } from '../modules/admin-context/providers/admin-context.provider.js';
import { CoinMarketCapProvider } from '../modules/app-providers/coinmarketcap.js';
import { DBProvider } from '../modules/app-providers/db.provider.js';
import { TenantAwareDBClient } from '../modules/app-providers/tenant-db-client.js';
import { AuthContextProvider } from '../modules/auth/providers/auth-context.provider.js';
import { BusinessTripsProvider } from '../modules/business-trips/providers/business-trips.provider.js';
import { ChargeSpreadProvider } from '../modules/charges/providers/charge-spread.provider.js';
import { ChargesAuthorizationProvider } from '../modules/charges/providers/charges-authorization.provider.js';
import { ChargesProvider } from '../modules/charges/providers/charges.provider.js';
import { DocumentsProvider } from '../modules/documents/providers/documents.provider.js';
import { CryptoExchangeProvider } from '../modules/exchange-rates/providers/crypto-exchange.provider.js';
import { ExchangeProvider } from '../modules/exchange-rates/providers/exchange.provider.js';
import { FiatExchangeProvider } from '../modules/exchange-rates/providers/fiat-exchange.provider.js';
import { FinancialAccountsProvider } from '../modules/financial-accounts/providers/financial-accounts.provider.js';
import { BusinessesOperationProvider } from '../modules/financial-entities/providers/businesses-operation.provider.js';
import { BusinessesProvider } from '../modules/financial-entities/providers/businesses.provider.js';
import { FinancialEntitiesProvider } from '../modules/financial-entities/providers/financial-entities.provider.js';
import { TaxCategoriesProvider } from '../modules/financial-entities/providers/tax-categories.provider.js';
import { BalanceCancellationProvider } from '../modules/ledger/providers/balance-cancellation.provider.js';
import { LedgerProvider } from '../modules/ledger/providers/ledger.provider.js';
import { UnbalancedBusinessesProvider } from '../modules/ledger/providers/unbalanced-businesses.provider.js';
import { MiscExpensesProvider } from '../modules/misc-expenses/providers/misc-expenses.provider.js';
import { TransactionsProvider } from '../modules/transactions/providers/transactions.provider.js';
import { VatProvider } from '../modules/vat/providers/vat.provider.js';
import type { Currency } from '../shared/enums.js';

export type ModuleContextLike = {
  injector: Injector;
  // Keep optional fields for compatibility with resolvers/helpers
  moduleId: string;
  env?: unknown;
};

class SimpleInjector implements Injector {
  private instances = new Map<unknown, unknown>();

  constructor(private factory: (token: unknown) => unknown) {}

  get<T>(token: unknown): T {
    if (this.instances.has(token)) return this.instances.get(token) as T;
    const instance = this.factory(token);
    this.instances.set(token, instance);
    return instance as T;
  }
}

export type ExchangeRateMockFn = (
  baseCurrency: Currency,
  quoteCurrency: Currency,
  date: Date,
) => Promise<number>;

export function createLedgerTestContext(options: {
  pool: Pool;
  moduleId?: string;
  env?: unknown;
  businessId: string;
  /**
   * Optional mock function to override ExchangeProvider.getExchangeRates.
   * Use this to fix exchange rates for deterministic tests.
   * @see createMockExchangeRates, mockExchangeRate from __tests__/helpers/exchange-mock.ts
   */
  mockExchangeRates?: ExchangeRateMockFn;
}): ModuleContextLike {
  const { pool, moduleId = 'test', env, businessId, mockExchangeRates } = options;

  const resolvedBusinessId = businessId;
  if (!resolvedBusinessId) {
    throw new Error('createLedgerTestContext requires businessId for auth context');
  }

  const dbProvider = new DBProvider(pool);

  // Common context object shared between injector and TenantAwareDBClient
  const context = {
    injector: undefined as unknown as Injector,
    moduleId,
    env,
  } as ModuleContextLike;

  // Create minimal AuthContextProvider for providers that need it
  const authContextProvider = {
    getAuthContext: async () => ({
      authType: null,
      tenant: { businessId: resolvedBusinessId },
    }),
  } as AuthContextProvider;

  const tenantAwareDB = new TenantAwareDBClient(dbProvider, authContextProvider);

  // Create AdminContextProvider for providers that need it
  const adminContextProvider = new AdminContextProvider(authContextProvider, tenantAwareDB);

  const injector = new SimpleInjector(token => {
    switch (token) {
      case DBProvider:
        return dbProvider;
      case AdminContextProvider:
        return adminContextProvider;
      case CoinMarketCapProvider:
        return new CoinMarketCapProvider();
      case FiatExchangeProvider:
        return new FiatExchangeProvider(dbProvider);
      case CryptoExchangeProvider:
        return new CryptoExchangeProvider(
          tenantAwareDB,
          adminContextProvider,
          new CoinMarketCapProvider(),
        );
      case ExchangeProvider: {
        const crypto = new CryptoExchangeProvider(
          tenantAwareDB,
          adminContextProvider,
          new CoinMarketCapProvider(),
        );
        const fiat = new FiatExchangeProvider(dbProvider);
        const exchangeProvider = new ExchangeProvider(crypto, fiat, adminContextProvider);
        // Apply mock if provided
        if (mockExchangeRates) {
          exchangeProvider.getExchangeRates = mockExchangeRates;
        }
        return exchangeProvider;
      }
      case LedgerProvider:
        return new LedgerProvider(tenantAwareDB, adminContextProvider);
      case UnbalancedBusinessesProvider:
        return new UnbalancedBusinessesProvider(tenantAwareDB, adminContextProvider);
      case BalanceCancellationProvider:
        return new BalanceCancellationProvider(tenantAwareDB);
      case MiscExpensesProvider:
        return new MiscExpensesProvider(tenantAwareDB, adminContextProvider);
      case DocumentsProvider:
        return new DocumentsProvider(tenantAwareDB, adminContextProvider);
      case TransactionsProvider:
        return new TransactionsProvider(tenantAwareDB);
      case BusinessesProvider:
        return new BusinessesProvider(tenantAwareDB, adminContextProvider);
      case FinancialAccountsProvider:
        return new FinancialAccountsProvider(tenantAwareDB, adminContextProvider);
      case TaxCategoriesProvider:
        return new TaxCategoriesProvider(tenantAwareDB, adminContextProvider);
      case ChargesProvider: {
        const chargesAuthorizationProvider = new ChargesAuthorizationProvider(
          authContextProvider,
          tenantAwareDB,
        );
        return new ChargesProvider(tenantAwareDB, chargesAuthorizationProvider);
      }
      case VatProvider:
        return new VatProvider(dbProvider);
      case FinancialEntitiesProvider: {
        const businessesProvider = new BusinessesProvider(tenantAwareDB, adminContextProvider);
        const taxCategoriesProvider = new TaxCategoriesProvider(
          tenantAwareDB,
          adminContextProvider,
        );
        const businessesOperationStub: Pick<BusinessesOperationProvider, 'deleteBusinessById'> = {
          deleteBusinessById: async (_businessId: string) => {},
        };
        return new FinancialEntitiesProvider(
          tenantAwareDB,
          businessesProvider,
          businessesOperationStub as unknown as BusinessesOperationProvider,
          taxCategoriesProvider,
        );
      }
      case BusinessTripsProvider:
        return new BusinessTripsProvider(tenantAwareDB, adminContextProvider);
      case ChargeSpreadProvider:
        return new ChargeSpreadProvider(tenantAwareDB, adminContextProvider);
      default:
        throw new Error(
          `Unsupported provider requested by injector: ${
            (token as { name?: string })?.name ?? String(token)
          }`,
        );
    }
  });

  context.injector = injector;
  return context;
}
