/*
This file is part of web3.js.

web3.js is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

web3.js is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with web3.js.  If not, see <http://www.gnu.org/licenses/>.
*/
// Taken from https://github.com/web3/web3.js/blob/v4.3.0/packages/web3-eth/src/rpc_method_wrappers.ts
// Modified to support Klaytn TxTypes

import { HexStr, getKaikasTxType, getRpcTxObject, isKlaytnTxType, parseTransaction } from "@kaiachain/js-ext-core";
import { Web3Context } from "web3-core";
import {
  estimateGas,
  signTransaction as ethSignTransaction,
} from "web3-eth";
import {
  ETH_DATA_FORMAT,
  DataFormat,
  EthExecutionAPI,
  SignedTransactionInfoAPI,
  TransactionSignedAPI,
  Transaction,
  Web3BaseProvider,
} from "web3-types";
import { format } from "web3-utils";
import { isNullish } from "web3-validator";

import { _parseTxType, bufferedGasLimit } from "../accounts/sign.js";
import { KlaytnTransaction } from "../types.js";

import { getTransactionFromOrToAttr } from "./utils/transaction_builder.js";

export async function signTransaction<ReturnFormat extends DataFormat>(
  web3Context: Web3Context<EthExecutionAPI>,
  transaction: Transaction,
  returnFormat: ReturnFormat,
): Promise<SignedTransactionInfoAPI> {
  // If not Klaytn TxType, fall back to web3-eth's original implementation
  if (!isKlaytnTxType(_parseTxType(transaction.type))) {
    return ethSignTransaction(web3Context, transaction, returnFormat);
  }

  // Resolve 'from' and 'to' field, like in the original web3-eth source
  const tx: KlaytnTransaction = {
    ...transaction,
    from: getTransactionFromOrToAttr("from", web3Context, transaction),
    to: getTransactionFromOrToAttr("to", web3Context, transaction),
  };

  // Fill 'gasLimit' field. Kaikas (window.klaytn) requires 'gas' field nonempty.
  // Fill 'tx.gasLimit' here, then rename to 'gas' in getRpcTxObject() below.
  if (isNullish(tx.gasLimit)) {
    if (!isNullish(tx.gas)) {
      tx.gasLimit = tx.gas;
    } else {
      const gasLimitHex = await estimateGas(web3Context, tx, "latest", ETH_DATA_FORMAT);
      const gasLimitNum = Number(gasLimitHex);
      const bufferedNum = bufferedGasLimit(gasLimitNum);
      tx.gasLimit = format({ format: "uint" }, bufferedNum, ETH_DATA_FORMAT);
    }
  }

  // transactionFormatted contains all fields including Klaytn-specific fields. This is the transaction to be signed.
  const transactionFormatted = {
    ...tx, // first copy all fields from tx (including Klaytn-specific fields)
    ...getRpcTxObject(tx), // then overwrite with formatted fields (only Ethereum fields)
  };

  // Translate to string 'type' field that Kaikas understands.
  if (isKaikas(web3Context.provider)) {
    transactionFormatted.type = getKaikasTxType(transactionFormatted.type);
  }

  // The result may be:
  // - a string
  // - an object { raw: string, tx: Transaction } (e.g. Klaytn node)
  // - an object { rawTranasction: string, ... }  (e.g. Kaikas)
  const response = await web3Context.requestManager.send({
    method: "klay_signTransaction",
    params: [transactionFormatted],
  });

  let rawTransaction: string;
  if (typeof response === "string") {
    rawTransaction = response;
  } else if (typeof response.rawTransaction === "string") {
    rawTransaction = response.rawTransaction;
  } else if (typeof response.raw === "string") {
    rawTransaction = response.raw;
  } else {
    throw new Error(`Unabled to parse signTransaction response: ${JSON.stringify(response)}`);
  }

  try {
    return {
      raw: rawTransaction,
      tx: getTransactionSignedAPI(rawTransaction),
    };
  } catch (e) {
    return {
      raw: rawTransaction,
      tx: {} as TransactionSignedAPI,
    };
  }
}

function getTransactionSignedAPI(rawTransaction: string): TransactionSignedAPI {
  const tx = parseTransaction(rawTransaction);

  return {
    type: HexStr.fromNumber(tx.type || 0),
    to: tx.to || "0x",
    gasPrice: HexStr.fromNumber(tx.gasPrice || 0),
    gas: HexStr.fromNumber(tx.gasLimit),
    nonce: HexStr.fromNumber(tx.nonce),
    value: HexStr.fromNumber(tx.value),
    input: tx.data || "0x",
    data: tx.data || "0x",
    chainId: HexStr.fromNumber(tx.chainId || 0),
    // Becuase Klaytn Tx may contain multiple signatures,
    // we do not return v,r,s to avoid confusion.
    v: "",
    r: "",
    s: "",
  };
}

function isKaikas(provider?: Web3BaseProvider<unknown>): boolean {
  return !isNullish(provider) && ((provider as any).isKaikas == true);
}