import type { Balance } from "@ledgerhq/coin-module-framework/api/types";
import { getCryptoAssetsStore } from "@ledgerhq/cryptoassets/state";
import { LedgerAPI4xx } from "@ledgerhq/errors";
import { promiseAllBatched } from "@ledgerhq/live-promise";
import BigNumber from "bignumber.js";
import { type HederaCoinConfig } from "../config";
import { HederaAddAccountError } from "../errors";
import { resolveConfig } from "./utils";
import { apiClient } from "../network/api";
import { getERC20BalancesForAccountV2 } from "../network/utils";

export async function getBalance({
  config,
  currencyId,
  address,
}: {
  config?: HederaCoinConfig;
  currencyId: string;
  address: string;
}): Promise<Balance[]> {
  const coinConfig = resolveConfig(config ?? currencyId);

  try {
    // Fetch only the specific staked node (or nothing at all for non-staking
    // accounts) instead of paginating the full /network/nodes list. The
    // validator lookup is chained on the account promise so it still runs
    // concurrently with the token fetches.
    const mirrorAccountPromise = apiClient.getAccount({ configOrCurrencyId: coinConfig, address });
    const validatorPromise = mirrorAccountPromise.then(account =>
      typeof account.staked_node_id === "number" && account.staked_node_id >= 0
        ? apiClient.getNode({ configOrCurrencyId: coinConfig, nodeId: account.staked_node_id })
        : null,
    );
    const [mirrorAccount, mirrorTokens, erc20TokenBalances, validator] = await Promise.all([
      mirrorAccountPromise,
      apiClient.getAccountTokens({ configOrCurrencyId: coinConfig, address }),
      getERC20BalancesForAccountV2({ configOrCurrencyId: coinConfig, address }),
      validatorPromise,
    ]);

    const mixedTokens = [...mirrorTokens, ...erc20TokenBalances];

    const nativeBalance: Balance = {
      asset: { type: "native" },
      value: BigInt(mirrorAccount.balance.balance),
      ...(validator && {
        stake: {
          uid: address,
          address,
          asset: { type: "native" },
          state: "active",
          amount: BigInt(mirrorAccount.balance.balance) + BigInt(mirrorAccount.pending_reward),
          amountDeposited: BigInt(mirrorAccount.balance.balance),
          amountRewarded: BigInt(mirrorAccount.pending_reward),
          delegate: validator.node_account_id,
          actions: [],
          details: {
            overstaked: BigInt(validator.stake) >= BigInt(validator.max_stake),
          },
        },
      }),
    };

    const tokenBalances = await promiseAllBatched(
      3,
      mixedTokens,
      async (item): Promise<Balance | null> => {
        const tokenAddress = "token_id" in item ? item.token_id : item.token.contractAddress;
        const calToken = await getCryptoAssetsStore().findTokenByAddressInCurrency(
          tokenAddress,
          currencyId,
        );

        if (!calToken || !calToken.units.length) {
          return null;
        }

        const roundedValue = new BigNumber(item.balance).toFixed(0);

        return {
          value: BigInt(roundedValue),
          asset: {
            type: calToken.tokenType,
            assetReference: calToken.contractAddress,
            assetOwner: address,
            name: calToken.name,
            unit: calToken.units[0],
          },
        };
      },
    );

    const balances: Balance[] = [nativeBalance, ...tokenBalances].filter(
      (balance): balance is Balance => balance !== null,
    );

    return balances;
  } catch (err) {
    const isNonExistentAccount =
      err instanceof HederaAddAccountError || (err instanceof LedgerAPI4xx && err.status === 404);

    if (isNonExistentAccount) {
      return [];
    }

    throw err;
  }
}
