/*
 * Copyright 2014-2020 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */

package com.webank.webase.transaction.trans;

import com.fasterxml.jackson.databind.JsonNode;
import com.webank.webase.transaction.base.ConstantCode;
import com.webank.webase.transaction.base.ConstantProperties;
import com.webank.webase.transaction.base.ResponseEntity;
import com.webank.webase.transaction.base.exception.BaseException;
import com.webank.webase.transaction.config.Web3Config;
import com.webank.webase.transaction.contract.ContractMapper;
import com.webank.webase.transaction.keystore.KeyStoreService;
import com.webank.webase.transaction.keystore.entity.EncodeInfo;
import com.webank.webase.transaction.keystore.entity.KeyStoreInfo;
import com.webank.webase.transaction.keystore.entity.SignType;
import com.webank.webase.transaction.trans.entity.ReqTransCallInfo;
import com.webank.webase.transaction.trans.entity.ReqTransSendInfo;
import com.webank.webase.transaction.trans.entity.TransInfoDto;
import com.webank.webase.transaction.util.CommonUtils;
import com.webank.webase.transaction.util.ContractAbiUtil;
import com.webank.webase.transaction.util.LogUtils;
import com.webank.webase.transaction.util.JsonUtils;
import java.io.IOException;
import java.math.BigInteger;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.fisco.bcos.channel.client.TransactionSucCallback;
import org.fisco.bcos.channel.handler.ChannelConnections;
import org.fisco.bcos.web3j.abi.FunctionEncoder;
import org.fisco.bcos.web3j.abi.FunctionReturnDecoder;
import org.fisco.bcos.web3j.abi.TypeReference;
import org.fisco.bcos.web3j.abi.datatypes.Function;
import org.fisco.bcos.web3j.abi.datatypes.Type;
import org.fisco.bcos.web3j.crypto.Credentials;
import org.fisco.bcos.web3j.crypto.RawTransaction;
import org.fisco.bcos.web3j.crypto.ExtendedRawTransaction;
import org.fisco.bcos.web3j.crypto.EncryptType;
import org.fisco.bcos.web3j.crypto.TransactionEncoder;
import org.fisco.bcos.web3j.crypto.ExtendedTransactionEncoder;
import org.fisco.bcos.web3j.crypto.Sign.SignatureData;
import org.fisco.bcos.web3j.crypto.gm.GenCredential;
import org.fisco.bcos.web3j.protocol.Web3j;
import org.fisco.bcos.web3j.protocol.core.DefaultBlockParameterName;
import org.fisco.bcos.web3j.protocol.core.Request;
import org.fisco.bcos.web3j.protocol.core.methods.request.Transaction;
import org.fisco.bcos.web3j.protocol.core.methods.response.AbiDefinition;
import org.fisco.bcos.web3j.protocol.core.methods.response.SendTransaction;
import org.fisco.bcos.web3j.protocol.core.methods.response.TransactionReceipt;
import org.fisco.bcos.web3j.utils.Numeric;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;

/**
 * TransService.
 * 
 */
@Slf4j
@Service
public class TransService {
    @Autowired
    Map<Integer, Web3j> web3jMap;
    @Autowired
    Web3Config web3Config;
    @Autowired
    private TransMapper transMapper;
    @Autowired
    private ContractMapper contractMapper;
    @Autowired
    private ThreadPoolTaskExecutor transExecutor;
    @Autowired
    private ConstantProperties properties;
    @Autowired
    private KeyStoreService keyStoreService;

