import fetch from "node-fetch";
import { AlchemyAssetTransfer } from "../types";
import { normalizeRequiredChainType, parseChainStringMap } from "./ChainConfig";

interface TransferBatchRequest {
  chainType: string;
  toAddress: string;
  fromBlock: string;
  toBlock?: string;
}

interface RpcResponse {
  id: number;
  result?: {
    transfers?: AlchemyAssetTransfer[];
    pageKey?: string;
  };
  error?: {
    message?: string;
  };
}

export class AlchemyTransfersClient {
  private readonly rpcUrlsByChainType: Record<string, string>;

  constructor(rpcUrlsRaw?: string) {
    this.rpcUrlsByChainType = parseChainStringMap(rpcUrlsRaw, "ALCHEMY_RPC_URLS");
  }

  isEnabled(): boolean {
    return Object.keys(this.rpcUrlsByChainType).length > 0;
  }

  async fetchTransfersBatch(requests: TransferBatchRequest[]): Promise<Map<string, AlchemyAssetTransfer[]>> {
    const requestsByUrl = new Map<string, TransferBatchRequest[]>();
    for (const request of requests) {
      const normalizedChainType = this.normalizeChainType(request.chainType);
      const url = this.rpcUrlsByChainType[normalizedChainType];
      if (!url) {
        continue;
      }

      const existing = requestsByUrl.get(url) || [];
      existing.push({
        ...request,
        chainType: normalizedChainType,
      });
      requestsByUrl.set(url, existing);
    }

    const results = new Map<string, AlchemyAssetTransfer[]>();
    for (const [url, group] of requestsByUrl.entries()) {
      const rpcPayload = group.map((request, index) => ({
        jsonrpc: "2.0",
        id: index + 1,
        method: "alchemy_getAssetTransfers",
        params: [
          {
            fromBlock: request.fromBlock,
            toBlock: request.toBlock || "latest",
            toAddress: request.toAddress,
            category: ["external", "internal", "erc20"],
            withMetadata: true,
            excludeZeroValue: true,
            maxCount: "0x3e8",
          },
        ],
      }));

      const response = await fetch(url, {
        method: "POST",
        headers: { "content-type": "application/json" },
        body: JSON.stringify(rpcPayload),
      });

      if (!response.ok) {
        throw new Error(`Alchemy transfers request failed with status ${response.status}`);
      }

      const json = await response.json() as RpcResponse[];
      for (const rpcResult of json) {
        const request = group[rpcResult.id - 1];
        if (!request) {
          continue;
        }

        if (rpcResult.error) {
          throw new Error(rpcResult.error.message || "Alchemy transfers request failed");
        }

        const key = this.resultKey(request.chainType, request.toAddress);
        results.set(key, rpcResult.result?.transfers || []);
      }
    }

    return results;
  }

  async getBlockNumber(chainType: string): Promise<number> {
    const normalizedChainType = this.normalizeChainType(chainType);
    const url = this.rpcUrlsByChainType[normalizedChainType];
    if (!url) {
      throw new Error(`Missing Alchemy RPC URL for ${normalizedChainType}`);
    }

    const response = await fetch(url, {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({
        jsonrpc: "2.0",
        id: 1,
        method: "eth_blockNumber",
        params: [],
      }),
    });

    if (!response.ok) {
      throw new Error(`Alchemy block number request failed with status ${response.status}`);
    }

    const json = await response.json() as { result?: string; error?: { message?: string } };
    if (json.error || !json.result) {
      throw new Error(json.error?.message || `Failed to read latest block for ${normalizedChainType}`);
    }

    return Number.parseInt(json.result, 16);
  }

  resultKey(chainType: string, toAddress: string): string {
    return `${chainType}:${toAddress.toLowerCase()}`;
  }

  private normalizeChainType(chainType: string): string {
    return normalizeRequiredChainType(chainType);
  }
}