package com.bizzan.bitrade.service;

import com.bizzan.bitrade.dao.CoinNetworkConfigDao;
import com.bizzan.bitrade.entity.Coin;
import com.bizzan.bitrade.entity.CoinNetwork;
import com.bizzan.bitrade.entity.CoinNetworkConfig;
import com.bizzan.bitrade.entity.CoinRechargeOrder;
import com.bizzan.bitrade.job.chain.ChainScanner;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Date;
import java.util.List;

/**
 * 链上充值确认 Service
 * 负责单笔充值订单的链上验证、入账、状态更新，使用事务保证原子性
 *
 * @author auto
 * @date 2026-05-13
 */
@Slf4j
@Service
public class CoinRechargeConfirmService {

    @Autowired
    private CoinRechargeOrderService coinRechargeOrderService;

    @Autowired
    private CoinService coinService;

    @Autowired
    private CoinNetworkConfigDao coinNetworkConfigDao;

    @Autowired
    private MemberWalletService memberWalletService;

    @Autowired
    private List<ChainScanner> chainScanners;

    /** CONFIRMING 状态超时小时数：超过此时间仍未找到 hash 则标记失败 */
    private static final int CONFIRMING_TIMEOUT_HOURS = 2;

    /**
     * 处理单笔待确认充值订单
     * 验证链上 hash → 入账到上级币种钱包 → 更新订单状态
     * 整个流程在同一事务内完成，入账失败则回滚订单状态变更
     *
     * @param order   待确认订单（CONFIRMING 状态）
     * @param network 调用方预加载的主网信息（可为 null，null 时跳过）
     */
    @Transactional(rollbackFor = Exception.class)
    public void confirmOrder(CoinRechargeOrder order, CoinNetwork network) {
        String orderNo = order.getOrderNo();
        String networkName = order.getNetworkName();

        // 1. 查找对应链的扫描器
        ChainScanner scanner = findScanner(networkName);
        if (scanner == null) {
            log.warn("[充值确认] 未找到主网 {} 的扫描器，跳过订单 {}", networkName, orderNo);
            return;
        }

        // 2. 校验主网 RPC 地址（由调用方传入，避免循环内重复查库）
        if (network == null || network.getRpcUrl() == null || network.getRpcUrl().isEmpty()) {
            log.warn("[充值确认] 主网 {} 未配置 RPC 地址，跳过订单 {}", networkName, orderNo);
            return;
        }

        // 3. 获取合约地址、合约精度、入账保留小数位
        CoinNetworkConfig networkConfig = coinNetworkConfigDao.findById(order.getNetworkConfigId());
        String contractAddress    = (networkConfig != null) ? networkConfig.getContractAddress()    : null;
        int contractDecimals      = (networkConfig != null) ? networkConfig.getContractDecimals()   : 6;
        int scale                 = (networkConfig != null) ? networkConfig.getScale()              : 2;

        // 4. 调用链扫描器验证 hash，用合约精度将链上原始金额换算为实际金额
        BigDecimal confirmedAmount = scanner.queryConfirmedAmount(
                network.getRpcUrl(),
                order.getTxHash(),
                order.getRechargeAddress(),
                contractAddress,
                contractDecimals
        );

        if (confirmedAmount == null) {
            // hash 未上链或未确认，检查是否已超时
            if (isConfirmingTimedOut(order)) {
                log.warn("[充值确认] 超过 {}h 未找到链上 hash，标记失败，orderNo={}, hash={}",
                        CONFIRMING_TIMEOUT_HOURS, orderNo, order.getTxHash());
                coinRechargeOrderService.markFailed(order,
                        "超过 " + CONFIRMING_TIMEOUT_HOURS + " 小时未找到链上 hash");
                return;
            }
            log.debug("[充值确认] hash 未确认，orderNo={}, hash={}", orderNo, order.getTxHash());
            return;
        }

        // 4.1 按入账保留小数位截断，去除链上精度冗余
        confirmedAmount = confirmedAmount.setScale(scale, RoundingMode.DOWN);

        if (confirmedAmount.compareTo(BigDecimal.ZERO) <= 0) {
            log.warn("[充值确认] 链上确认金额为 0，标记失败，orderNo={}", orderNo);
            coinRechargeOrderService.markFailed(order, "链上确认金额为 0");
            return;
        }
        if(confirmedAmount.compareTo(order.getAmount())!=0){
            log.warn("填写金额和脸上金额不一致，标记失败，orderNo={}", orderNo);
            coinRechargeOrderService.markFailed(order, "链上确认金额为 0");
            return;
        }
        // 5. 查询子币种实体（按 coin.name 查询）
        // MemberWalletService.recharge 内部会将 EUSDT/TUSDT/USDT-BEP20 的金额入账到上级 USDT 钱包
        Coin coin = coinService.findOne(order.getCoinName());
        if (coin == null) {
            log.error("[充值确认] 币种不存在，coinName={}，标记失败，orderNo={}", order.getCoinName(), orderNo);
            coinRechargeOrderService.markFailed(order, "币种不存在：" + order.getCoinName());
            return;
        }

        // 6. 入账：按 memberId 查找用户钱包，增加余额、记录流水、发送 Kafka 通知
        memberWalletService.rechargeByMemberId(
                order.getMemberId(), coin, order.getRechargeAddress(), confirmedAmount, order.getTxHash());

        // 7. 更新订单状态为已完成，记录链上实际到账金额
        coinRechargeOrderService.markCompleted(order, confirmedAmount);

        log.info("[充值确认] 充值到账成功，orderNo={}, memberId={}, amount={}, coin={}",
                orderNo, order.getMemberId(), confirmedAmount, order.getCoinName());
    }

    /**
     * 判断 CONFIRMING 订单是否已超过超时阈值
     * 以 submitHashTime 为起点，未设置时回退到 createTime
     *
     * @param order 待确认订单
     * @return true 表示已超时
     */
    private boolean isConfirmingTimedOut(CoinRechargeOrder order) {
        // 优先使用用户提交 hash 的时间，回退到订单创建时间
        Date baseTime = order.getSubmitHashTime() != null
                ? order.getSubmitHashTime()
                : order.getCreateTime();
        if (baseTime == null) {
            return false;
        }
        long elapsedMs = System.currentTimeMillis() - baseTime.getTime();
        return elapsedMs > (long) CONFIRMING_TIMEOUT_HOURS * 60 * 60 * 1000;
    }

    /**
     * 根据主网名称查找对应的链扫描器
     *
     * @param networkName 主网名称，如 ERC20 / TRC20 / BEP20
     * @return 匹配的扫描器，未找到返回 null
     */
    private ChainScanner findScanner(String networkName) {
        for (ChainScanner scanner : chainScanners) {
            if (scanner.supports(networkName)) {
                return scanner;
            }
        }
        return null;
    }
}