    /**
     * save transaction request data.
     * 
     * @param req parameter
     * @return
     */
    public ResponseEntity save(ReqTransSendInfo req) throws BaseException {
        long startTime = System.currentTimeMillis();
        // check groupId
        int groupId = req.getGroupId();
        if (!checkGroupId(groupId)) {
            log.warn("save fail. groupId:{} has not been configured", groupId);
            throw new BaseException(ConstantCode.GROUPID_NOT_CONFIGURED);
        }
        // check stateless uuid
        String uuidStateless = req.getUuidStateless();
        TransInfoDto transInfo = transMapper.selectTransInfo(groupId, uuidStateless);
        if (transInfo != null) {
            log.error("save groupId:{} uuidStateless:{} exists", groupId, uuidStateless);
            long endTime = System.currentTimeMillis();
            LogUtils.monitorBusinessLogger().info(ConstantProperties.CODE_BUSINESS_10004,
                    endTime - startTime, ConstantProperties.MSG_BUSINESS_10004);
            throw new BaseException(ConstantCode.UUID_IS_EXISTS);
        }
        // check sign type
        if (!SignType.isInclude(req.getSignType())) {
            log.warn("save fail. signType:{} is not existed", req.getSignType());
            throw new BaseException(ConstantCode.SIGN_TYPE_ERROR);
        }
        // check sign user id
        if (SignType.CLOUDCALL.getValue() == req.getSignType()) {
            String signUserId = req.getSignUserId();
            if (StringUtils.isBlank(signUserId)) {
                log.warn("deploy fail. sign user id is empty");
                throw new BaseException(ConstantCode.SIGN_USERID_EMPTY);
            } else {
                boolean result = keyStoreService.checkSignUserId(signUserId);
                if (!result) {
                    throw new BaseException(ConstantCode.SIGN_USERID_ERROR);
                }
            }
        }
        String uuidDeploy = req.getUuidDeploy();
        String contractAddress = req.getContractAddress();
        List<Object> abiList = req.getContractAbi();
        // check request style
        if (StringUtils.isBlank(uuidDeploy)
                && (StringUtils.isBlank(contractAddress) || abiList.isEmpty())) {
            throw new BaseException(ConstantCode.ADDRESS_ABI_EMPTY);
        }
        // check if contract has been deployed
        if (StringUtils.isBlank(contractAddress)) {
            contractAddress = contractMapper.selectContractAddress(groupId, uuidDeploy);
        }
        if (StringUtils.isBlank(contractAddress)) {
            log.warn("save fail. contract has not been deployed");
            throw new BaseException(ConstantCode.CONTRACT_NOT_DEPLOED);
        }
        // check contractAbi
        String contractAbi = "";
        if (abiList.isEmpty()) {
            contractAbi = contractMapper.selectContractAbi(groupId, uuidDeploy);
            if (StringUtils.isBlank(contractAbi)) {
                log.warn("save fail. uuidDeploy:{} abi is not exists", uuidDeploy);
                throw new BaseException(ConstantCode.CONTRACT_ABI_EMPTY);
            }
        } else {
            contractAbi = JsonUtils.toJSONString(abiList);
        }
        // check function
        String funcName = req.getFuncName();
        AbiDefinition abiDefinition = ContractAbiUtil.getAbiDefinition(funcName, contractAbi);
        if (abiDefinition == null) {
            log.warn("save fail. func:{} is not exists", funcName);
            throw new BaseException(ConstantCode.FUNCTION_NOT_EXISTS);
        }
        if (abiDefinition.isConstant()) {
            log.warn("save fail. func:{} is constant", funcName);
            throw new BaseException(ConstantCode.FUNCTION_NOT_CONSTANT);
        }
        // check function parameter
        List<Object> params = req.getFuncParam();
        List<String> funcInputTypes = ContractAbiUtil.getFuncInputType(abiDefinition);
        if (funcInputTypes.size() != params.size()) {
            log.warn("save fail. funcInputTypes:{}, params:{}", funcInputTypes, params);
            throw new BaseException(ConstantCode.IN_FUNCPARAM_ERROR);
        }
        // check input format
        ContractAbiUtil.inputFormat(funcInputTypes, params);
        // check output format
        List<String> funOutputTypes = ContractAbiUtil.getFuncOutputType(abiDefinition);
        ContractAbiUtil.outputFormat(funOutputTypes);
        // insert db
        TransInfoDto transInfoDto = new TransInfoDto();
        transInfoDto.setGroupId(groupId);
        transInfoDto.setUuidStateless(uuidStateless);
        transInfoDto.setUuidDeploy(uuidDeploy);
        transInfoDto.setContractAbi(contractAbi);
        transInfoDto.setContractAddress(contractAddress);
        transInfoDto.setFuncName(funcName);
        transInfoDto.setFuncParam(JsonUtils.toJSONString(params));
        transInfoDto.setSignType(req.getSignType());
        transInfoDto.setSignUserId(req.getSignUserId());
        transInfoDto.setGmtCreate(new Date());
        transMapper.insertTransInfo(transInfoDto);

        log.info("save end. groupId:{} uuidStateless:{}", groupId, uuidStateless);
        ResponseEntity response = new ResponseEntity(ConstantCode.RET_SUCCEED);
        long endTime = System.currentTimeMillis();
        LogUtils.monitorBusinessLogger().info(ConstantProperties.CODE_BUSINESS_10002,
                endTime - startTime, ConstantProperties.MSG_BUSINESS_10002);
        return response;
    }

