import { cosmosclient, proto, rest } from "@cosmos-client/core";
import {
  Balance,
  FeeType,
  Fees,
  Network,
  TxHash,
  TxType,
  singleFee,
} from "../client";
import { CosmosSDKClient, TxLog } from "../cosmos";
import {
  Address,
  Asset,
  AssetDOJNative,
  BaseAmount,
  assetAmount,
  assetFromString,
  assetToBase,
  assetToString,
  baseAmount,
  isAssetDOJNative,
  isSynthAsset,
} from "@dojima-wallet/utils";
import axios from "axios";
import * as bech32Buffer from "bech32-buffer";
import Long from "long";

import {
  MsgCreateOperator,
  MsgRegisterChain,
  MsgNativeTx,
  MsgSetIpAddressTx,
  MsgSetPubkeysTx,
  MsgSetVersionTx,
  MsgCreateEndpoint,
} from "./messages";
import { hermes } from "./proto/MsgCompiled";
import { ChainId, ExplorerUrls, NodeInfoResponse, TxData } from "./types";

export const DOJ_DECIMAL = 8;
export const DEFAULT_GAS_ADJUSTMENT = 2;
export const DEFAULT_GAS_LIMIT_VALUE = "8000000";
export const DEPOSIT_GAS_LIMIT_VALUE = "600000000";
export const MAX_TX_COUNT = 100;

const DENOM_DOJ_NATIVE = "doj";

// const DEFAULT_MAINNET_EXPLORER_URL = "https://api-h4s.dojima.network";
// const DEFAULT_STAGENET_EXPLORER_URL = "https://api-h4s.dojima.network";
// const DEFAULT_TESTNET_EXPLORER_URL = "https://api-test-h4s.dojima.network";
// // const DEFAULT_TESTNET_EXPLORER_URL = "http://localhost:1317";
// // const txUrl = `${DEFAULT_EXPLORER_URL}/tx`;
// // const addressUrl = `${DEFAULT_EXPLORER_URL}/address`;
// export const defaultExplorerUrls: ExplorerUrls = {
//   root: {
//     [Network.Testnet]: `${DEFAULT_TESTNET_EXPLORER_URL}?network=testnet`,
//     [Network.Stagenet]: `${DEFAULT_STAGENET_EXPLORER_URL}?network=stagenet`,
//     [Network.Mainnet]: DEFAULT_MAINNET_EXPLORER_URL,
//   },
//   tx: {
//     [Network.Testnet]: `${DEFAULT_TESTNET_EXPLORER_URL}/tx`,
//     [Network.Stagenet]: `${DEFAULT_STAGENET_EXPLORER_URL}/tx`,
//     [Network.Mainnet]: `${DEFAULT_MAINNET_EXPLORER_URL}/tx`,
//   },
//   address: {
//     [Network.Testnet]: `${DEFAULT_TESTNET_EXPLORER_URL}/address`,
//     [Network.Stagenet]: `${DEFAULT_STAGENET_EXPLORER_URL}/address`,
//     [Network.Mainnet]: `${DEFAULT_MAINNET_EXPLORER_URL}/address`,
//   },
// };

/**
 * Get denomination from Asset
 *
 * @param {Asset} asset
 * @returns {string} The denomination of the given asset.
 */
export const getDenom = (asset: Asset): string => {
  if (isAssetDOJNative(asset)) return DENOM_DOJ_NATIVE;
  if (isSynthAsset(asset)) return assetToString(asset).toLowerCase();
  return asset.symbol.toLowerCase();
};

/**
 * Get Asset from denomination
 *
 * @param {string} denom
 * @returns {Asset|null} The asset of the given denomination.
 */
export const assetFromDenom = (denom: string): Asset | null => {
  if (denom === DENOM_DOJ_NATIVE) return AssetDOJNative;
  return assetFromString(denom.toUpperCase());
};

/**
 * Response guard for transaction broadcast
 *
 * @param {any} response The response from the node.
 * @returns {boolean} `true` or `false`.
 */
