package com.bizzan.bitrade.service;

import com.bizzan.bitrade.constant.CoinRechargeOrderStatus;
import com.bizzan.bitrade.dao.CoinRechargeOrderDao;
import com.bizzan.bitrade.entity.Coin;
import com.bizzan.bitrade.entity.CoinNetworkConfig;
import com.bizzan.bitrade.entity.CoinRechargeOrder;
import com.bizzan.bitrade.service.Base.BaseService;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;

/**
 * 链上充值订单 Service
 *
 * @author auto
 * @date 2026-05-13
 */
@Service
public class CoinRechargeOrderService extends BaseService {

    @Autowired
    private CoinRechargeOrderDao coinRechargeOrderDao;

    /**
     * 创建充值订单
     * 校验子币种、主网配置及收款地址后生成待支付订单，返回订单号
     *
     * @param memberId        会员ID
     * @param coin            子币种（已校验存在）
     * @param networkConfig   主网配置（已校验存在且含收款地址）
     * @param amount          充值金额
     * @return 保存后的充值订单
     * @throws IllegalArgumentException 收款地址未配置时抛出
     */
    @Transactional(rollbackFor = Exception.class)
    public CoinRechargeOrder createOrder(Long memberId, Coin coin,
                                         CoinNetworkConfig networkConfig, BigDecimal amount) {
        // 1. 校验收款地址已配置
        if (StringUtils.isBlank(networkConfig.getRechargeAddress())) {
            throw new IllegalArgumentException("该主网暂未配置收款地址，无法发起充值");
        }
        // 2. 生成唯一订单号
        String orderNo = generateOrderNo();
        // 3. 构建订单实体
        CoinRechargeOrder order = buildOrder(memberId, coin, networkConfig, amount, orderNo);
        // 4. 持久化
        return coinRechargeOrderDao.save(order);
    }

    /**
     * 回写交易 hash，将订单状态变更为待链上确认
     *
     * @param orderNo  订单号
     * @param memberId 当前会员ID（用于鉴权，防止越权操作）
     * @param txHash   链上交易 hash
     * @return 更新后的订单
     * @throws IllegalArgumentException 订单不存在、不属于当前用户或状态不合法时抛出
     */
    @Transactional(rollbackFor = Exception.class)
    public CoinRechargeOrder submitTxHash(String orderNo, Long memberId, String txHash) {
        // 1. 查询订单
        CoinRechargeOrder order = coinRechargeOrderDao.findByOrderNo(orderNo);
        if (order == null) {
            throw new IllegalArgumentException("订单不存在：" + orderNo);
        }
        // 2. 鉴权：只能操作自己的订单
        if (!order.getMemberId().equals(memberId)) {
            throw new IllegalArgumentException("无权操作该订单");
        }
        // 3. 状态校验：只有待支付状态才能提交 hash
        if (order.getStatus() != CoinRechargeOrderStatus.PENDING) {
            throw new IllegalArgumentException("当前订单状态不允许提交 hash，状态：" + order.getStatus().getDesc());
        }
        // 4. 回写 hash，变更状态
        updateHashAndStatus(order, txHash);
        return coinRechargeOrderDao.save(order);
    }

    /**
     * 按会员ID分页查询充值订单列表
     *
     * @param memberId 会员ID
     * @param pageNo   页码（从1开始）
     * @param pageSize 每页条数
     * @return 分页结果
     */
    @Transactional(readOnly = true)
    public Page<CoinRechargeOrder> findByMemberId(Long memberId, int pageNo, int pageSize) {
        PageRequest pageRequest = new PageRequest(pageNo - 1, pageSize,
                new Sort(Sort.Direction.DESC, "createTime"));
        return coinRechargeOrderDao.findAllByMemberIdOrderByCreateTimeDesc(memberId, pageRequest);
    }

    /**
     * 按订单号查询（含鉴权）
     *
     * @param orderNo  订单号
     * @param memberId 当前会员ID
     * @return 订单
     * @throws IllegalArgumentException 订单不存在或不属于当前用户时抛出
     */
    @Transactional(readOnly = true)
    public CoinRechargeOrder findByOrderNoAndMemberId(String orderNo, Long memberId) {
        CoinRechargeOrder order = coinRechargeOrderDao.findByOrderNo(orderNo);
        if (order == null) {
            throw new IllegalArgumentException("订单不存在：" + orderNo);
        }
        if (!order.getMemberId().equals(memberId)) {
            throw new IllegalArgumentException("无权查看该订单");
        }
        return order;
    }