    /**
     * transaction query.
     * 
     * @param req parameter
     * @return
     */
    public ResponseEntity call(ReqTransCallInfo req) throws BaseException {
        int groupId = req.getGroupId();
        String uuidDeploy = req.getUuidDeploy();
        String contractAddress = req.getContractAddress();
        List<Object> abiList = req.getContractAbi();
        String funcName = req.getFuncName();
        List<Object> params = req.getFuncParam();
        try {
            // check groupId
            if (!checkGroupId(groupId)) {
                log.warn("call fail. groupId:{} has not been configured", groupId);
                throw new BaseException(ConstantCode.GROUPID_NOT_CONFIGURED);
            }
            // check request style
            if (StringUtils.isBlank(uuidDeploy)
                    && (StringUtils.isBlank(contractAddress) || abiList.isEmpty())) {
                throw new BaseException(ConstantCode.ADDRESS_ABI_EMPTY);
            }
            // check if contract has been deployed
            if (StringUtils.isBlank(contractAddress)) {
                contractAddress = contractMapper.selectContractAddress(groupId, uuidDeploy);
            }
            if (StringUtils.isBlank(contractAddress)) {
                log.warn("save fail. contract has not been deployed");
                throw new BaseException(ConstantCode.CONTRACT_NOT_DEPLOED);
            }
            // check contractAbi
            String contractAbi = "";
            if (abiList.isEmpty()) {
                contractAbi = contractMapper.selectContractAbi(groupId, uuidDeploy);
                if (StringUtils.isBlank(contractAbi)) {
                    log.warn("save fail. uuidDeploy:{} abi is not exists", uuidDeploy);
                    throw new BaseException(ConstantCode.CONTRACT_ABI_EMPTY);
                }
            } else {
                contractAbi = JsonUtils.toJSONString(abiList);
            }
            // check function
            AbiDefinition abiDefinition = ContractAbiUtil.getAbiDefinition(funcName, contractAbi);
            if (abiDefinition == null) {
                log.warn("call fail. func:{} is not exists", funcName);
                throw new BaseException(ConstantCode.FUNCTION_NOT_EXISTS);
            }
            if (!abiDefinition.isConstant()) {
                log.warn("call fail. func:{} is not constant", funcName);
                throw new BaseException(ConstantCode.FUNCTION_MUST_CONSTANT);
            }
            // check function parameter
            List<String> funcInputTypes = ContractAbiUtil.getFuncInputType(abiDefinition);
            if (funcInputTypes.size() != params.size()) {
                log.warn("call fail. funcInputTypes:{}, params:{}", funcInputTypes, params);
                throw new BaseException(ConstantCode.IN_FUNCPARAM_ERROR);
            }
            // check input format
            List<Type> finalInputs = ContractAbiUtil.inputFormat(funcInputTypes, params);
            // check output format
            List<String> funOutputTypes = ContractAbiUtil.getFuncOutputType(abiDefinition);
            List<TypeReference<?>> finalOutputs = ContractAbiUtil.outputFormat(funOutputTypes);
            // encode function
            Function function = new Function(funcName, finalInputs, finalOutputs);
            String encodedFunction = FunctionEncoder.encode(function);
            String callOutput = web3jMap.get(groupId)
                    .call(Transaction.createEthCallTransaction(keyStoreService.getRandomAddress(),
                            contractAddress, encodedFunction), DefaultBlockParameterName.LATEST)
                    .send().getValue().getOutput();
            List<Type> typeList =
                    FunctionReturnDecoder.decode(callOutput, function.getOutputParameters());
            ResponseEntity response = new ResponseEntity(ConstantCode.RET_SUCCEED);
            if (typeList.size() > 0) {
                response.setData(ContractAbiUtil.callResultParse(funOutputTypes, typeList));
            } else {
                response.setData(typeList);
            }
            return response;
        } catch (IOException e) {
            log.error("call funcName:{} Exception:{}", funcName, e);
            throw new BaseException(ConstantCode.TRANSACTION_QUERY_FAILED);
        }
    }

