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.alipay.mychain.sdk.domain.transaction.LogEntry;
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.ApplicationContextUtil;
import com.izouma.nineth.utils.HashUtils;
import com.izouma.nineth.utils.SnowflakeIdWorker;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
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;

    @Retryable(maxAttempts = 2, backoff = @Backoff(delay = 5000), value = Exception.class)
    public NFTAccount createAccount(String username) throws Exception{
        log.info("尝试创建NFT账户, 用户名: {}", username);
        if (!ApplicationContextUtil.isRunProd()) {
            NFTAccount account = new NFTAccount(username, generalProperties.getKmsKey(), "1");
            log.info("测试服和开发环境生成假的账户 {}", username);
            return account;
        }

        CallRestBizParam callRestBizParam = CallRestBizParam.builder()
                .orderId(String.valueOf(snowflakeIdWorker.nextId()))
                .bizid(generalProperties.getBizId())
                .account(restClientProperties.getAccount())
                .mykmsKeyId(restClientProperties.getKmsId())
                .newAccountId(username)
                .newAccountKmsId(generalProperties.getKmsKey())
                .method(Method.TENANTCREATEACCUNT)
                .gas(100000L).build();
        BaseResp baseResp = restClient.chainCallForBiz(callRestBizParam);
        NFTAccount account = new NFTAccount(username, generalProperties.getKmsKey(), baseResp.getData());
        if (baseResp.isSuccess() || "400123".equals(baseResp.getCode())) {
            log.info("创建NFT账户成功 {}", account);
            return account;
        } else {
            throw new RuntimeException(baseResp.getCode());
        }
    }

    @Retryable(maxAttempts = 2, 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 (!ApplicationContextUtil.isRunProd()) {
            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);
        log.info("EVM合约执行结果: toAccount: {}, tokenId: {}, resp: {}",  toAccount, tokenId, resp);
        if ("200".equals(resp.getCode())) {
            // 合约调用交易回执内容
            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());
            return nft;
        }

        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());
            }
        }

        if ("211".equals(resp.getCode())) {
            throw new BusinessException("EVM合约执行限流("+resp.getCode()+")");
        }


        throw new BusinessException("执行EVM合约mint失败("+resp.getCode()+")");
    }

    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 NFT transferToken(String tokenId, String fromAccount, String toAccount) throws Exception {
        JSONArray jsonArray = new JSONArray();
        jsonArray.add(Utils.getIdentityByName(fromAccount));
        jsonArray.add(Utils.getIdentityByName(toAccount));
        jsonArray.add(Long.parseLong(tokenId, 16));

        CallRestBizParam callRestBizParam = CallRestBizParam.builder()
                .orderId(String.valueOf(new SnowflakeIdWorker(0, 0).nextId()))
                .bizid(restClientProperties.getBizid())
                .account(restClientProperties.getAccount())
                .contractName(generalProperties.getContractName())
                .methodSignature("transferFrom(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);
            for (LogEntry logEntry : txReceipt.getLogs()) {
                if (logEntry.getTopics().get(0).equals(HashUtils.Keccak256("Transfer(identity,identity,uint256)"))) {
                    String transferTokenId = logEntry.getTopics().get(3);
                    txReceipt.getBlockNumber();
                    NFT nft = new NFT(txReceipt.getHash(), tokenId, txReceipt.getBlockNumber(), txReceipt.getGasUsed());
                    log.info("NFT转移成功 {}", nft);
                    return nft;
                }
            }
        } else {
            // 异步交易未成功需要根据状态码判断交易状态
            log.error("EVM合约执行未成功: " + resp.getCode());
        }
        throw new BusinessException("创建nft失败");
    }

    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(new SnowflakeIdWorker(0, 0).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);
    }

}
