package com.izouma.nineth.service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alipay.mychain.sdk.api.utils.Utils;
import com.antfinancial.mychain.baas.tool.restclient.RestClient;
import com.antfinancial.mychain.baas.tool.restclient.RestClientProperties;
import com.antfinancial.mychain.baas.tool.restclient.model.CallRestBizParam;
import com.antfinancial.mychain.baas.tool.restclient.model.Method;
import com.antfinancial.mychain.baas.tool.restclient.model.ReceiptDecoration;
import com.antfinancial.mychain.baas.tool.restclient.response.BaseResp;
import com.izouma.nineth.config.Constants;
import com.izouma.nineth.config.GeneralProperties;
import com.izouma.nineth.dto.NFT;
import com.izouma.nineth.dto.NFTAccount;
import com.izouma.nineth.event.AccountCreatedEvent;
import com.izouma.nineth.exception.BusinessException;
import com.izouma.nineth.utils.SnowflakeIdWorker;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Hex;
import org.springframework.context.ApplicationContext;
import org.springframework.core.env.Environment;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@Service
@Slf4j
@AllArgsConstructor
public class NFTService {
    private final RestClient           restClient;
    private final RestClientProperties restClientProperties;
    private final GeneralProperties    generalProperties;
    private final SnowflakeIdWorker    snowflakeIdWorker;
    private final ApplicationContext   context;
    private final Environment          env;