    /**
     * get transaction event.
     * 
     * @param groupId groupId
     * @param uuidStateless uuid
     * @return
     */
    public ResponseEntity getEvent(int groupId, String uuidStateless) throws BaseException {
        ResponseEntity response = new ResponseEntity(ConstantCode.RET_SUCCEED);
        TransInfoDto transInfo = transMapper.selectTransInfo(groupId, uuidStateless);
        if (transInfo == null) {
            log.warn("getOutput fail. trans is not exist uuidStateless:{}.", uuidStateless);
            throw new BaseException(ConstantCode.TRANS_NOT_EXIST);
        }
        String transHash = transInfo.getTransHash();
        // check if trans has been sent
        if (StringUtils.isBlank(transHash)) {
            log.warn("getEvent fail. trans not sent to the chain uuidStateless:{}.", uuidStateless);
            throw new BaseException(ConstantCode.TRANS_NOT_SENT);
        }
        String contractAbi = transInfo.getContractAbi();
        if (StringUtils.isBlank(contractAbi)) {
            log.warn("getEvent fail. uuidStateless:{} abi is not exists", uuidStateless);
            throw new BaseException(ConstantCode.CONTRACT_ABI_EMPTY);
        }
        List<AbiDefinition> abiList = ContractAbiUtil.getEventAbiDefinitions(contractAbi);
        if (abiList.isEmpty()) {
            log.warn("getEvent fail. uuidStateless:{} event is not exists", uuidStateless);
            throw new BaseException(ConstantCode.EVENT_NOT_EXISTS);
        }
        try {
            // get TransactionReceipt
            TransactionReceipt receipt = web3jMap.get(groupId).getTransactionReceipt(transHash)
                    .send().getTransactionReceipt().get();
            Object result = ContractAbiUtil.receiptParse(receipt, abiList);
            response.setData(result);
        } catch (IOException e) {
            log.error("getEvent getTransactionReceipt fail. transHash:{} ", transHash);
            throw new BaseException(ConstantCode.NODE_REQUEST_FAILED);
        }
        return response;
    }

