import { BaseCurrency } from '../../entities';
import {
  ChainId,
  ID_TO_CHAIN_ID,
  ID_TO_DEFAULT_PROVIDER,
  IS_MAP,
  MCS_CONTRACT_ADDRESS_SET,
  NETWORK_NAME_TO_ID,
  TOKEN_REGISTER_ADDRESS_SET,
} from '../../constants';
import { BarterFee, VaultBalance } from '../../types/responseTypes';
import { TokenRegister } from '../../libs/TokenRegister';
import { BigNumber, ethers } from 'ethers';
import { RelayCrossChainService } from '../../libs/mcs/RelayCrossChainService';
import MCS_MAP_METADATA from '../../abis/MAPCrossChainServiceRelay.json';
import { getTokenByAddressAndChainId } from '../../utils/tokenUtil';
import { BarterJsonRpcProvider } from '../../types/paramTypes';
import { ID_TO_SUPPORTED_TOKEN } from '../../constants/supported_tokens';
import { getHexAddress } from '../../utils';
import { VaultToken } from '../../libs/VaultToken';

/**
 * get fee for bridging srcToken to targetChain
 * @param srcToken
 * @param targetChain
 * @param amount
 * @param rpcProvider use default rpcProvider when not specified
 */
export async function getBridgeFee(
  srcToken: BaseCurrency,
  targetChain: string,
  amount: string,
  rpcProvider: BarterJsonRpcProvider
): Promise<BarterFee> {
  const chainId: string = rpcProvider.chainId.toString();

  const mapChainId: string = rpcProvider.chainId.toString();
  const mapProvider = new ethers.providers.JsonRpcProvider(
    rpcProvider.url ? rpcProvider.url : ID_TO_DEFAULT_PROVIDER(mapChainId)
  );
  let totalFeeBySrcToken: string;
  const tokenRegister = new TokenRegister(
    TOKEN_REGISTER_ADDRESS_SET[chainId]!,
    mapProvider
  );
  const toMapFeeAmount = await tokenRegister.getTokenFee(
    getHexAddress(srcToken.address, srcToken.chainId, true),
    amount,
    rpcProvider.chainId.toString()
  );

  totalFeeBySrcToken = toMapFeeAmount;
  if (!IS_MAP(targetChain)) {
    const mapTokenAddress = await tokenRegister.getRelayChainToken(
      srcToken.chainId.toString(),
      srcToken
    );
    const adjustedAmount = await tokenRegister.getRelayChainAmount(
      srcToken,
      srcToken.chainId.toString(),
      BigNumber.from(amount).sub(BigNumber.from(toMapFeeAmount)).toString()
    );

    const feeAmount = await tokenRegister.getTokenFee(
      mapTokenAddress,
      adjustedAmount,
      targetChain.toString()
    );

    totalFeeBySrcToken = BigNumber.from(totalFeeBySrcToken)
      .add(BigNumber.from(feeAmount))
      .toString();
  }

  return Promise.resolve({
    feeToken: srcToken,
    amount: totalFeeBySrcToken.toString(),
  });
}

/**
 * get vault balance
 * @param fromChainId
 * @param fromToken
 * @param toChainId
 * @param rpcProvider
 */
export async function getVaultBalance(
  fromChainId: string,
  fromToken: BaseCurrency,
  toChainId: string,
  rpcProvider: BarterJsonRpcProvider
): Promise<VaultBalance> {
  if (fromChainId != fromToken.chainId) {
    throw new Error("Request Error: chainId and token.chainId doesn't match");
  }

  const mapChainId: string = rpcProvider.chainId.toString();
  const provider = new ethers.providers.JsonRpcProvider(
    rpcProvider.url ? rpcProvider.url : ID_TO_DEFAULT_PROVIDER(mapChainId)
  );

  const tokenRegister = new TokenRegister(
    TOKEN_REGISTER_ADDRESS_SET[mapChainId]!,
    provider
  );

  const mapTokenAddress = IS_MAP(fromChainId)
    ? fromToken.address
    : await tokenRegister.getRelayChainToken(fromChainId.toString(), fromToken);

  const vaultAddress = await tokenRegister.getVaultToken(mapTokenAddress);
  const vaultToken = new VaultToken(vaultAddress, provider);

  const tokenBalance = await vaultToken.getVaultBalance(toChainId.toString());
  let toChainTokenAddress = mapTokenAddress;
  if (!IS_MAP(toChainId)) {
    toChainTokenAddress = await tokenRegister.getToChainToken(
      mapTokenAddress,
      toChainId
    );

    if (toChainTokenAddress === '0x') {
      throw new Error(
        'Internal Error: Cannot find corresponding target token on target chain'
      );
    }
  }

  return Promise.resolve({
    token: getTokenByAddressAndChainId(toChainTokenAddress, toChainId),
    balance: tokenBalance.toString(),
  });
}

export async function getTargetToken(
  srcToken: BaseCurrency,
  targetChainId: string,
  rpcProvider: BarterJsonRpcProvider
): Promise<BaseCurrency> {
  const tokenAddress = await getTargetTokenAddress(
    srcToken,
    targetChainId,
    rpcProvider
  );
  if (tokenAddress === '0x') {
    throw new Error('token does not exist');
  }
  return getTokenByAddressAndChainId(tokenAddress, targetChainId);
}

export async function getTargetTokenAddress(
  srcToken: BaseCurrency,
  targetChainId: string,
  rpcProvider: BarterJsonRpcProvider
): Promise<string> {
  const mapChainId: string = rpcProvider.chainId.toString();
  const provider = new ethers.providers.JsonRpcProvider(
    rpcProvider.url ? rpcProvider.url : ID_TO_DEFAULT_PROVIDER(mapChainId)
  );
  const tokenRegister = new TokenRegister(
    TOKEN_REGISTER_ADDRESS_SET[mapChainId]!,
    provider
  );
  const mapTokenAddress = await tokenRegister.getRelayChainToken(
    srcToken.chainId.toString(),
    srcToken
  );
  let targetTokenAddress = mapTokenAddress;
  if (!IS_MAP(targetChainId)) {
    targetTokenAddress = await tokenRegister.getToChainToken(
      mapTokenAddress,
      targetChainId
    );
  }
  return targetTokenAddress;
}

export async function getTokenCandidates(
  fromChainId: string,
  toChainId: string,
  provider: BarterJsonRpcProvider
): Promise<BaseCurrency[]> {
  let ret = [];
  const fromChainTokenList = ID_TO_SUPPORTED_TOKEN(fromChainId);
  for (let i = 0; i < fromChainTokenList.length; i++) {
    const token: BaseCurrency = fromChainTokenList[i]!;
    if ((await getTargetTokenAddress(token, toChainId, provider)) != '0x') {
      ret.push(token);
    }
  }
  return ret;
}