export const isBroadcastSuccess = (response: unknown): boolean =>
  typeof response === "object" &&
  response !== null &&
  "logs" in response &&
  (response as Record<string, unknown>).logs !== undefined;

/**
 * Get address prefix based on the network.
 *
 * @param {Network} network
 * @returns {string} The address prefix based on the network.
 *
 **/
export const getPrefix = (network: Network) => {
  switch (network) {
    case Network.Mainnet:
      return "dojima";
    case Network.Stagenet:
      return "sdojima";
    // case Network.Testnet:
    //   return "dojima";
    case Network.Testnet:
      return "tdojima";
  }
};

/**
 * Register type for encoding `MsgSetVersion` messages
 */
export const registerSetVersionCodecs = () => {
  cosmosclient.codec.register(
    "/hermes.hermes.v1beta1.types.MsgSetVersion",
    hermes.hermes.v1beta1.types.MsgSetVersion
  );
};

/**
 * Register type for encoding `MsgSetNodeKeys` messages
 */
export const registerSetNodePubkeysCodecs = () => {
  cosmosclient.codec.register(
    "/hermes.hermes.v1beta1.types.MsgSetNodeKeys",
    hermes.hermes.v1beta1.types.MsgSetNodeKeys
  );
};

/**
 * Register type for encoding `MsgCreateOperator` messages
 */
export const registerCreateOperatorCodecs = () => {
  cosmosclient.codec.register(
    "/hermes.operatorstaking.v1beta1.MsgCreateOperator",
    hermes.operatorstaking.v1beta1.MsgCreateOperator
  );
};

/**
 * Register type for encoding `MsgRegisterChain` messages
 */
export const registerRegisterChainCodecs = () => {
  cosmosclient.codec.register(
    "/hermes.chainlist.v1beta1.MsgRegisterChainWithCU",
    hermes.chainlist.v1beta1.MsgRegisterChainWithCU
  );
};

export const registerCreateEndpointCodecs = () => {
  cosmosclient.codec.register(
    "/hermes.chainlist.v1beta1.MsgCreateEndpoint",
    hermes.chainlist.v1beta1.MsgCreateEndpoint
  );
};

/**
 * Register type for encoding `MsgDeposit` messages
 */
export const registerDepositCodecs = () => {
  cosmosclient.codec.register(
    "/hermes.hermes.v1beta1.types.MsgDeposit",
    hermes.hermes.v1beta1.types.MsgDeposit
  );
};

/**
 * Register type for encoding `MsgSend` messages
 */
export const registerSendCodecs = () => {
  cosmosclient.codec.register(
    "/hermes.hermes.v1beta1.types.MsgSend",
    hermes.hermes.v1beta1.types.MsgSend
  );
};

/**
 * Register type for encoding `MsgSetIpAddress` messages
 */
export const registerSetIpAddrCodecs = () => {
  cosmosclient.codec.register(
    "/hermes.hermes.v1beta1.types.MsgSetIPAddress",
    hermes.hermes.v1beta1.types.MsgSetIPAddress
  );
};

/**
 * Parse transaction data from event logs
 *
 * @param {TxLog[]} logs List of tx logs
 * @param {Address} address - Address to get transaction data for
 * @returns {TxData} Parsed transaction data
 */
