package com.table.transfer.module.service;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.util.concurrent.RateLimiter;
import com.table.transfer.module.enpty.TransactionInfoDTO;
import com.table.transfer.module.enpty.TransferRequest;
import com.table.transfer.module.enpty.TransferResponse;
import com.table.transfer.util.encryp.RSAEncryptionUtil;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.util.encoders.Hex;
import org.springframework.stereotype.Service;
import org.tron.trident.core.ApiWrapper;
import org.tron.trident.core.contract.Contract;
import org.tron.trident.core.contract.Trc20Contract;
import org.tron.trident.crypto.Hash;
import org.tron.trident.proto.Chain;
import org.tron.trident.proto.Response;
import org.tron.trident.utils.Base58Check;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.nio.charset.StandardCharsets;
import java.rmi.ServerException;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;


@Slf4j
@Service
public class TronService {


    // tronGrid API配置
    private static final String API_KEY = "0ab62ce0-04bd-45e8-9570-db636c19b6df";
    /**
     * 租赁一次能量需要的trx
     */
    public static final long DEFAULT_LEASE_AMOUNT_SUN = 2_500_000L; // 2.5 TRX
    /**
     * 能量租赁地址
     */
    public static final String DEFAULT_ENERGY_LEASE_ADDRESS = "TDCaC2PcAWenMv6ErDUq847ENHMtufud2H";

    /**
     * 能量租赁等待时间上限
     */
    private static final long ENERGY_WAIT_TIMEOUT_MS = 50_000L;
    /**
     * 地址+合约,余额(健康检查的时候顺便加载)
     */
    private final Cache<String, BigDecimal> addressBalance = CacheBuilder.newBuilder()
            .expireAfterAccess(1, TimeUnit.HOURS)
            .build();
    //防止能量回收延迟
    private static final Cache<String, Integer> energyRecycling  = CacheBuilder.newBuilder()
            .expireAfterAccess(12, TimeUnit.SECONDS)
            .build();
    private static final Integer energyRecyclingTime=10000;
    /**
     * trx 精度
     */
    private static final BigDecimal SUN_TO_TRX = BigDecimal.valueOf(1_000_000);
    /**
     * 单次转账的能量阈值,但是租赁时有时候会略低于这个值,此时多付点trx,能正常接受
     */
    private static final long ENERGY_PER_TRANSFER = 55_000L;

    /**
     * 频率限制器
     */
    private final RateLimiter ApiWrapperLimiter = RateLimiter.create(2);
    /**
     * ApiWrapper 连接异常的重试次数
     */
    private int heathApiWrapperCount = 0;
    /**
     * ApiWrapper 连接异常的最大重试次数
     */
    private final int MaxHeathApiWrapperCount = 20;

    /**
     * 配合限制器限制请求频率
     */
    private ApiWrapper limitApiWrapper(ApiWrapper apiWrapper) {
        ApiWrapperLimiter.acquire();
        return apiWrapper;
    }

    /**
     * 获取一个健康可用的ApiWrapper
     */
    public ApiWrapper heathApiWrapper(String privateKey, String fromAddress, String trc20Contract) {
        ApiWrapper apiWrapper = null;
        while (heathApiWrapperCount < MaxHeathApiWrapperCount) {
            try {
                apiWrapper = ApiWrapper.ofMainnet(privateKey, API_KEY);
                //健康检查，顺便更新余额
                long trxBalance = limitApiWrapper(apiWrapper).getAccountBalance(fromAddress);
                BigDecimal trxInTrx = new BigDecimal(trxBalance).divide(new BigDecimal(1_000_000), 6, RoundingMode.HALF_UP);
                addressBalance.put(fromAddress, trxInTrx);

                Contract contract = limitApiWrapper(apiWrapper).getContract(trc20Contract);
                Trc20Contract token = new Trc20Contract(contract, fromAddress, limitApiWrapper(apiWrapper));
                BigInteger balanceOf = token.balanceOf(fromAddress);
                // todo 管理代币精度
                BigDecimal divisor = new BigDecimal(1000000);
                BigDecimal divide = new BigDecimal(balanceOf).divide(divisor, 8, RoundingMode.HALF_UP);
                addressBalance.put(fromAddress + trc20Contract, divide);
                heathApiWrapperCount = 0;
                return apiWrapper;
            } catch (Exception e) {
                if (apiWrapper != null) {
                    apiWrapper.close();
                }
                log.warn("ApiWrapper连接异常{}，尝试第{}次重新连接", e.getMessage(), MaxHeathApiWrapperCount);
                heathApiWrapperCount++;
            }
        }
        throw new RuntimeException("ApiWrapper连接异常,全部重试都已失败 heathApiWrapperCount:" + heathApiWrapperCount);

    }

