package com.table.transfer.util.ether;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import lombok.extern.slf4j.Slf4j;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.core.DefaultBlockParameterName;
import org.web3j.protocol.core.methods.response.EthGetTransactionCount;

import java.math.BigInteger;
import java.util.concurrent.TimeUnit;

@Slf4j
public class EtherNonceManager {

    // 缓存5分钟nonce
    private static final Cache<String, BigInteger> addressNonceCache = CacheBuilder.newBuilder()
            .expireAfterAccess(5, TimeUnit.MINUTES)
            .build();

    /**
     * 获取nonce - 保守策略
     * 缓存的值是"下一个要使用的nonce"
     */
    public static BigInteger getNonce(String chain, String address) throws Exception {
        String cacheKey = chain + address;
        Web3j web3j = Web3jManager.getWeb3j(chain);

        // 获取链上最新nonce（这个值就是下一个可用的nonce）
        BigInteger chainNonce = getChainNonce(web3j, address);

        // 尝试从缓存获取（缓存的值也应该是下一个要用的nonce）
        BigInteger cachedNonce = addressNonceCache.getIfPresent(cacheKey);

        if (cachedNonce == null) {
            // 缓存为空：直接使用链上nonce，缓存保持为链上nonce转账结束后更新缓存nonce
            addressNonceCache.put(cacheKey, chainNonce.add(BigInteger.ONE));
            log.info("缓存为空，使用链上nonce: {}", chainNonce);
            return chainNonce;
        }

        // 缓存存在：对比缓存和链上nonce
        if (cachedNonce.compareTo(chainNonce) == 0) {
            // 一致：使用缓存nonce，缓存递增（因为这次要用掉这个nonce）
            addressNonceCache.put(cacheKey, cachedNonce.add(BigInteger.ONE));
            log.info("缓存一致，使用nonce: {}", cachedNonce);
            return cachedNonce;
        } else if (cachedNonce.compareTo(chainNonce) < 0) {
            // 缓存落后：使用链上nonce，更新缓存为链上nonce
            addressNonceCache.put(cacheKey, chainNonce.add(BigInteger.ONE));
            log.info("缓存落后，使用链上nonce: {}", chainNonce);
            return chainNonce;
        } else {
            // 缓存超前：等待链上同步到缓存的值
            log.warn("缓存超前，等待链上同步: 缓存={}, 链上={}", cachedNonce, chainNonce);
            BigInteger syncedNonce = waitForChainSync(web3j, cacheKey, address);
            // 等待成功后，缓存设置为syncedNonce + 1（因为这次要用掉syncedNonce）
            addressNonceCache.put(cacheKey, syncedNonce.add(BigInteger.ONE));
            return syncedNonce;
        }
    }

    /**
     * 处理缓存超前情况：等待链上nonce同步到缓存的值
     */
    private static BigInteger waitForChainSync(Web3j web3j, String cacheKey, String address) throws Exception {
        int waitCount = 0;
        BigInteger cacheNonce;

        while ((cacheNonce = addressNonceCache.getIfPresent(cacheKey)) != null && waitCount < 30) {
            Thread.sleep(10_000);

            BigInteger currentChainNonce = getChainNonce(web3j, address);

            // 等待链上nonce同步,相等正常,大于可能是有人平台外转账了
            if (currentChainNonce.compareTo(cacheNonce) >= 0) {
                log.info("链上已同步，返回缓存nonce: {}", cacheNonce);
                return cacheNonce;
            }
            log.info("等待链上同步: 缓存={}, 链上={}, 等待{}/30", cacheNonce, currentChainNonce, ++waitCount);
        }

        // 缓存过期或等待超时，或者一直小于本地,那么可能是有转账失败了,本地无法降低nonce值,使用链上nonce
        BigInteger finalChainNonce = getChainNonce(web3j, address);
        log.info("缓存过期或等待超时，使用链上nonce: {}", finalChainNonce);
        return finalChainNonce;
    }

    /**
     * 从链上获取nonce（包含pending交易）
     * 这个返回值就是下一个可用的nonce
     */
    private static BigInteger getChainNonce(Web3j web3j, String address) throws Exception {
        EthGetTransactionCount count = web3j
                .ethGetTransactionCount(address, DefaultBlockParameterName.PENDING)
                .send();
        return count.getTransactionCount();
    }
}