import { normalizeRequiredChainType, parseChainStringMap } from "../services/ChainConfig";
import { DepositEvent } from "../types";
import { DepositScanRequest, IDepositScannerPort } from "./ProviderPorts";

export class MultiProviderDepositScanner implements IDepositScannerPort {
  private readonly scannersByProvider: Record<string, IDepositScannerPort>;
  private readonly providerByChainType: Record<string, string>;
  private readonly defaultProvider: string;

  constructor(
    scannersByProvider: Record<string, IDepositScannerPort>,
    providerByChainTypeRaw: string | undefined,
    defaultProvider: string = "alchemy"
  ) {
    this.scannersByProvider = Object.entries(scannersByProvider).reduce<Record<string, IDepositScannerPort>>(
      (accumulator, [provider, scanner]) => {
        const key = provider.trim().toLowerCase();
        if (key && scanner) {
          accumulator[key] = scanner;
        }
        return accumulator;
      },
      {}
    );

    this.providerByChainType = Object.entries(
      parseChainStringMap(providerByChainTypeRaw, "DEPOSIT_SCANNER_PROVIDER_BY_CHAIN")
    ).reduce<Record<string, string>>((accumulator, [chainType, provider]) => {
      accumulator[chainType] = provider.toLowerCase();
      return accumulator;
    }, {});

    this.defaultProvider = String(defaultProvider || "alchemy").trim().toLowerCase();
  }

  isEnabled(): boolean {
    return Object.values(this.scannersByProvider).some((scanner) => scanner.isEnabled());
  }

  async scanDeposits(requests: DepositScanRequest[]): Promise<Map<string, DepositEvent[]>> {
    const requestsByProvider = new Map<string, DepositScanRequest[]>();
    for (const request of requests) {
      const provider = this.resolveProvider(request.chainType);
      const existing = requestsByProvider.get(provider) || [];
      existing.push(request);
      requestsByProvider.set(provider, existing);
    }

    const result = new Map<string, DepositEvent[]>();
    for (const [provider, providerRequests] of requestsByProvider.entries()) {
      const scanner = this.scannersByProvider[provider];
      if (!scanner) {
        throw new Error(`No deposit scanner configured for provider '${provider}'`);
      }
      if (!scanner.isEnabled()) {
        throw new Error(`Deposit scanner provider '${provider}' is configured but not enabled`);
      }

      const providerResults = await scanner.scanDeposits(providerRequests);
      for (const request of providerRequests) {
        const providerKey = scanner.resultKey(request.chainType, request.address);
        const aggregateKey = this.resultKey(request.chainType, request.address);
        result.set(aggregateKey, providerResults.get(providerKey) || []);
      }
    }

    return result;
  }

  getBlockNumber(chainType: string): Promise<number> {
    const provider = this.resolveProvider(chainType);
    const scanner = this.scannersByProvider[provider];
    if (!scanner) {
      throw new Error(`No deposit scanner configured for provider '${provider}'`);
    }
    return scanner.getBlockNumber(chainType);
  }

  resultKey(chainType: string, address: string): string {
    const normalizedChainType = normalizeRequiredChainType(chainType);
    const normalizedAddress = this.normalizeAddress(address);
    return `${normalizedChainType}:${normalizedAddress}`;
  }

  private resolveProvider(chainType: string): string {
    const normalizedChainType = normalizeRequiredChainType(chainType);
    const provider = this.providerByChainType[normalizedChainType] || this.defaultProvider;
    return provider.toLowerCase();
  }

  private normalizeAddress(address: string): string {
    const trimmed = String(address || "").trim();
    if (trimmed.startsWith("0x") || trimmed.startsWith("0X")) {
      return trimmed.toLowerCase();
    }
    return trimmed;
  }
}