export const getDepositTxDataFromLogs = (
  logs: TxLog[],
  address: Address
): TxData => {
  const events = logs[0]?.events;

  if (!events) {
    throw Error("No events in logs available");
  }

  type TransferData = { sender: string; recipient: string; amount: BaseAmount };
  type TransferDataList = TransferData[];
  const transferDataList: TransferDataList = events.reduce(
    (acc: TransferDataList, { type, attributes }) => {
      if (type === "transfer") {
        return attributes.reduce((acc2, { key, value }, index) => {
          if (index % 3 === 0)
            acc2.push({
              sender: "",
              recipient: "",
              amount: baseAmount(0, DOJ_DECIMAL),
            });
          const newData = acc2[acc2.length - 1];
          if (key === "sender") newData.sender = value;
          if (key === "recipient") newData.recipient = value;
          if (key === "amount")
            newData.amount = baseAmount(value.replace(/doj/, ""), DOJ_DECIMAL);
          return acc2;
        }, acc);
      }
      return acc;
    },
    []
  );

  const txData: TxData = transferDataList
    // filter out txs which are not based on given address
    .filter(
      ({ sender, recipient }) => sender === address || recipient === address
    )
    // transform `TransferData` -> `TxData`
    .reduce(
      (acc: TxData, { sender, recipient, amount }) => ({
        ...acc,
        from: [...acc.from, { amount, from: sender }],
        to: [...acc.to, { amount, to: recipient }],
      }),
      { from: [], to: [], type: TxType.Transfer }
    );

  return txData;
};

/**
 * Get the default fee.
 *
 * @returns {Fees} The default fee.
 */
export const getDefaultFees = (): Fees => {
  const fee = assetToBase(assetAmount(0.02 /* 0.02 DOJ */, DOJ_DECIMAL));
  return singleFee(FeeType.FlatFee, fee);
};

/**
 * Get transaction type.
 *
 * @param {string} txData the transaction input data
 * @param {string} encoding `base64` or `hex`
 * @returns {string} the transaction type.
 */
export const getTxType = (
  txData: string,
  encoding: "base64" | "hex"
): string => {
  return Buffer.from(txData, encoding).toString().slice(4);
};

/**
 * Helper to get HermesChain's chain id
 * @param {string} nodeUrl HermesNode url
 */
export const getChainId = async (nodeUrl: string): Promise<ChainId> => {
  const { data } = await axios.get<NodeInfoResponse>(
    `${nodeUrl}/cosmos/base/tendermint/v1beta1/node_info`
  );
  return (
    data?.default_node_info?.network ||
    Promise.reject("Could not parse chain id")
  );
};

/**
 * Builds final unsigned TX
 *
 * @param cosmosSdk - CosmosSDK
 * @param txBody - txBody with encoded Msgs
 * @param signerPubkey - signerPubkey string
 * @param sequence - account sequence
 * @param gasLimit - transaction gas limit
 * @returns
 */
export const buildUnsignedTx = ({
  cosmosSdk,
  txBody,
  signerPubkey,
  sequence,
  gasLimit,
}: {
  cosmosSdk: cosmosclient.CosmosSDK;
  txBody: proto.cosmos.tx.v1beta1.TxBody;
  signerPubkey: proto.google.protobuf.Any;
  sequence: Long;
  gasLimit?: Long;
}): cosmosclient.TxBuilder => {
  const authInfo = new proto.cosmos.tx.v1beta1.AuthInfo({
    signer_infos: [
      {
        public_key: signerPubkey,
        mode_info: {
          single: {
            mode: proto.cosmos.tx.signing.v1beta1.SignMode.SIGN_MODE_DIRECT,
          },
        },
        sequence: sequence,
      },
    ],
    fee: {
      amount: null,
      gas_limit: gasLimit || null,
    },
  });

  return new cosmosclient.TxBuilder(cosmosSdk, txBody, authInfo);
};

/**
 * Estimates usage of gas
 *
 * Note: Be careful by using this helper function,
 * it's still experimental and result might be incorrect.
 * Change `multiplier` to get a valid estimation of gas.
 */