    /**
     * 转账服务
     */
    public TransferResponse transfer(TransferRequest request) throws Exception {
        validateTransferRequest(request);
        //私钥
        String privateKey = request.getPrivateKey();
        //目标地址
        String toAddress = request.getToAddress();
        //金额
        String amount = request.getAmount();
        //发送地址
        String fromAddress = request.getFromAddress();
        //是否代币转账
        boolean isTokenTransfer = request.isTokenTransfer();
        //合约地址
        String contractAddress = request.getContractAddress();
        //mq串行,无并发
        ApiWrapper apiWrapper = null;
        try {
            //获取检查过后的apiWrapper
            apiWrapper = heathApiWrapper(privateKey, fromAddress, contractAddress);

            // 验证私钥和地址匹配
            String derivedAddress = limitApiWrapper(apiWrapper).keyPair.toBase58CheckAddress();
            if (!fromAddress.equalsIgnoreCase(derivedAddress)) {
                throw new RuntimeException("私钥和发送地址不匹配");
            }

            String txId;
            if (isTokenTransfer) {
                //代币转账
                txId = transferToken(apiWrapper, fromAddress, toAddress, contractAddress, amount);
            } else {
                //主币转账
                txId = transferTrx(apiWrapper, fromAddress, toAddress, amount);
            }

            log.info("TRON交易已发送，交易ID: {}", txId);
            return TransferResponse.success(txId);

        } catch (Exception e) {
            log.error("TRON转账失败: {}", e.getMessage(), e);
            return TransferResponse.error("TRON转账失败: " + e.getMessage());
        } finally {
            if (apiWrapper != null) {
                apiWrapper.close();
            }
        }

    }

    /**
     * TRX 主币转账
     */
    private String transferTrx(ApiWrapper apiWrapper, String fromAddress, String toAddress, String amount) throws Exception {
        try {
            long amountInSun = new BigDecimal(amount).multiply(SUN_TO_TRX).longValue();

            if (amountInSun <= 0) {
                throw new RuntimeException("转账金额必须大于0");
            }

            // 检查主币余额
            BigDecimal balance = addressBalance.getIfPresent(fromAddress);
            if (balance == null) {
                balance = getBalance(apiWrapper, fromAddress);
            }

            BigDecimal requiredBalance = new BigDecimal(amount);
            if (balance.compareTo(requiredBalance) < 0) {
                throw new RuntimeException("TRX余额不足，当前余额: " + balance + " TRX，需要: " + requiredBalance + " TRX");
            }

            log.info("开始TRX转账: {} -> {}, 金额: {} TRX ({} SUN)", fromAddress, toAddress, amount, amountInSun);

            Response.TransactionExtention transactionExtention = limitApiWrapper(apiWrapper).transfer(fromAddress, toAddress, amountInSun);

            if (!transactionExtention.getResult().getResult()) {
                throw new RuntimeException("交易构建失败: " + transactionExtention.getResult().getCode());
            }

            Chain.Transaction transaction = transactionExtention.getTransaction();
            Chain.Transaction signedTx = limitApiWrapper(apiWrapper).signTransaction(transaction);
            return limitApiWrapper(apiWrapper).broadcastTransaction(signedTx);
//            return "测试503hash";
        } catch (Exception e) {
            log.error("TRX转账失败: {}", e.getMessage(), e);
            throw new RuntimeException("TRX转账失败: " + e.getMessage());
        }
    }

