import { getNetworkInfo, Network } from "@injectivelabs/networks";
import { DEFAULT_STD_FEE } from "@injectivelabs/utils";
import {
  PrivateKey,
  TxGrpcClient,
  ChainRestAuthApi,
  createTransaction,
  MsgExecuteContract,
} from "@injectivelabs/sdk-ts";
import { fromUint8Array } from "js-base64";
import { impossible, Payload } from "./vaa";
import { NETWORKS } from "./networks";
import { CONTRACTS } from "@certusone/wormhole-sdk/lib/cjs/utils/consts";

export async function execute_injective(
  payload: Payload,
  vaa: Buffer,
  environment: "MAINNET" | "TESTNET" | "DEVNET"
) {
  if (environment === "DEVNET") {
    throw new Error("Injective is not supported in DEVNET");
  }
  const chainName = "injective";
  let n = NETWORKS[environment][chainName];
  if (!n.key) {
    throw Error(`No ${environment} key defined for Injective`);
  }
  let contracts = CONTRACTS[environment][chainName];
  const endPoint =
    environment === "MAINNET" ? Network.MainnetK8s : Network.TestnetK8s;

  const network = getNetworkInfo(endPoint);
  const walletPKHash = n.key;
  const walletPK = PrivateKey.fromHex(walletPKHash);
  const walletInjAddr = walletPK.toBech32();
  const walletPublicKey = walletPK.toPublicKey().toBase64();

  let target_contract: string;
  let action: string;
  let execute_msg: object;

  switch (payload.module) {
    case "Core":
      target_contract = contracts.core;
      action = "submit_v_a_a";
      execute_msg = {
        [action]: {
          vaa: fromUint8Array(vaa),
        },
      };
      switch (payload.type) {
        case "GuardianSetUpgrade":
          console.log("Submitting new guardian set");
          break;
        case "ContractUpgrade":
          console.log("Upgrading core contract");
          break;
        default:
          impossible(payload);
      }
      break;
    case "NFTBridge":
      if (contracts.nft_bridge === undefined) {
        // NOTE: this code can safely be removed once the injective NFT bridge is
        // released, but it's fine for it to stay, as the condition will just be
        // skipped once 'contracts.nft_bridge' is defined
        throw new Error("NFT bridge not supported yet for injective");
      }
      target_contract = contracts.nft_bridge;
      action = "submit_vaa";
      execute_msg = {
        [action]: {
          data: fromUint8Array(vaa),
        },
      };
      switch (payload.type) {
        case "ContractUpgrade":
          console.log("Upgrading contract");
          break;
        case "RegisterChain":
          console.log("Registering chain");
          break;
        case "Transfer":
          console.log("Completing transfer");
          break;
        default:
          impossible(payload);
      }
      break;
    case "TokenBridge":
      console.log("contracts:", contracts);
      if (contracts.token_bridge === undefined) {
        throw new Error("contracts.token_bridge is undefined");
      }
      target_contract = contracts.token_bridge;
      action = "submit_vaa";
      execute_msg = {
        [action]: {
          data: fromUint8Array(vaa),
        },
      };
      switch (payload.type) {
        case "ContractUpgrade":
          console.log("Upgrading contract");
          break;
        case "RegisterChain":
          console.log("Registering chain");
          break;
        case "Transfer":
          console.log("Completing transfer");
          break;
        case "AttestMeta":
          console.log("Creating wrapped token");
          break;
        case "TransferWithPayload":
          throw Error("Can't complete payload 3 transfer from CLI");
        default:
          impossible(payload);
          break;
      }
      break;
    default:
      target_contract = impossible(payload);
      execute_msg = impossible(payload);
  }

  console.log("execute_msg", execute_msg);
  const transaction = MsgExecuteContract.fromJSON({
    sender: walletInjAddr,
    contractAddress: target_contract,
    exec: {
      action,
      ...execute_msg[action],
    },
  });
  console.log("transaction:", transaction);

  const accountDetails = await new ChainRestAuthApi(
    network.sentryHttpApi
  ).fetchAccount(walletInjAddr);
  const { signBytes, txRaw } = createTransaction({
    message: transaction.toDirectSign(),
    memo: "",
    fee: {
      ...DEFAULT_STD_FEE,
      gas: (parseInt(DEFAULT_STD_FEE.gas, 10) * 2.5).toString(),
    },
    pubKey: walletPublicKey,
    sequence: parseInt(accountDetails.account.base_account.sequence, 10),
    accountNumber: parseInt(
      accountDetails.account.base_account.account_number,
      10
    ),
    chainId: network.chainId,
  });
  console.log("txRaw", txRaw);

  console.log("sign transaction...");
  /** Sign transaction */
  const sig = await walletPK.sign(Buffer.from(signBytes));

  /** Append Signatures */
  txRaw.setSignaturesList([sig]);

  const txService = new TxGrpcClient(network.sentryGrpcApi);

  console.log("simulate transaction...");
  /** Simulate transaction */
  try {
    const simulationResponse = await txService.simulate(txRaw);
    console.log(
      `Transaction simulation response: ${JSON.stringify(
        simulationResponse.gasInfo
      )}`
    );
  } catch (e) {
    console.log("Failed to simulate:", e);
    return;
  }

  console.log("broadcast transaction...");
  /** Broadcast transaction */
  const txResponse = await txService.broadcast(txRaw);
  console.log("txResponse", txResponse);

  if (txResponse.code !== 0) {
    console.log(`Transaction failed: ${txResponse.rawLog}`);
  } else {
    console.log(
      `Broadcasted transaction hash: ${JSON.stringify(txResponse.txHash)}`
    );
  }
}