    /**
     * get transaction output.
     * 
     * @param groupId groupId
     * @param uuidStateless uuid
     * @return
     */
    public ResponseEntity getOutput(int groupId, String uuidStateless) throws BaseException {
        ResponseEntity response = new ResponseEntity(ConstantCode.RET_SUCCEED);
        TransInfoDto transInfo = transMapper.selectTransInfo(groupId, uuidStateless);
        if (transInfo == null) {
            log.warn("getOutput fail. trans is not exist uuidStateless:{}.", uuidStateless);
            throw new BaseException(ConstantCode.TRANS_NOT_EXIST);
        }
        String transOutput = transInfo.getTransOutput();
        // check if trans has been sent
        if (StringUtils.isBlank(transOutput)) {
            log.warn("getOutput fail. trans output is empty uuidStateless:{}.", uuidStateless);
            throw new BaseException(ConstantCode.TRANS_OUTPUT_EMPTY);
        }
        String contractAbi = transInfo.getContractAbi();
        if (StringUtils.isBlank(contractAbi)) {
            log.warn("getOutput fail. uuidStateless:{} abi is not exists", uuidStateless);
            throw new BaseException(ConstantCode.CONTRACT_ABI_EMPTY);
        }
        // get AbiDefinition
        AbiDefinition abiDefinition =
                ContractAbiUtil.getAbiDefinition(transInfo.getFuncName(), contractAbi);
        // check output format
        List<String> funOutputTypes = ContractAbiUtil.getFuncOutputType(abiDefinition);
        List<TypeReference<?>> finalOutputs = ContractAbiUtil.outputFormat(funOutputTypes);
        // encode function
        Function function = new Function(transInfo.getFuncName(), null, finalOutputs);
        List<Type> typeList = FunctionReturnDecoder.decode(transInfo.getTransOutput(),
                function.getOutputParameters());
        if (typeList.size() > 0) {
            response.setData(ContractAbiUtil.callResultParse(funOutputTypes, typeList));
        } else {
            response.setData(typeList);
        }
        return response;
    }

    /**
     * get transaction info.
     * 
     * @param groupId groupId
     * @param uuidStateless uuid
     * @return
     */
    public ResponseEntity getTransInfo(int groupId, String uuidStateless)
            throws BaseException {
        ResponseEntity response = new ResponseEntity(ConstantCode.RET_SUCCEED);
        TransInfoDto transInfo = transMapper.selectTransInfo(groupId, uuidStateless);
        if (transInfo == null) {
            log.warn("getTransactionHash fail. trans is not exist uuidStateless:{}.",
                    uuidStateless);
            throw new BaseException(ConstantCode.TRANS_NOT_EXIST);
        }
        response.setData(transInfo);
        return response;
    }

    /**
     * handleTransInfo.
     * 
     * @param transInfoList transInfoList
     */
    public void handleTransInfo(List<TransInfoDto> transInfoList) {
        for (TransInfoDto transInfoDto : transInfoList) {
            try {
                Thread.sleep(properties.getSleepTime());
                transExecutor.execute(new Runnable() {
                    @Override
                    public void run() {
                        transSend(transInfoDto);
                    }
                });
            } catch (RejectedExecutionException e) {
                log.error("schedule threadPool is full", e);
            } catch (InterruptedException e) {
                log.error("schedule InterruptedException", e);
                Thread.currentThread().interrupt();
            }
        }
    }