    /**
     * TRC20 代币转账
     */
    private String transferToken(ApiWrapper apiWrapper, String fromAddress, String toAddress,
                                 String contractAddress, String amount) throws Exception {
        String txHash = null;
        try {

            // 1. 检查代币余额
            BigDecimal tokenBalance = addressBalance.getIfPresent(fromAddress + contractAddress);
            if (tokenBalance == null) {
                tokenBalance = getTokenBalance(apiWrapper, contractAddress, fromAddress);
            }
            BigDecimal transferAmount = new BigDecimal(amount);
            if (tokenBalance.compareTo(transferAmount) < 0) {
                throw new RuntimeException("代币余额不足，当前余额: " + tokenBalance + "，需要: " + transferAmount);
            }


            // 2. 计算需要的能量笔数
            //todo 没有U需要2笔能量,有的需要1笔 ,但是转账其他代币不知道是不是也是以USDT为基数的
            int energyCount = calculateRequiredEnergyCount(apiWrapper, contractAddress, toAddress);
            long requiredEnergy = ENERGY_PER_TRANSFER * energyCount;
            log.info("代币转账需要 {} 笔能量，总计 {} 能量", energyCount, requiredEnergy);

            Integer ifPresent = energyRecycling.getIfPresent(fromAddress);
            if(ifPresent!=null){
                //存在说明近期租过能量了,等待一段时间,等能量回收完成再租赁,防止能量被超收
                Thread.sleep(energyRecyclingTime);
            }
            // 3. 检查当前能量是否足够
            long currentEnergy = getAvailableEnergy(apiWrapper, fromAddress);
            if (currentEnergy < requiredEnergy) {
                log.info("能量不足: {} < {}，需要租赁 {} 笔能量", currentEnergy, requiredEnergy, energyCount);
                // 租赁精确数量的能量
                leaseEnergy(apiWrapper, fromAddress, energyCount);
                // 等待能量到账
                waitForEnergyReplenishment(apiWrapper, fromAddress, requiredEnergy);
            } else {
                log.info("能量充足: {} >= {}，直接进行转账", currentEnergy, requiredEnergy);
            }

            // 4. 执行代币转账
            BigInteger amountInSmallestUnit = new BigDecimal(amount)
                    .multiply(BigDecimal.TEN.pow(6)) // 默认6位小数
                    .toBigInteger();

            if (amountInSmallestUnit.compareTo(BigInteger.ZERO) <= 0) {
                throw new RuntimeException("转账金额必须大于0");
            }

            log.info("开始TRC20转账: {} -> {}, 合约: {}, 金额: {}, 能量笔数: {}", fromAddress, toAddress, contractAddress, amount, energyCount);

            // 使用Trc20Contract进行转账
            Contract contract = limitApiWrapper(apiWrapper).getContract(contractAddress);
            Trc20Contract token = new Trc20Contract(contract, fromAddress, limitApiWrapper(apiWrapper));
            txHash = token.transfer(toAddress, amountInSmallestUnit.longValue(), 0, "", 1000000000L);
            //转账完成后记录这个地址10秒内转过账了
            energyRecycling.put(fromAddress,1);
            log.info("代币转账广播成功: txId={}", txHash);

            return txHash;

        } catch (Exception e) {
            log.error("TRC20代币转账失败: {}", e.getMessage(), e);

            // 如果交易已广播但后续出错，检查是否已确认
            if (txHash != null && isTransactionConfirmed(apiWrapper, txHash)) {
                log.info("检测到交易已上链，尽管本地异常: {}", txHash);
                return txHash;
            }

            throw new RuntimeException("TRC20代币转账失败: " + e.getMessage());
        }
    }