    @Async
    public void createAccount(Long userId) {
        NFTAccount account = null;
        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(5000);
                account = createAccount("account_" + userId);
                break;
            } catch (Exception e) {
            }
        }
        if (account != null) {
            context.publishEvent(new AccountCreatedEvent(this, userId, account));
        }
    }

    @Retryable(maxAttempts = 10, backoff = @Backoff(delay = 5000), value = BusinessException.class)
    public NFTAccount createAccount(String username) {
        CallRestBizParam callRestBizParam = CallRestBizParam.builder()
                .orderId(String.valueOf(snowflakeIdWorker.nextId()))
                .bizid(Constants.bizId)
                .account(restClientProperties.getAccount())
                .mykmsKeyId(restClientProperties.getKmsId())
                .newAccountId(username)
                .newAccountKmsId(Constants.kmsKey)
                .method(Method.TENANTCREATEACCUNT)
                .gas(100000L).build();
        try {
            BaseResp baseResp = restClient.chainCallForBiz(callRestBizParam);
            NFTAccount account = new NFTAccount(username, Constants.kmsKey, baseResp.getData());
            if (baseResp.isSuccess() || "400123".equals(baseResp.getCode())) {
                log.info("创建账户成功 {}", account);
                return account;
            } else {
                throw new RuntimeException(baseResp.getCode());
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error("创建账户失败", e);
            throw new BusinessException("创建账户失败");
        }
    }

    @Retryable(maxAttempts = 10, backoff = @Backoff(delay = 5000), value = BusinessException.class)
    public synchronized NFT createToken(String toAccount, String tokenId) throws Exception {
        log.info("开始执行EVM合约mint, toAccount: {}, tokenId: {}", toAccount, tokenId);
        if (Arrays.asList(env.getActiveProfiles()).contains("notifytest")) {
            NFT nft = new NFT("1", tokenId, new BigInteger("1"), new BigInteger("1"));
            log.info("测试服生成假token {}", tokenId);
            return nft;
        }
        JSONArray jsonArray = new JSONArray();
        jsonArray.add(Utils.getIdentityByName(toAccount));
        jsonArray.add(new BigInteger(tokenId, 16));
        CallRestBizParam callRestBizParam = CallRestBizParam.builder()
                .orderId(String.valueOf(snowflakeIdWorker.nextId()))
                .bizid(restClientProperties.getBizid())
                .account(restClientProperties.getAccount())
                .contractName(generalProperties.getContractName())
                .methodSignature("mint(identity,uint256)")
                .inputParamListStr(jsonArray.toJSONString())
                .outTypes("[]")//合约返回值类型
                .mykmsKeyId(restClientProperties.getKmsId())
                .method(Method.CALLCONTRACTBIZASYNC)
                .tenantid(restClientProperties.getTenantid())
                .gas(500000L)
                .build();
        BaseResp resp = restClient.bizChainCallWithReceipt(callRestBizParam);
        if (!resp.isSuccess()) {
            log.info("EVM合约执行失败: code: {}, data: {}, toAccount: {}, tokenId: {}", resp.getCode(), resp.getData(), toAccount, tokenId);
        }
        if ("200".equals(resp.getCode())) {
            log.info("EVM合约执行成功");
            // 合约调用交易回执内容
            ReceiptDecoration txReceipt = JSON.parseObject(resp.getData(), ReceiptDecoration.class);
            BigInteger gasUsed = txReceipt.getGasUsed();
            long result = txReceipt.getResult();
            log.info("EVM合约交易内容: 哈希 " + txReceipt.getHash() + ", 消耗燃料 " + gasUsed + ", 结果 " + result);
            NFT nft = new NFT(txReceipt.getHash(), tokenId, txReceipt.getBlockNumber(), txReceipt.getGasUsed());
            log.info("NFT生成成功 {}", nft);
            return nft;
        } else if ("211".equals(resp.getCode())) {
            log.error("EVM合约执行限流");
        } else if ("10201".equals(resp.getCode())) {
            // 异步交易未成功需要根据状态码判断交易状态
            ReceiptDecoration txReceipt = JSON.parseObject(resp.getData(), ReceiptDecoration.class);
            String out = new String(matchAndReplaceNonEnglishChar(txReceipt.getOutput()));
            // 异步交易未成功需要根据状态码判断交易状态
            log.error("EVM合约执行未成功: code: {}, out: {}, tokenId: {}", resp.getCode(), out, tokenId);
            if (out.endsWith("ERC721: token already minted")) {
                return new NFT(txReceipt.getHash(), tokenId, txReceipt.getBlockNumber(), txReceipt.getGasUsed());
            }
        }
        throw new BusinessException("执行EVM合约mint失败");
    }

    public static byte[] matchAndReplaceNonEnglishChar(byte[] data) {
        List<Byte> list = new ArrayList<>();
        for (byte c : data) {
            if (c >= 32 && c <= 127) {
                list.add(c);
            }
        }
        byte[] bytes = new byte[list.size()];
        for (int i = 0; i < list.size(); i++) {
            bytes[i] = list.get(i);
        }
        return bytes;
    }

    public String ownerOf(String tokenId) throws Exception {
        CallRestBizParam callRestBizParam = CallRestBizParam.builder()
                .orderId(String.valueOf(snowflakeIdWorker.nextId()))
                .bizid(restClientProperties.getBizid())
                .account(restClientProperties.getAccount())
                .contractName(generalProperties.getContractName())
                .methodSignature("ownerOf(uint256)")
                .inputParamListStr("[" + new BigInteger(tokenId, 16) + "]")
                .outTypes("[identity]")//合约返回值类型
                .mykmsKeyId(restClientProperties.getKmsId())
                .method(Method.CALLCONTRACTBIZASYNC)
                .tenantid(restClientProperties.getTenantid())
                .gas(500000L)
                .build();
        BaseResp resp = restClient.bizChainCallWithReceipt(callRestBizParam);
        if (!resp.isSuccess()) {
            log.info("EVM合约执行失败: " + resp.getCode() + ", " + resp.getData());
        }
        if ("200".equals(resp.getCode())) {
            log.info("EVM合约执行成功");
            // 合约调用交易回执内容
            ReceiptDecoration txReceipt = JSON.parseObject(resp.getData(), ReceiptDecoration.class);
            BigInteger gasUsed = txReceipt.getGasUsed();
            long result = txReceipt.getResult();
            log.info("EVM合约交易内容: 哈希 " + txReceipt.getHash() + ", 消耗燃料 " + gasUsed + ", 结果 " + result);
            String identity = Hex.encodeHexString(txReceipt.getOutput());
            log.info("owner is: {}", identity);
            return identity;
        } else {
            // 异步交易未成功需要根据状态码判断交易状态
            log.error("EVM合约执行未成功: " + resp.getCode());
            throw new BusinessException("EVM合约执行未成功: " + resp.getCode());
        }
    }

    public NFT transferToken(String tokenId, String fromAccount, String toAccount) throws Exception {
        JSONArray jsonArray = new JSONArray();
        jsonArray.add(Utils.getIdentityByName("raex_official"));
        jsonArray.add(Utils.getIdentityByName("9th_HCWWflAZ_"));
        jsonArray.add("95057808871064671354760012409081314299");
        CallRestBizParam callRestBizParam = CallRestBizParam.builder()
                .orderId(String.valueOf(snowflakeIdWorker.nextId()))
                .bizid(restClientProperties.getBizid())
                .account(restClientProperties.getAccount())
                .contractName("raex12")
                .methodSignature("ownerTransfer(identity,identity,uint256)")
                .inputParamListStr(jsonArray.toJSONString())
                .outTypes("[]")//合约返回值类型
                .mykmsKeyId(restClientProperties.getKmsId())
                .method(Method.CALLCONTRACTBIZASYNC)
                .tenantid(restClientProperties.getTenantid())
                .gas(500000L)
                .build();
        BaseResp resp = restClient.bizChainCallWithReceipt(callRestBizParam);
        if (!resp.isSuccess()) {
            log.info("EVM合约执行失败: " + resp.getCode() + ", " + resp.getData());
        }
        if ("200".equals(resp.getCode())) {
            log.info("EVM合约执行成功");
            // 合约调用交易回执内容
            ReceiptDecoration txReceipt = JSON.parseObject(resp.getData(), ReceiptDecoration.class);
            BigInteger gasUsed = txReceipt.getGasUsed();
            long result = txReceipt.getResult();
            log.info("EVM合约交易内容: 哈希 " + txReceipt.getHash() + ", 消耗燃料 " + gasUsed + ", 结果 " + result);
            return new NFT(txReceipt.getHash(), tokenId, txReceipt.getBlockNumber(), txReceipt.getGasUsed());
        } else {
            // 异步交易未成功需要根据状态码判断交易状态
            log.error("EVM合约执行未成功: " + resp.getCode());
        }
        throw new BusinessException("转移token失败");
    }

    public NFT setApprovalForAll(String account) throws Exception {
        JSONArray jsonArray = new JSONArray();
        jsonArray.add(Utils.getIdentityByName(account));
        jsonArray.add(true);

        CallRestBizParam callRestBizParam = CallRestBizParam.builder()
                .orderId(String.valueOf(snowflakeIdWorker.nextId()))
                .bizid(restClientProperties.getBizid())
                .account(restClientProperties.getAccount())
                .contractName(generalProperties.getContractName())
                .methodSignature("setApprovalForAll(identity,bool)")
                .inputParamListStr(jsonArray.toJSONString())
                .outTypes("[]")//合约返回值类型
                .mykmsKeyId(restClientProperties.getKmsId())
                .method(Method.CALLCONTRACTBIZASYNC)
                .tenantid(restClientProperties.getTenantid())
                .gas(500000L)
                .build();
        BaseResp resp = restClient.bizChainCallWithReceipt(callRestBizParam);
        if (!resp.isSuccess()) {
            log.info("EVM合约执行失败: " + resp.getCode() + ", " + resp.getData());
        }
        if ("200".equals(resp.getCode())) {
            log.info("EVM合约执行成功");
            // 合约调用交易回执内容
            ReceiptDecoration txReceipt = JSON.parseObject(resp.getData(), ReceiptDecoration.class);
            BigInteger gasUsed = txReceipt.getGasUsed();
            long result = txReceipt.getResult();
            log.info("EVM合约交易内容: 哈希 " + txReceipt.getHash() + ", 消耗燃料 " + gasUsed + ", 结果 " + result);
        } else {
            // 异步交易未成功需要根据状态码判断交易状态
            log.error("EVM合约执行未成功: " + resp.getCode());
        }
        throw new BusinessException("创建nft失败");
    }

    public void deployContract(String name, String code) throws Exception {
        String orderId = "order_" + System.currentTimeMillis();
        CallRestBizParam callRestBizParam = CallRestBizParam.builder()
                .orderId(orderId)
                .bizid(restClientProperties.getBizid())
                .account(restClientProperties.getAccount())
                .contractName(name)
                .contractCode(code)
                .mykmsKeyId(restClientProperties.getKmsId())
                .method(Method.DEPLOYCONTRACTFORBIZ)
                .tenantid(restClientProperties.getTenantid())
                .gas(1000000L).build();
        BaseResp resp = restClient.chainCallForBiz(callRestBizParam);
        if (!resp.isSuccess()) {
            log.info("EVM合约执行失败: " + resp.getCode() + ", " + resp.getData());
        }
        log.info("EVM合约执行成功");
        // 合约调用交易回执内容
        ReceiptDecoration txReceipt = JSON.parseObject(resp.getData(), ReceiptDecoration.class);
        BigInteger gasUsed = txReceipt.getGasUsed();
        long result = txReceipt.getResult();
        log.info("EVM合约交易内容: 哈希 " + txReceipt.getHash() + ", 消耗燃料 " + gasUsed + ", 结果 " + result);
    }

}