export const getEstimatedGas = async ({
  cosmosSDKClient,
  txBody,
  privKey,
  accountNumber,
  accountSequence,
  multiplier,
}: {
  cosmosSDKClient: CosmosSDKClient;
  txBody: proto.cosmos.tx.v1beta1.TxBody;
  privKey: proto.cosmos.crypto.secp256k1.PrivKey;
  accountNumber: Long;
  accountSequence: Long;
  multiplier?: number;
}): Promise<Long | undefined> => {
  const pubKey = privKey.pubKey();
  const txBuilder = buildUnsignedTx({
    cosmosSdk: cosmosSDKClient.sdk,
    txBody: txBody,
    signerPubkey: cosmosclient.codec.instanceToProtoAny(pubKey),
    sequence: accountSequence,
  });

  const signDocBytes = txBuilder.signDocBytes(accountNumber);
  txBuilder.addSignature(privKey.sign(signDocBytes));

  const resp = await rest.tx.simulate(cosmosSDKClient.sdk, {
    tx_bytes: txBuilder.txBytes(),
  });

  const estimatedGas = resp.data?.gas_info?.gas_used ?? null;

  if (!estimatedGas) {
    throw new Error("Could not get data of estimated gas");
  }

  return Long.fromString(estimatedGas).multiply(
    multiplier || DEFAULT_GAS_ADJUSTMENT
  );
};

export const buildRegisterChainTx = async ({
  msgRegisterChain,
  nodeUrl,
  chainId,
}: {
  msgRegisterChain: MsgRegisterChain;
  nodeUrl: string;
  chainId: ChainId;
}): Promise<proto.cosmos.tx.v1beta1.TxBody> => {
  const networkChainId = await getChainId(nodeUrl);
  if (!networkChainId || chainId !== networkChainId) {
    throw new Error(
      `Invalid network (asked: ${chainId} / returned: ${networkChainId}`
    );
  }

  const { chain, computeUnits, signer } = msgRegisterChain;

  const signerAddr = signer.toString();
  const signerDecoded = bech32Buffer.decode(signerAddr);

  if (!chain.chainId) {
    throw new Error("Chain id is not provided");
  }

  const registerChain = {
    Chain: {
      Name: chain.name.toString(),
      Ticker: chain.ticker,
      Id: chain.chainId || "",
    },
    Cu: {
      blockunits: computeUnits.blockUnits,
      transactionunits: computeUnits.txnUnits,
    },
    signer: signerDecoded.data,
  };

  const registerChainMsg =
    hermes.chainlist.v1beta1.MsgRegisterChainWithCU.fromObject(registerChain);

  return new proto.cosmos.tx.v1beta1.TxBody({
    messages: [cosmosclient.codec.instanceToProtoAny(registerChainMsg)],
  });
};

export const buildCreateEndpointTx = async ({
  msgCreateEndpoint,
  nodeUrl,
  chainId,
}: {
  msgCreateEndpoint: MsgCreateEndpoint;
  nodeUrl: string;
  chainId: ChainId;
}): Promise<proto.cosmos.tx.v1beta1.TxBody> => {
  const networkChainId = await getChainId(nodeUrl);
  if (!networkChainId || chainId !== networkChainId) {
    throw new Error(
      `Invalid network (asked: ${chainId} / returned: ${networkChainId}`
    );
  }

  const { chain, rpcUrl, wsUrl, signer } = msgCreateEndpoint;
  const signerAddr = signer.toString();
  const signerDecoded = bech32Buffer.decode(signerAddr);

  if (!chain.chainId) {
    throw new Error("Chain id is not provided");
  }

  const createEndpoint = {
    Chain: {
      Name: chain.name,
      Ticker: chain.ticker,
      Id: chain.chainId || "",
    },
    Rpc: rpcUrl,
    Ws: wsUrl,
    signer: signerDecoded.data,
  };

  const createEndpointMsg =
    hermes.chainlist.v1beta1.MsgCreateEndpoint.fromObject(createEndpoint);

  return new proto.cosmos.tx.v1beta1.TxBody({
    messages: [cosmosclient.codec.instanceToProtoAny(createEndpointMsg)],
  });
};

/**
 * Builds a create operator transaction
 * @param {MsgCreateOperator} msgCreateOperator
 * @param {string} nodeUrl
 * @param {ChainId} chainId
 */