    /**
     * 循环等待能量补充
     */
    public void waitForEnergyReplenishment(ApiWrapper apiWrapper, String address, long requiredEnergy) throws Exception {
        long startTime = System.currentTimeMillis();

        while (System.currentTimeMillis() - startTime < ENERGY_WAIT_TIMEOUT_MS) {
            try {
                long availableEnergy = getAvailableEnergy(apiWrapper, address);
                if (availableEnergy >= requiredEnergy) {
                    log.info("能量已补充至 {}", availableEnergy);
                    return;
                }
                log.info("当前能量 {}，需要 {}，继续等待...", availableEnergy, requiredEnergy);
                Thread.sleep(3000);
            } catch (Exception e) {
                log.warn("查询能量失败: {}", e.getMessage());
                Thread.sleep(3000);
            }
        }
        log.info("能量租赁失败,消耗trx转账");
//        能量没租到直接转,多点手续费,记录下日志就行
//        throw new RuntimeException("能量租赁未到账，超时");
    }

    /**
     * 计算需要的能量笔数
     * 目标地址有代币：1笔能量
     * 目标地址无代币：2笔能量
     */
    private int calculateRequiredEnergyCount(ApiWrapper apiWrapper, String contractAddress, String toAddress) throws Exception {
        try {
            BigDecimal targetBalance = getTokenBalance(apiWrapper, contractAddress, toAddress);
            return targetBalance.compareTo(BigDecimal.ZERO) > 0 ? 1 : 2;
        } catch (Exception e) {
            log.warn("计算能量笔数失败，使用保守值2: {}", e.getMessage());
            return 2; // 保守起见，使用2笔能量
        }
    }

    /**
     * 查询余额
     */
    public BigDecimal getBalance(ApiWrapper apiWrapper, String address) throws Exception {
        try {
            long balanceInSun = limitApiWrapper(apiWrapper).getAccountBalance(address);
            return BigDecimal.valueOf(balanceInSun).divide(SUN_TO_TRX);
        } catch (Exception e) {
            log.error("获取主币余额失败: {}", address, e);
            throw new ServerException("获取TRX余额失败: " + address);
        }
    }

    /**
     * 查询代币余额
     */
    public BigDecimal getTokenBalance(ApiWrapper apiWrapper, String contractAddress, String targetAddress) {
        Contract contract = limitApiWrapper(apiWrapper).getContract(contractAddress);
        Trc20Contract token = new Trc20Contract(contract, targetAddress, limitApiWrapper(apiWrapper));
        BigInteger balanceOf = token.balanceOf(targetAddress);
        //配置代币精度和小数点保留位数
        BigDecimal divisor = new BigDecimal(1000000);
        BigDecimal divide = new BigDecimal(balanceOf).divide(divisor, 8, RoundingMode.HALF_UP);
        return divide;

    }


    /**
     * 检查交易是否确认
     */
    private boolean isTransactionConfirmed(ApiWrapper apiWrapper, String txHash) {
        try {
            Response.TransactionInfo transactionInfo = limitApiWrapper(apiWrapper).getTransactionInfoById(txHash);
            return transactionInfo != null;
        } catch (Exception e) {
            log.warn("检查交易确认状态失败: {}", txHash, e);
            return false;
        }
    }


    public TransactionInfoDTO getTransactionInfoByHash(ApiWrapper apiWrapper, String assetCode, String transactionHash) throws Exception {
        try {
            // 先尝试用ApiWrapper获取

            Response.TransactionInfo transactionInfo = limitApiWrapper(apiWrapper).getTransactionInfoById(transactionHash);

            if (transactionInfo != null) {
                return buildTransactionInfoDTO(transactionInfo, assetCode);
            }
        } catch (Exception e) {
            log.warn("ApiWrapper获取交易信息失败，降级到TronScan API: {}", transactionHash);
        }
        throw new Exception();
        // 降级到TronScan API
//        return getTransactionInfoFromTronScan(assetCode, transactionHash);
    }

