package org.fisco.bcos.web3j.tx;

import java.io.IOException;
import java.math.BigInteger;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
import org.fisco.bcos.channel.client.TransactionSucCallback;
import org.fisco.bcos.web3j.crypto.*;
import org.fisco.bcos.web3j.protocol.Web3j;
import org.fisco.bcos.web3j.protocol.core.Request;
import org.fisco.bcos.web3j.protocol.core.methods.response.SendTransaction;
import org.fisco.bcos.web3j.tx.exceptions.TxHashMismatchException;
import org.fisco.bcos.web3j.utils.Numeric;
import org.fisco.bcos.web3j.utils.TxHashVerifier;

/**
 * TransactionManager implementation using Ethereum wallet file to create and sign transactions
 * locally.
 *
 * <p>This transaction manager provides support for specifying the chain id for transactions as per
 * <a href="https://github.com/ethereum/EIPs/issues/155">EIP155</a>.
 */
public class ExtendedRawTransactionManager extends TransactionManager {
    private final Web3j web3j;
    final Credentials credentials;

    @Deprecated private final byte chainId;

    private final BigInteger groupId;
    private final BigInteger fiscoChainId;

    protected TxHashVerifier txHashVerifier = new TxHashVerifier();

    @Deprecated
    public Web3j getWeb3j() {
        return web3j;
    }

    public Credentials getCredentials() {
        return credentials;
    }

    public ExtendedRawTransactionManager(
            Web3j web3j,
            Credentials credentials,
            byte chainId,
            BigInteger groupId,
            BigInteger fiscoChainId) {
        super(web3j, credentials);
        this.web3j = web3j;
        this.credentials = credentials;
        this.chainId = chainId;
        this.groupId = groupId;
        this.fiscoChainId = fiscoChainId;
    }

    @Deprecated
    public ExtendedRawTransactionManager(
            Web3j web3j,
            Credentials credentials,
            byte chainId,
            int attempts,
            int sleepDuration,
            BigInteger groupId,
            BigInteger fiscoChainId) {
        super(web3j, attempts, sleepDuration, credentials);
        this.web3j = web3j;
        this.credentials = credentials;
        this.chainId = chainId;
        this.groupId = groupId;
        this.fiscoChainId = fiscoChainId;
    }

    public ExtendedRawTransactionManager(
            Web3j web3j, Credentials credentials, BigInteger groupId, BigInteger fiscoChainId) {
        super(web3j, credentials);
        this.web3j = web3j;
        this.credentials = credentials;
        this.groupId = groupId;
        this.fiscoChainId = fiscoChainId;
        this.chainId = ChainId.NONE;
    }

    @Deprecated
    public ExtendedRawTransactionManager(
            Web3j web3j,
            Credentials credentials,
            int attempts,
            int sleepDuration,
            BigInteger groupId,
            BigInteger fiscoChainId) {
        this(web3j, credentials, ChainId.NONE, attempts, sleepDuration, groupId, fiscoChainId);
    }

    @Override
    protected BigInteger getBlockLimit() throws IOException {
        return web3j.getBlockNumberCache();
    }

    public TxHashVerifier getTxHashVerifier() {
        return txHashVerifier;
    }

    public void setTxHashVerifier(TxHashVerifier txHashVerifier) {
        this.txHashVerifier = txHashVerifier;
    }

    @Override
    public ExtendedRawTransaction createTransaction(
            BigInteger gasPrice,
            BigInteger gasLimit,
            String to,
            String data,
            BigInteger value,
            String extraData)
            throws IOException {
        // Random r = new SecureRandom();
        Random r = ThreadLocalRandom.current();
        BigInteger randomid = new BigInteger(250, r);
        BigInteger blockLimit = getBlockLimit();
        ExtendedRawTransaction rawTransaction =
                ExtendedRawTransaction.createTransaction(
                        randomid,
                        gasPrice,
                        gasLimit,
                        blockLimit,
                        to,
                        value,
                        data,
                        fiscoChainId,
                        groupId,
                        extraData);
        return rawTransaction;
    }

    @Override
    public SendTransaction sendTransaction(
            BigInteger gasPrice,
            BigInteger gasLimit,
            String to,
            String data,
            BigInteger value,
            String extraData)
            throws IOException {

        ExtendedRawTransaction rawTransaction =
                createTransaction(gasPrice, gasLimit, to, data, value, extraData);

        return signAndSend(rawTransaction);
    }

    @Override
    public SendTransaction sendTransaction(
            BigInteger gasPrice,
            BigInteger gasLimit,
            String to,
            String data,
            BigInteger value,
            String extraData,
            TransactionSucCallback callback)
            throws IOException {

        ExtendedRawTransaction rawTransaction =
                createTransaction(gasPrice, gasLimit, to, data, value, extraData);
        return signAndSend(rawTransaction, callback);
    }

    @Override
    public SendTransaction sendTransaction(String signedTransaction)
            throws IOException, TxHashMismatchException {
        SendTransaction sendTransaction = web3j.sendRawTransaction(signedTransaction).send();
        if (sendTransaction != null && !sendTransaction.hasError()) {
            String txHashLocal = Hash.sha3(signedTransaction);
            String txHashRemote = sendTransaction.getTransactionHash();
            if (!txHashVerifier.verify(txHashLocal, txHashRemote)) {
                throw new TxHashMismatchException(txHashLocal, txHashRemote);
            }
        }

        return sendTransaction;
    }

    @Override
    public SendTransaction sendTransaction(
            String signedTransaction, TransactionSucCallback callback)
            throws IOException, TxHashMismatchException {
        Request<?, SendTransaction> request = web3j.sendRawTransaction(signedTransaction);
        request.setNeedTransCallback(true);
        request.setTransactionSucCallback(callback);

        request.sendOnly();

        return null;

        /*
        if (ethSendTransaction != null && !ethSendTransaction.hasError()) {
            String txHashLocal = Hash.sha3(signedTransaction);
            String txHashRemote = ethSendTransaction.getTransactionHash();
            if (!txHashVerifier.verify(txHashLocal, txHashRemote)) {
                throw new TxHashMismatchException(txHashLocal, txHashRemote);
            }
        }

        return ethSendTransaction;
        */
    }

    @Override
    public String sign(ExtendedRawTransaction rawTransaction) {
        byte[] signedMessage;

        if (chainId > ChainId.NONE) {
            signedMessage =
                    ExtendedTransactionEncoder.signMessage(rawTransaction, chainId, credentials);
        } else {
            signedMessage = ExtendedTransactionEncoder.signMessage(rawTransaction, credentials);
        }

        String signedData = Numeric.toHexString(signedMessage);

        return signedData;
    }

    public SendTransaction signAndSend(ExtendedRawTransaction rawTransaction) throws IOException {
        String signedTransaction = sign(rawTransaction);
        SendTransaction result = sendTransaction(signedTransaction);
        return result;
    }

    public SendTransaction signAndSend(
            ExtendedRawTransaction rawTransaction, TransactionSucCallback callback)
            throws IOException {
        String signedTransaction = sign(rawTransaction);
        SendTransaction result = sendTransaction(signedTransaction, callback);
        return result;
    }
}