export const buildCreateOperatorTx = async ({
  msgCreateOperator,
  nodeUrl,
  chainId,
}: {
  msgCreateOperator: MsgCreateOperator;
  nodeUrl: string;
  chainId: ChainId;
}): Promise<proto.cosmos.tx.v1beta1.TxBody> => {
  const networkChainId = await getChainId(nodeUrl);
  if (!networkChainId || chainId !== networkChainId) {
    throw new Error(
      `Invalid network (asked: ${chainId} / returned: ${networkChainId}`
    );
  }

  const { signer, stakeAmount, serverAddress } = msgCreateOperator;

  const signerAddr = signer.toString();
  const signerDecoded = bech32Buffer.decode(signerAddr);

  const createOperatorMsg = {
    stake: stakeAmount,
    server: serverAddress,
    signer: signerDecoded.data,
  };

  const operatorMsg =
    hermes.operatorstaking.v1beta1.MsgCreateOperator.fromObject(
      createOperatorMsg
    );

  return new proto.cosmos.tx.v1beta1.TxBody({
    messages: [cosmosclient.codec.instanceToProtoAny(operatorMsg)],
  });
};

/**
 * Structure a MsgDeposit
 *
 * @param {MsgNativeTx} msgNativeTx Msg of type `MsgNativeTx`.
 * @param {string} nodeUrl Node url
 * @param {chainId} ChainId Chain id of the network
 *
 * @returns {Tx} The transaction details of the given transaction id.
 *
 * @throws {"Invalid client url"} Thrown if the client url is an invalid one.
 */
export const buildDepositTx = async ({
  msgNativeTx,
  nodeUrl,
  chainId,
}: {
  msgNativeTx: MsgNativeTx;
  nodeUrl: string;
  chainId: ChainId;
}): Promise<proto.cosmos.tx.v1beta1.TxBody> => {
  const networkChainId = await getChainId(nodeUrl);
  if (!networkChainId || chainId !== networkChainId) {
    throw new Error(
      `Invalid network (asked: ${chainId} / returned: ${networkChainId}`
    );
  }

  const signerAddr = msgNativeTx.signer.toString();
  const signerDecoded = bech32Buffer.decode(signerAddr);

  const msgDepositObj = {
    coins: msgNativeTx.coins,
    memo: msgNativeTx.memo,
    signer: signerDecoded.data,
  };

  const depositMsg =
    hermes.hermes.v1beta1.types.MsgDeposit.fromObject(msgDepositObj);

  return new proto.cosmos.tx.v1beta1.TxBody({
    messages: [cosmosclient.codec.instanceToProtoAny(depositMsg)],
    memo: msgNativeTx.memo,
  });
};

/**
 * Structure a MsgSetVersion
 *
 * @param {MsgSetVersionTx} msgSetVersionTx Msg of type `MsgSetVersionTx`.
 * @param {string} nodeUrl Node url
 * @param {chainId} ChainId Chain id of the network
 *
 * @returns {Tx} The transaction details of the given transaction id.
 *
 * @throws {"Invalid client url"} Thrown if the client url is an invalid one.
 */
export const buildSetVersionTx = async ({
  msgSetVersionTx,
  nodeUrl,
  chainId,
}: {
  msgSetVersionTx: MsgSetVersionTx;
  nodeUrl: string;
  chainId: ChainId;
}): Promise<proto.cosmos.tx.v1beta1.TxBody> => {
  const networkChainId = await getChainId(nodeUrl);
  if (!networkChainId || chainId !== networkChainId) {
    throw new Error(
      `Invalid network (asked: ${chainId} / returned: ${networkChainId}`
    );
  }

  const signerAddr = msgSetVersionTx.signer.toString();
  const signerDecoded = bech32Buffer.decode(signerAddr);

  const msgSetVersionObj = {
    version: msgSetVersionTx.version,
    signer: signerDecoded.data,
  };

  const versionMsg =
    hermes.hermes.v1beta1.types.MsgSetVersion.fromObject(msgSetVersionObj);

  return new proto.cosmos.tx.v1beta1.TxBody({
    messages: [cosmosclient.codec.instanceToProtoAny(versionMsg)],
  });
};