    /**
     * transaction send.
     * 
     * @param transInfoDto transaction info
     */
    public void transSend(TransInfoDto transInfoDto) {
        log.debug("transSend transInfoDto:{}", JsonUtils.toJSONString(transInfoDto));
        Long id = transInfoDto.getId();
        log.info("transSend id:{}", id);
        int groupId = transInfoDto.getGroupId();
        int requestCount = transInfoDto.getRequestCount();
        int signType = transInfoDto.getSignType();
        try {
            // check status
            int status = transMapper.selectStatus(id, transInfoDto.getGmtCreate());
            if (status == 1) {
                log.debug("transSend id:{} has successed.", id);
                return;
            }
            // requestCount + 1
            transMapper.updateRequestCount(id, requestCount + 1, transInfoDto.getGmtCreate());
            // check requestCount
            if (requestCount == properties.getRequestCountMax()) {
                log.warn("transSend id:{} has reached limit:{}", id,
                        properties.getRequestCountMax());
                LogUtils.monitorAbnormalLogger().error(ConstantProperties.CODE_ABNORMAL_S0004,
                        ConstantProperties.MSG_ABNORMAL_S0004);
                return;
            }

            String contractAbi = transInfoDto.getContractAbi();
            String contractAddress = transInfoDto.getContractAddress();
            String funcName = transInfoDto.getFuncName();
            List<Object> params = JsonUtils.toJavaObjectList(transInfoDto.getFuncParam(), Object.class);

            // get function abi
            AbiDefinition abiDefinition = ContractAbiUtil.getAbiDefinition(funcName, contractAbi);
            // input format
            List<String> funcInputTypes = ContractAbiUtil.getFuncInputType(abiDefinition);
            List<Type> finalInputs = ContractAbiUtil.inputFormat(funcInputTypes, params);
            // output format
            List<String> funOutputTypes = ContractAbiUtil.getFuncOutputType(abiDefinition);
            List<TypeReference<?>> finalOutputs = ContractAbiUtil.outputFormat(funOutputTypes);
            // encode function
            Function function = new Function(funcName, finalInputs, finalOutputs);
            String encodedFunction = FunctionEncoder.encode(function);
            // data sign
            String signMsg = signMessage(groupId, signType, transInfoDto.getSignUserId(),
                    contractAddress, encodedFunction);
            if (StringUtils.isBlank(signMsg)) {
                return;
            }
            // send transaction
            final CompletableFuture<TransactionReceipt> transFuture = new CompletableFuture<>();
            sendMessage(groupId, signMsg, transFuture);
            TransactionReceipt receipt =
                    transFuture.get(properties.getTransMaxWait(), TimeUnit.SECONDS);
            transInfoDto.setTransHash(receipt.getTransactionHash());
            transInfoDto.setTransOutput(receipt.getOutput());
            transInfoDto.setReceiptStatus(receipt.isStatusOK());
            if (receipt.isStatusOK()) {
                transInfoDto.setHandleStatus(1);
            }
            transMapper.updateHandleStatus(transInfoDto);
        } catch (Exception e) {
            log.error("fail transSend id:{}", id, e);
            LogUtils.monitorAbnormalLogger().error(ConstantProperties.CODE_ABNORMAL_S0002,
                    ConstantProperties.MSG_ABNORMAL_S0002);
        }
    }

