import { type RefetchOptions, useQueries } from "@tanstack/react-query";

import type { Token } from "@/types/index";
import { useConfig } from "@hooks/useConfig";
import { bungeeApiFetcher } from "@utils/fetcher";

export const useFetchTokens = ({ addresses }: { addresses?: string[] }) => {
  const { apiKey, baseUrl } = useConfig();

  let _addresses = addresses;

  if (!_addresses || !_addresses.length) {
    _addresses = [""];
  }

  const { tokensWithBalances, ...rest } = useQueries({
    queries: _addresses.map((address) => ({
      queryKey: ["bungee-supported-tokens", apiKey, address],
      queryFn: () => fetchTokens({ address, baseUrl, apiKey }),
      refetchOnWindowFocus: false,
      retry: 2,
      staleTime: 20000,
    })),
    combine: (queries) => {
      return {
        error: queries.find((d) => d.isError)?.error.message,
        isError: queries.some((d) => d.isError),
        isLoading: queries.some((d) => d.isLoading),
        tokenBalances: queries.some((d) => !d.data)
          ? []
          : queries.reduce<Token[]>((acc, d) => {
              const tokens = Object.values(d.data?.result || {});
              for (const tokenArray of tokens) {
                acc.push(...tokenArray);
              }
              return acc;
            }, []),
        walletBalances: queries.some((d) => !d.data)
          ? []
          : queries.map((d) => ({
              wallet: d.data?.address,
              tokenBalances: d.data?.result || [],
            })),
        tokensWithBalances: queries.every((d) => d.data)
          ? mergeTokenItems(queries.map((d) => d.data?.result as TokenRecord))
          : undefined,
        refetch: (options?: RefetchOptions) =>
          Promise.all(queries.map((d) => d.refetch(options))),
      };
    },
  });

  return {
    data: tokensWithBalances,
    ...rest,
  };
};

type TokenRecord = Record<number, Token[]>;

export function mergeTokenItems(items: TokenRecord[]): TokenRecord {
  const merged: TokenRecord = {};

  // Iterate through each item (TokenRecord)
  for (const item of items) {
    // Iterate through each chainId in the current item
    for (const chainId of Object.keys(item)) {
      const chainIdNum = Number(chainId);

      // Initialize array for this chainId if it doesn't exist
      if (!merged[chainIdNum]) {
        merged[chainIdNum] = [];
      }

      // Process tokens for this chainId
      for (const token of item[chainIdNum]) {
        // Check for duplicates based on chainId + address combination
        const existingIndex: number = merged[chainIdNum].findIndex(
          (existingToken: Token) =>
            existingToken.address.toLowerCase() === token.address.toLowerCase()
        );

        if (existingIndex === -1) {
          // Token doesn't exist, add it
          merged[chainIdNum].push(token);
        } else {
          // Token exists, keep the one with higher balance for deduplication
          const existingToken: Token = merged[chainIdNum][existingIndex];
          const existingBalanceUsd: number = existingToken.balanceInUsd || 0;
          const newBalanceUsd: number = token.balanceInUsd || 0;

          // Replace with token that has higher USD balance
          // If both have same balance, keep the existing one (first come, first served)
          if (newBalanceUsd > existingBalanceUsd) {
            merged[chainIdNum][existingIndex] = token;
          }
        }
      }
    }
  }

  return merged;
}

export async function fetchTokens({
  address,
  baseUrl,
  apiKey,
}: {
  address: string;
  baseUrl: string | undefined;
  apiKey: string | undefined;
}) {
  if (!baseUrl || !apiKey) {
    return { result: {}, address };
  }

  let query = `${baseUrl}/tokens/list`;
  if (address) query += `?userAddress=${address}`;
  const response = await bungeeApiFetcher<{
    result: Record<number, Token[]>;
  }>(query, apiKey);

  return { result: response.result, address };
}