    /**
     * 查询所有待链上确认的订单（定时任务扫描使用）
     *
     * @return CONFIRMING 状态的订单列表
     */
    @Transactional(readOnly = true)
    public java.util.List<CoinRechargeOrder> findAllConfirming() {
        return coinRechargeOrderDao.findAllByStatus(CoinRechargeOrderStatus.CONFIRMING);
    }

    /**
     * 将订单标记为已完成，记录实际到账金额
     *
     * @param order          订单（持久态）
     * @param confirmedAmount 链上确认的实际到账金额
     */
    @Transactional(rollbackFor = Exception.class)
    public void markCompleted(CoinRechargeOrder order, BigDecimal confirmedAmount) {
        order.setAmount(confirmedAmount);
        order.setStatus(CoinRechargeOrderStatus.COMPLETED);
        coinRechargeOrderDao.save(order);
    }

    /**
     * 将订单标记为失败，记录失败原因
     *
     * @param order  订单（持久态）
     * @param reason 失败原因
     */
    @Transactional(rollbackFor = Exception.class)
    public void markFailed(CoinRechargeOrder order, String reason) {
        order.setStatus(CoinRechargeOrderStatus.FAILED);
        order.setRemark(reason);
        coinRechargeOrderDao.save(order);
    }

    /**
     * 将订单标记为已取消，记录取消原因
     *
     * @param order  订单（持久态）
     * @param reason 取消原因
     */
    @Transactional(rollbackFor = Exception.class)
    public void markCancelled(CoinRechargeOrder order, String reason) {
        order.setStatus(CoinRechargeOrderStatus.CANCELLED);
        order.setRemark(reason);
        coinRechargeOrderDao.save(order);
    }

    /**
     * 查询超时未支付的 PENDING 订单（创建时间早于指定时间）
     *
     * @param expireMinutes 超时分钟数
     * @return 超时订单列表
     */
    @Transactional(readOnly = true)
    public java.util.List<CoinRechargeOrder> findAllPendingExpired(int expireMinutes) {
        // 计算超时截止时间
        Date beforeTime = new Date(System.currentTimeMillis() - (long) expireMinutes * 60 * 1000);
        return coinRechargeOrderDao.findAllByStatusAndCreateTimeBefore(
                CoinRechargeOrderStatus.PENDING, beforeTime);
    }

    // ----------------------------- 私有方法 -----------------------------

    /**
     * 构建充值订单实体
     *
     * @param memberId      会员ID
     * @param coin          子币种
     * @param networkConfig 主网配置
     * @param amount        充值金额
     * @param orderNo       订单号
     * @return 订单实体（未持久化）
     */
    private CoinRechargeOrder buildOrder(Long memberId, Coin coin,
                                          CoinNetworkConfig networkConfig,
                                          BigDecimal amount, String orderNo) {
        CoinRechargeOrder order = new CoinRechargeOrder();
        order.setOrderNo(orderNo);
        order.setMemberId(memberId);
        order.setCoinName(coin.getName());
        order.setNetworkConfigId(networkConfig.getId());
        order.setNetworkName(networkConfig.getNetwork().getName());
        order.setRechargeAddress(networkConfig.getRechargeAddress());
        order.setAmount(amount);
        order.setStatus(CoinRechargeOrderStatus.PENDING);
        return order;
    }

    /**
     * 回写 hash 并更新状态为待链上确认
     *
     * @param order  订单（持久态）
     * @param txHash 链上交易 hash
     */
    private void updateHashAndStatus(CoinRechargeOrder order, String txHash) {
        order.setTxHash(txHash);
        order.setStatus(CoinRechargeOrderStatus.CONFIRMING);
        order.setSubmitHashTime(new Date());
    }

    /**
     * 生成唯一订单号：RC + yyyyMMddHHmmss + 6位随机数
     * 若冲突则重试，最多重试 5 次
     *
     * @return 唯一订单号
     */
    private String generateOrderNo() {
        String prefix = "RC" + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
        Random random = new Random();
        for (int i = 0; i < 5; i++) {
            String orderNo = prefix + String.format("%06d", random.nextInt(1000000));
            if (!coinRechargeOrderDao.existsByOrderNo(orderNo)) {
                return orderNo;
            }
        }
        // 极低概率走到这里，追加毫秒数兜底
        return prefix + System.currentTimeMillis() % 1000000;
    }
}