/**
 * Structure a MsgSetNodeKeys
 *
 * @param {MsgSetPubkeysTx} msgSetPubkeysTx Msg of type `MsgSetPubkeysTx`.
 * @param {string} nodeUrl Node url
 * @param {chainId} ChainId Chain id of the network
 *
 * @returns {Tx} The transaction details of the given transaction id.
 *
 * @throws {"Invalid client url"} Thrown if the client url is an invalid one.
 */
export const buildSetPubkeysTx = async ({
  msgSetNodePubkeysTx,
  nodeUrl,
  chainId,
}: {
  msgSetNodePubkeysTx: MsgSetPubkeysTx;
  nodeUrl: string;
  chainId: ChainId;
}): Promise<proto.cosmos.tx.v1beta1.TxBody> => {
  const networkChainId = await getChainId(nodeUrl);
  if (!networkChainId || chainId !== networkChainId) {
    throw new Error(
      `Invalid network (asked: ${chainId} / returned: ${networkChainId}`
    );
  }

  const signerAddr = msgSetNodePubkeysTx.signer.toString();
  const signerDecoded = bech32Buffer.decode(signerAddr);

  const msgSetNodePubkeysObj = {
    pubKeySetSet: {
      secp256k1: msgSetNodePubkeysTx.secp256k1Pubkey,
      ed25519: msgSetNodePubkeysTx.ed25519Pubkey,
    },
    validatorConsPubKey: msgSetNodePubkeysTx.validatorConsPubkey,
    signer: signerDecoded.data,
  };

  const nodePubkeysMsg =
    hermes.hermes.v1beta1.types.MsgSetNodeKeys.fromObject(msgSetNodePubkeysObj);
  return new proto.cosmos.tx.v1beta1.TxBody({
    messages: [cosmosclient.codec.instanceToProtoAny(nodePubkeysMsg)],
  });
};

/**
 * Structure a MsgSetIpAddress
 *
 * @param {MsgSetIpAddressTx} msgSetIpAddressTx Msg of type `MsgSetIpAddressTx`.
 * @param {string} nodeUrl Node url
 * @param {chainId} ChainId Chain id of the network
 *
 * @returns {Tx} The transaction details of the given transaction id.
 *
 * @throws {"Invalid client url"} Thrown if the client url is an invalid one.
 */
export const buildSetIpAddressTx = async ({
  msgSetIpAddressTx,
  nodeUrl,
  chainId,
}: {
  msgSetIpAddressTx: MsgSetIpAddressTx;
  nodeUrl: string;
  chainId: ChainId;
}): Promise<proto.cosmos.tx.v1beta1.TxBody> => {
  const networkChainId = await getChainId(nodeUrl);
  if (!networkChainId || chainId !== networkChainId) {
    throw new Error(
      `Invalid network (asked: ${chainId} / returned: ${networkChainId}`
    );
  }

  const signerAddr = msgSetIpAddressTx.signer.toString();
  const signerDecoded = bech32Buffer.decode(signerAddr);

  const msgSetIpAddressObj = {
    ipAddress: msgSetIpAddressTx.ipAddress,
    signer: signerDecoded.data,
  };

  const ipAddressMsg =
    hermes.hermes.v1beta1.types.MsgSetIPAddress.fromObject(msgSetIpAddressObj);

  return new proto.cosmos.tx.v1beta1.TxBody({
    messages: [cosmosclient.codec.instanceToProtoAny(ipAddressMsg)],
  });
};

/**
 * Structure a MsgSend
 *
 * @param fromAddress - required, from address string
 * @param toAddress - required, to address string
 * @param assetAmount - required, asset amount string (e.g. "10000")
 * @param assetDenom - required, asset denom string (e.g. "doj")
 * @param memo - optional, memo string
 *
 * @returns
 */