    /**
     * 构建交易信息DTO
     */
    private TransactionInfoDTO buildTransactionInfoDTO(Response.TransactionInfo transactionInfo, String assetCode) {
        TransactionInfoDTO dto = new TransactionInfoDTO();
        dto.setHash(transactionInfo.getId().toString());
        dto.setStatus(transactionInfo.getResult().toString().toLowerCase());
        dto.setBlockNumber(transactionInfo.getBlockNumber());
        dto.setTimestamp(transactionInfo.getBlockTimeStamp());

        // 设置手续费
        BigDecimal feeInSun = new BigDecimal(transactionInfo.getFee());
        BigDecimal feeInTrx = feeInSun.divide(SUN_TO_TRX);
        dto.setGasFee(feeInTrx);

        // 这里可以根据transactionInfo解析from/to/amount等详细信息
        // 需要根据实际交易类型进行解析

        return dto;
    }

    /**
     * 验证地址格式
     */
    public boolean validateAddress(String address) {
        if (address == null || address.length() < 20) return false;
        try {
            byte[] decoded = Base58Check.base58ToBytes(address);
            return decoded.length == 21;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 转账前参数检查
     */
    public void validateTransferRequest(TransferRequest transferRequest) throws Exception {
        String pk = RSAEncryptionUtil.decrypt(transferRequest.getEncryptionPrivateKey());

        transferRequest.setPrivateKey(pk);

        if (!validateAddress(transferRequest.getToAddress())) {
            throw new Exception("无效的接收地址");
        }
        if (!validateAddress(transferRequest.getFromAddress())) {
            throw new Exception("无效的发送地址");
        }

        try {
            BigDecimal amount = new BigDecimal(transferRequest.getAmount());
            if (amount.compareTo(BigDecimal.ZERO) <= 0) {
                throw new Exception("转账金额必须大于0");
            }
        } catch (NumberFormatException e) {
            throw new Exception("无效的金额格式");
        }

        if (transferRequest.isTokenTransfer() &&
                (transferRequest.getContractAddress() == null || transferRequest.getContractAddress().isEmpty())) {
            throw new Exception("代币转账需要合约地址");
        }
    }

    /**
     * 检查账户能量值
     *
     * @param address
     * @return
     * @throws Exception
     */
    public long getAvailableEnergy(ApiWrapper apiWrapper, String address) throws Exception {
        try {
            Response.AccountResourceMessage resource = limitApiWrapper(apiWrapper).getAccountResource(address);
            return resource.getEnergyLimit() - resource.getEnergyUsed();
        } catch (Exception e) {
            log.error("获取账户能量失败: {}", address, e);
            throw new RuntimeException("获取账户能量失败: " + e.getMessage());
        }
    }

    /**
     * 租赁能量（精确笔数，无法叠加）
     */
    public void leaseEnergy(ApiWrapper apiWrapper, String fromAddress, int energyCount) throws Exception {
        if (energyCount <= 0) {
            throw new IllegalArgumentException("能量笔数必须大于0");
        }

        long leaseAmount = DEFAULT_LEASE_AMOUNT_SUN * energyCount;
        log.info("租赁 {} 笔能量，金额: {} TRX from {} to {}", energyCount, BigDecimal.valueOf(leaseAmount).divide(BigDecimal.valueOf(1_000_000)), fromAddress, DEFAULT_ENERGY_LEASE_ADDRESS);

        Response.TransactionExtention transfer = limitApiWrapper(apiWrapper).transfer(fromAddress, DEFAULT_ENERGY_LEASE_ADDRESS, leaseAmount);
        Chain.Transaction signedTx = limitApiWrapper(apiWrapper).signTransaction(transfer);
        String txId = limitApiWrapper(apiWrapper).broadcastTransaction(signedTx);

        if (txId == null || txId.isEmpty()) {
            throw new RuntimeException("能量租赁交易广播失败");
        }

        log.info("能量租赁交易广播成功: {}", txId);
    }
}