package com.spot.job;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.spot.dto.mq.OrderMessage;
import com.spot.entity.ExOrder;
import com.spot.enums.OrderStatus;
import com.spot.mapper.ExOrderMapper;
import com.spot.service.OrderService;
import com.spot.utils.Constant;
import com.spot.utils.TimeUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * 超时初始化订单撤单定时任务
 * 每30秒扫描一次，将超过3分钟且状态为 INIT(0) 或 PENDING_CANCEL(5) 的订单执行撤单
 *
 * @author jean
 * @date 2026-05-06
 */
@Slf4j
@Component
public class CancelTimeoutInitOrderJob {

    @Autowired
    private ExOrderMapper exOrderMapper;

    @Autowired
    private OrderService orderService;

    @Autowired
    private KafkaTemplate<String, String> kafkaTemplate;

    @Value("${kafka.topic.match:SPOT_MATCH_}")
    private String matchTopic;

    /** 已发送撤单消息的 orderId 缓存，1小时内不重复发送 */
    private static final Cache<Long, Boolean> SENT_CACHE = Caffeine.newBuilder()
            .expireAfterWrite(1, TimeUnit.HOURS)
            .build();

    /**
     * 每30秒执行一次超时订单撤单检查
     */
    @Scheduled(fixedDelay = 30_000)
    public void execute() {
        log.info("========== 开始执行超时初始化订单撤单定时任务 ==========");

        try {
            // 1. 查询超过指定分钟的 INIT/PENDING_CANCEL 订单
            LocalDateTime threeMinutesAgo = LocalDateTime.now().minusMinutes(Constant.ORDER_TIMEOUT_MINUTES);
            LambdaQueryWrapper<ExOrder> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.in(ExOrder::getStatus,
                            OrderStatus.INIT.getCode(),
                            OrderStatus.PENDING_CANCEL.getCode())
                    .lt(ExOrder::getMtime, threeMinutesAgo)
                    .orderByAsc(ExOrder::getMtime);

            List<ExOrder> timeoutOrders = exOrderMapper.selectList(queryWrapper);

            if (timeoutOrders == null || timeoutOrders.isEmpty()) {
                log.debug("未找到需要撤单的超时订单");
                return;
            }

            log.info("找到 {} 个超时订单，开始撤单处理", timeoutOrders.size());

            int successCount = 0;
            int failureCount = 0;

            // 2. 逐笔处理：事务内更新状态，事务外发送 MQ
            for (ExOrder order : timeoutOrders) {
                try {
                    // 缓存过滤：1小时内已发送过撤单消息则跳过
                    if (SENT_CACHE.getIfPresent(order.getId()) != null) {
                        log.debug("撤单消息已发送过，跳过 - OrderId: {}", order.getId());
                        continue;
                    }
                    boolean updated = orderService.cancelTimeoutInitOrder(order);
                    if (updated) {
                        // 事务提交后发送撤单 MQ，并记入缓存
                        OrderMessage cancelMessage = buildCancelOrderMessage(order);
                        sendCancelOrderMessage(cancelMessage, order.getSymbol());
                        SENT_CACHE.put(order.getId(), Boolean.TRUE);
                        successCount++;
                    } else {
                        failureCount++;
                    }
                } catch (Exception e) {
                    failureCount++;
                    log.error("处理超时订单失败 - OrderId: {}", order.getId(), e);
                }
            }

            log.info("========== 超时订单撤单任务完成 - 总数: {}, 成功: {}, 失败: {} ==========",
                    timeoutOrders.size(), successCount, failureCount);

        } catch (Exception e) {
            log.error("执行超时订单撤单任务异常", e);
        }
    }

    /**
     * 构建撤单 OrderMessage
     *
     * @param order 订单信息
     * @return 撤单消息
     */
    private OrderMessage buildCancelOrderMessage(ExOrder order) {
        OrderMessage msg = new OrderMessage();
        msg.setId(order.getId());
        msg.setUserId(order.getUserId());
        msg.setSymbol(order.getSymbol());
        msg.setSide(order.getSide());
        msg.setType(order.getType());
        msg.setStatus(OrderStatus.PENDING_CANCEL.getCode());
        msg.setPrice(order.getPrice());
        msg.setVolume(order.getVolume());
        msg.setFeeRateMaker(order.getFeeRateMaker());
        msg.setFeeRateTaker(order.getFeeRateTaker());
        msg.setFee(order.getFee() != null ? order.getFee() : BigDecimal.ZERO);
        msg.setDealVolume(order.getDealVolume() != null ? order.getDealVolume() : BigDecimal.ZERO);
        msg.setDealMoney(order.getDealMoney() != null ? order.getDealMoney() : BigDecimal.ZERO);
        msg.setAvgPrice(order.getAvgPrice() != null ? order.getAvgPrice() : BigDecimal.ZERO);
        msg.setSource(order.getSource());
        msg.setRefundVolume(order.getRefundVolume() != null ? order.getRefundVolume() : BigDecimal.ZERO);
        msg.setCtime(order.getCtime());
        msg.setMtime(TimeUtils.now());
        msg.setTimestamp(System.currentTimeMillis());
        msg.setMessageId(UUID.randomUUID().toString().replace("-", ""));
        return msg;
    }

    /**
     * 发送撤单消息到 Kafka（失败仅记录日志，不影响任务执行）
     *
     * @param cancelMessage 撤单消息
     * @param symbol        交易对
     */
    private void sendCancelOrderMessage(OrderMessage cancelMessage, String symbol) {
        try {
            String topic = matchTopic + symbol;
            String messageBody = JSON.toJSONString(cancelMessage);
            // 使用 messageId（UUID）作为 Kafka key，避免与 INIT 消息相同 key 被幂等过滤
            String kafkaKey = cancelMessage.getMessageId() != null ? cancelMessage.getMessageId() : String.valueOf(cancelMessage.getId());
            kafkaTemplate.send(topic, kafkaKey, messageBody)
                    .addCallback(
                            result -> log.info("撤单消息发送成功 - OrderId: {}, Symbol: {}, Topic: {}",
                                    cancelMessage.getId(), symbol, topic),
                            ex -> log.error("发送撤单消息失败 - OrderId: {}, Symbol: {}",
                                    cancelMessage.getId(), symbol, ex)
                    );
        } catch (Exception e) {
            log.error("发送撤单消息异常 - OrderId: {}, Symbol: {}", cancelMessage.getId(), symbol, e);
        }
    }
}