export const buildTransferTx = async ({
  fromAddress,
  toAddress,
  assetAmount,
  assetDenom,
  memo = "",
  nodeUrl,
  chainId,
}: {
  fromAddress: Address;
  toAddress: Address;
  assetAmount: BaseAmount;
  assetDenom: string;
  memo?: string;
  nodeUrl: string;
  chainId: ChainId;
}): Promise<proto.cosmos.tx.v1beta1.TxBody> => {
  const networkChainId = await getChainId(nodeUrl);
  if (!networkChainId || chainId !== networkChainId) {
    throw new Error(
      `Invalid network (asked: ${chainId} / returned: ${networkChainId}`
    );
  }

  const fromDecoded = bech32Buffer.decode(fromAddress);
  const toDecoded = bech32Buffer.decode(toAddress);

  const transferObj = {
    fromAddress: fromDecoded.data,
    toAddress: toDecoded.data,
    amount: [
      {
        amount: assetAmount.amount().toString(),
        denom: assetDenom,
      },
    ],
  };

  const transferMsg =
    hermes.hermes.v1beta1.types.MsgSend.fromObject(transferObj);

  return new proto.cosmos.tx.v1beta1.TxBody({
    messages: [cosmosclient.codec.instanceToProtoAny(transferMsg)],
    memo,
  });
};

/**
 * Get the balance of a given address.
 *
 * @param {Address} address By default, it will return the balance of the current wallet. (optional)
 * @param {Asset} asset If not set, it will return all assets available. (optional)
 * @param {cosmosClient} CosmosSDKClient
 *
 * @returns {Balance[]} The balance of the address.
 */
export const getBalance = async ({
  address,
  assets,
  cosmosClient,
}: {
  address: Address;
  assets?: Asset[];
  cosmosClient: CosmosSDKClient;
}): Promise<Balance[]> => {
  const balances = await cosmosClient.getBalance(address);
  if (balances.length === 0) {
    const data = [
      {
        asset: AssetDOJNative,
        amount: baseAmount(0, DOJ_DECIMAL),
      },
    ];
    return data;
  } else {
    return balances
      .map((balance) => ({
        asset:
          (balance.denom && assetFromDenom(balance.denom)) || AssetDOJNative,
        amount: baseAmount(balance.amount, DOJ_DECIMAL),
      }))
      .filter(
        (balance) =>
          !assets ||
          assets.filter(
            (asset) => assetToString(balance.asset) === assetToString(asset)
          ).length
      );
  }
};

/**
 * Get the explorer url.
 *
 * @param {Network} network
 * @param {ExplorerUrls} Explorer urls
 * @returns {string} The explorer url for hermeschain based on the given network.
 */
export const getExplorerUrl = (
  { root }: ExplorerUrls,
  network: Network
): string => root[network];

/**
 * Get explorer address url.
 *
 * @param {ExplorerUrls} Explorer urls
 * @param {Network} network
 * @param {Address} address
 * @returns {string} The explorer url for the given address.
 */
export const getExplorerAddressUrl = ({
  urls,
  network,
  address,
}: {
  urls: ExplorerUrls;
  network: Network;
  address: Address;
}): string => {
  const url = `${urls.address[network]}/${address}`;
  switch (network) {
    case Network.Mainnet:
      return url;
    case Network.Stagenet:
      return `${url}?network=stagenet`;
    case Network.Testnet:
      return `${url}?network=testnet`;
  }
};

/**
 * Get transaction url.
 *
 * @param {ExplorerUrls} Explorer urls
 * @param {Network} network
 * @param {TxHash} txID
 * @returns {string} The explorer url for the given transaction id.
 */
export const getExplorerTxUrl = ({
  urls,
  network,
  txID,
}: {
  urls: ExplorerUrls;
  network: Network;
  txID: TxHash;
}): string => {
  const url = `${urls.tx[network]}/${txID}`;
  switch (network) {
    case Network.Mainnet:
      return url;
    case Network.Stagenet:
      return `${url}?network=stagenet`;
    case Network.Testnet:
      return `${url}?network=testnet`;
  }
};

export type ComputeUnits = {
  blockUnits: number;
  txnUnits: number;
};
