import { AlchemyTransfersClient } from "../../services/AlchemyTransfersClient";
import { normalizeEvmAmount } from "../../services/AssetAmountNormalizer";
import { IMappingStore } from "../../services/StoreContracts";
import { AlchemyAssetTransfer, DepositEvent } from "../../types";
import { DepositScanRequest, IDepositScannerPort } from "../ProviderPorts";

type NormalizeResult =
  | { reason: "ignored" | "unsupported" }
  | { reason: "accepted"; event: DepositEvent };

/**
 * QuickNode-compatible scanner adapter.
 * Uses the same underlying transfer client as Alchemy but marks events with
 * a distinct provider identity. In production, this would use QuickNode-specific
 * APIs and filtering logic.
 */
export class QuickNodeDepositScannerAdapter implements IDepositScannerPort {
  private readonly supportedCategories = new Set(["external", "internal", "erc20"]);

  constructor(
    private readonly transfersClient: AlchemyTransfersClient,
    private readonly mappingStoreService: IMappingStore
  ) {}

  isEnabled(): boolean {
    return this.transfersClient.isEnabled();
  }

  async scanDeposits(requests: DepositScanRequest[]): Promise<Map<string, DepositEvent[]>> {
    const batchRequests = requests.map((request) => ({
      chainType: request.chainType,
      toAddress: request.address,
      fromBlock: request.fromBlock,
    }));

    const transferResults = await this.transfersClient.fetchTransfersBatch(batchRequests);
    const result = new Map<string, DepositEvent[]>();

    for (const request of requests) {
      const key = this.resultKey(request.chainType, request.address);
      const transfers = transferResults.get(key) || [];
      const events: DepositEvent[] = [];

      for (const transfer of transfers) {
        const normalized = await this.normalizeTransfer(request.chainType, request.address, transfer);
        if (normalized.reason === "accepted") {
          events.push(normalized.event);
        }
      }

      result.set(key, events);
    }

    return result;
  }

  getBlockNumber(chainType: string): Promise<number> {
    return this.transfersClient.getBlockNumber(chainType);
  }

  resultKey(chainType: string, address: string): string {
    return this.transfersClient.resultKey(chainType, address);
  }

  private async normalizeTransfer(
    chainType: string,
    requestedAddress: string,
    transfer: AlchemyAssetTransfer
  ): Promise<NormalizeResult> {
    const category = transfer.category?.toLowerCase();
    if (!category || !this.supportedCategories.has(category)) {
      return { reason: "unsupported" };
    }

    if (transfer.erc721TokenId || (transfer.erc1155Metadata && transfer.erc1155Metadata.length > 0)) {
      return { reason: "unsupported" };
    }

    const recipient = transfer.to?.trim();
    const txHash = transfer.hash?.trim();
    if (!recipient || !txHash || recipient.toLowerCase() !== requestedAddress.toLowerCase()) {
      return { reason: "ignored" };
    }

    const mapping = await this.mappingStoreService.getByTrackedAddress(recipient);
    if (!mapping) {
      return { reason: "ignored" };
    }

    const amount = this.resolveAmount(transfer);
    if (!amount) {
      return { reason: "ignored" };
    }

    const event = {
      source: "quicknode",
      status: "confirmed" as const,
      idempotency_key: txHash,
      user_id: mapping.userId,
      wallet_address: recipient,
      amount,
      asset: this.resolveAsset(transfer),
      tx_hash: txHash,
      block_number: this.hexToNumber(transfer.blockNum),
      log_index: this.resolveLogIndex(transfer),
      from_address: transfer.from || null,
      to_address: recipient,
      asset_address: transfer.rawContract?.address || null,
      chain_type: mapping.wallet.chain_type || chainType,
      network: mapping.wallet.network || null,
      wallet_id: mapping.wallet.wallet_id || null,
      organization_id: mapping.wallet.organization_id || null,
      branch_id: mapping.wallet.branch_id || null,
      terminal_id: mapping.wallet.terminal_id || null,
      payment_id: mapping.wallet.payment_id || null,
      supported_asset_id: mapping.wallet.supported_asset_id || null,
      derivation_index: mapping.wallet.derivation_index || null,
      occurred_at: transfer.metadata?.blockTimestamp || new Date().toISOString(),
      raw_payload: {
        provider: "quicknode",
        reconciliation: true,
        transfer,
      },
    };

    return {
      reason: "accepted",
      event,
    };
  }

  private resolveAmount(transfer: AlchemyAssetTransfer): string | null {
    return normalizeEvmAmount({
      rawValue: transfer.rawContract?.value,
      decimals: transfer.rawContract?.decimal,
      value: transfer.value,
      isNative: !transfer.rawContract?.address,
    });
  }

  private resolveAsset(transfer: AlchemyAssetTransfer): string {
    if (transfer.category === "erc20" && transfer.rawContract?.address) {
      return transfer.rawContract.address.toLowerCase();
    }

    if (transfer.rawContract?.address) {
      return transfer.rawContract.address.toLowerCase();
    }

    return "native";
  }

  private resolveLogIndex(transfer: AlchemyAssetTransfer): string | null {
    // Alchemy transfers don't have logIndex in the standard format
    // Use uniqueId as a proxy if available
    if (transfer.uniqueId) {
      return transfer.uniqueId;
    }
    return null;
  }

  private hexToNumber(hex: string | undefined): number {
    if (!hex || typeof hex !== "string") {
      return 0;
    }

    try {
      return parseInt(hex, 16);
    } catch {
      return 0;
    }
  }
}