    /**
     * data sign.
     * 
     * @param groupId id
     * @param signType type
     * @param contractAddress info
     * @param data info
     * @return
     */
    public String signMessage(int groupId, int signType, String signUserId, String contractAddress,
            String data) throws IOException, BaseException {
        Random r = new Random();
        BigInteger randomid = new BigInteger(250, r);
        BigInteger blockLimit = web3jMap.get(groupId).getBlockNumberCache();
        String versionContent = web3jMap.get(groupId).getNodeVersion().sendForReturnString();
        String signMsg = "";
        if (versionContent.contains("2.0.0-rc1") || versionContent.contains("release-2.0.1")) {
            RawTransaction rawTransaction = RawTransaction.createTransaction(randomid,
                    ConstantProperties.GAS_PRICE, ConstantProperties.GAS_LIMIT, blockLimit,
                    contractAddress, BigInteger.ZERO, data);
            if (signType == SignType.LOCALCONFIG.getValue()) {
                Credentials credentials = GenCredential.create(properties.getPrivateKey());
                byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, credentials);
                signMsg = Numeric.toHexString(signedMessage);
            } else if (signType == SignType.LOCALRANDOM.getValue()) {
                KeyStoreInfo keyStoreInfo = keyStoreService.getKey();
                Credentials credentials = GenCredential.create(keyStoreInfo.getPrivateKey());
                byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, credentials);
                signMsg = Numeric.toHexString(signedMessage);
            } else if (signType == SignType.CLOUDCALL.getValue()) {
                byte[] encodedTransaction = TransactionEncoder.encode(rawTransaction);
                String encodedDataStr = Numeric.toHexString(encodedTransaction);

                EncodeInfo encodeInfo = new EncodeInfo();
                encodeInfo.setEncodedDataStr(encodedDataStr);
                encodeInfo.setSignUserId(signUserId);
                String signDataStr = keyStoreService.getSignData(encodeInfo);
                if (StringUtils.isBlank(signDataStr)) {
                    log.warn("deploySend get sign data error.");
                    return null;
                }

                SignatureData signData = CommonUtils.stringToSignatureData(signDataStr);
                byte[] signedMessage = TransactionEncoder.encode(rawTransaction, signData);
                signMsg = Numeric.toHexString(signedMessage);
            }
        } else {
            JsonNode version = JsonUtils.stringToJsonNode(versionContent);
            if (version == null) {
                log.error("parse json error");
            }
            String chainId = version.get("Chain Id").asText();
            ExtendedRawTransaction extendedRawTransaction =
                    ExtendedRawTransaction.createTransaction(randomid, ConstantProperties.GAS_PRICE,
                            ConstantProperties.GAS_LIMIT, blockLimit, contractAddress,
                            BigInteger.ZERO, data, new BigInteger(chainId),
                            BigInteger.valueOf(groupId), "");
            if (signType == SignType.LOCALCONFIG.getValue()) {
                Credentials credentials = GenCredential.create(properties.getPrivateKey());
                byte[] signedMessage =
                        ExtendedTransactionEncoder.signMessage(extendedRawTransaction, credentials);
                signMsg = Numeric.toHexString(signedMessage);
            } else if (signType == SignType.LOCALRANDOM.getValue()) {
                KeyStoreInfo keyStoreInfo = keyStoreService.getKey();
                Credentials credentials = GenCredential.create(keyStoreInfo.getPrivateKey());
                byte[] signedMessage =
                        ExtendedTransactionEncoder.signMessage(extendedRawTransaction, credentials);
                signMsg = Numeric.toHexString(signedMessage);
            } else if (signType == SignType.CLOUDCALL.getValue()) {
                byte[] encodedTransaction =
                        ExtendedTransactionEncoder.encode(extendedRawTransaction);
                String encodedDataStr = Numeric.toHexString(encodedTransaction);

                EncodeInfo encodeInfo = new EncodeInfo();
                encodeInfo.setEncodedDataStr(encodedDataStr);
                encodeInfo.setSignUserId(signUserId);
                String signDataStr = keyStoreService.getSignData(encodeInfo);
                if (StringUtils.isBlank(signDataStr)) {
                    log.warn("deploySend get sign data error.");
                    return null;
                }

                SignatureData signData = CommonUtils.stringToSignatureData(signDataStr);
                byte[] signedMessage =
                        ExtendedTransactionEncoder.encode(extendedRawTransaction, signData);
                signMsg = Numeric.toHexString(signedMessage);
            }
        }
        return signMsg;
    }

    /**
     * send message to node.
     * 
     * @param signMsg signMsg
     * @param future future
     */
    public void sendMessage(int groupId, String signMsg,
            final CompletableFuture<TransactionReceipt> future) throws IOException {
        Request<?, SendTransaction> request = web3jMap.get(groupId).sendRawTransaction(signMsg);
        request.setNeedTransCallback(true);
        request.setTransactionSucCallback(new TransactionSucCallback() {
            @Override
            public void onResponse(TransactionReceipt receipt) {
                log.info("onResponse receipt:{}", receipt);
                future.complete(receipt);
                return;
            }
        });
        request.send();
    }

    /**
     * check groupId.
     * 
     * @param groupId info
     * @return
     */
    public boolean checkGroupId(int groupId) {
        List<ChannelConnections> connList = web3Config.getGroupConfig().getAllChannelConnections();
        for (ChannelConnections conn : connList) {
            if (groupId == conn.getGroupId()) {
                return true;
            }
        }
        return false;
    }

    /**
     * delete transaction data.
     */
    public void deleteDataSchedule() {
        transMapper.deletePartData(properties.getKeepDays());
    }
}