package com.spot.start;

import com.alibaba.fastjson.JSON;
import com.spot.config.MatchingEngine;
import com.spot.dto.mq.TradeResult;
import com.spot.entity.ConfigCoinSymbol;
import com.spot.entity.ConfigSymbol;
import com.spot.entity.ExOrder;
import com.spot.enums.OrderStatus;
import com.spot.service.ConfigCoinSymbolService;
import com.spot.service.ConfigSymbolService;
import com.spot.service.ExOrderService;
import com.spot.service.NewMatchingEngine;
import com.spot.service.RedisLatestPriceService;
import com.spot.utils.Constant;
import com.spot.utils.PriceCache;
import com.spot.utils.TimeUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.kafka.clients.admin.AdminClient;
import org.apache.kafka.clients.admin.NewTopic;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.kafka.core.KafkaAdmin;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 订单数据加载器 - 项目启动时从数据库加载订单数据到内存，并处理排队订单
 *
 * @author jean
 * @date 2026-05-07
 */
@Component
@Slf4j
public class OrderDataLoader implements CommandLineRunner {

    @Autowired
    private ConfigSymbolService configSymbolService;

    @Autowired
    private ConfigCoinSymbolService configCoinSymbolService;

    @Autowired
    private ExOrderService exOrderService;

    @Autowired
    private RedisLatestPriceService redisLatestPriceService;

    @Autowired
    private MatchingEngine matchingEngine;

    @Autowired
    private NewMatchingEngine newMatchingEngine;

    @Autowired
    private KafkaTemplate<String, String> kafkaTemplate;

    @Autowired
    private KafkaAdmin kafkaAdmin;

    @Value("${kafka.topic.trade:SPOT_TRADE_}")
    private String tradeTopic;

    /**
     * 排队缓存 Map（状态0,5的订单，按时间排序，不分买卖方向）
     * Key: symbol；Value: 订单列表（按创建时间升序）
     */
    private static final Map<String, List<ExOrder>> QUEUE_CACHE = new ConcurrentHashMap<>();

    /** 活跃订单状态：new(1)、part_filled(3) */
    private static final List<Integer> ACTIVE_STATUSES = Arrays.asList(
            OrderStatus.NEW.getCode(), OrderStatus.PART_FILLED.getCode());

    /** 排队订单状态：init(0)、pending_cancel(5) */
    private static final List<Integer> QUEUE_STATUSES = Arrays.asList(
            OrderStatus.INIT.getCode(), OrderStatus.PENDING_CANCEL.getCode());

    @Override
    public void run(String... args) throws Exception {
        log.info("开始执行订单数据加载器初始化...");
        long startTime = System.currentTimeMillis();
        try {
            // 1. 获取所有开放交易的币对
            List<ConfigSymbol> openConfigSymbols = configSymbolService.getOpenSymbols();
            log.info("步骤1完成: 获取到 {} 个开放交易币对", openConfigSymbols.size());
            if (openConfigSymbols.isEmpty()) {
                log.warn("没有找到开放交易的币对，跳过订单数据加载");
                return;
            }

            List<String> openSymbols = openConfigSymbols.stream()
                    .map(ConfigSymbol::getSymbol)
                    .collect(Collectors.toList());

            // 2. 预创建所有币对的 Kafka Topic，确保消费者启动时元数据已就绪
            ensureTopicsExist(openSymbols);
            log.info("步骤2完成: Kafka Topic 预创建完成，共 {} 个交易对", openSymbols.size());

            // 3. 加载币种配置到缓存
            List<ConfigCoinSymbol> openCoinSymbols = configCoinSymbolService.getOpenCoinSymbols();
            for (ConfigCoinSymbol coinSymbol : openCoinSymbols) {
                if (coinSymbol != null && coinSymbol.getCoinSymbol() != null) {
                    Constant.COIN_SYMBOL_CONFIG_CACHE.put(coinSymbol.getCoinSymbol(), coinSymbol);
                }
            }
            log.info("步骤3完成: 加载了 {} 个币种配置", openCoinSymbols.size());

            // 3. 加载交易对配置到缓存
            for (ConfigSymbol configSymbol : openConfigSymbols) {
                if (configSymbol != null && configSymbol.getSymbol() != null) {
                    Constant.SYMBOL_CONFIG_CACHE.put(configSymbol.getSymbol(), configSymbol);
                }
            }
            log.info("步骤4完成: 加载了 {} 个交易对配置", openConfigSymbols.size());

            // 5. 从数据库加载订单数据
            int totalOrdersLoaded = 0;
            for (String symbol : openSymbols) {
                try {
                    List<Integer> allStatuses = new ArrayList<>();
                    allStatuses.addAll(ACTIVE_STATUSES);
                    allStatuses.addAll(QUEUE_STATUSES);
                    List<ExOrder> orders = exOrderService.getOrdersBySymbolAndStatuses(symbol, allStatuses);
                    if (!orders.isEmpty()) {
                        totalOrdersLoaded += orders.size();
                        loadActiveOrders(symbol, orders);
                        loadQueueOrders(symbol, orders);
                    }
                } catch (Exception e) {
                    log.error("加载交易对 [{}] 的订单数据时发生错误，跳过该交易对", symbol, e);
                }
            }
            log.info("步骤5完成: 共加载 {} 条订单数据", totalOrdersLoaded);

            // 6. 从 Redis 初始化最新价格
            initLatestPricesFromRedis(openConfigSymbols);
            log.info("步骤6完成: Redis价格初始化完成");

            // 7. 多线程处理排队订单
            processQueueOrdersMultiThread();
            QUEUE_CACHE.clear();
            log.info("步骤7完成: 排队订单处理完成");

            long duration = (System.currentTimeMillis() - startTime) / 1000;
            log.info("订单数据加载器初始化完成！总耗时: {} 秒", duration);

        } catch (Exception e) {
            long duration = (System.currentTimeMillis() - startTime) / 1000;
            log.error("加载订单数据时发生严重错误，已耗时: {} 秒", duration, e);
            throw e;
        }
    }

    /** 加载活跃订单（状态1,3）到新引擎缓存 */
    private void loadActiveOrders(String symbol, List<ExOrder> allOrders) {
        List<ExOrder> activeOrders = allOrders.stream()
                .filter(order -> ACTIVE_STATUSES.contains(order.getStatus()) && order.getSide() != null)
                .collect(Collectors.toList());
        if (activeOrders.isEmpty()) {
            return;
        }
        Map<String, List<ExOrder>> groupedOrders = activeOrders.stream()
                .collect(Collectors.groupingBy(order -> symbol + "_" + order.getSide()));
        groupedOrders.forEach((key, orders) -> {
            try {
                NewMatchingEngine.getNewEngineCache().put(key,
                        new java.util.concurrent.CopyOnWriteArrayList<>(orders));
                log.info("新引擎缓存 [{}]: {} 条", key, orders.size());
            } catch (Exception e) {
                log.warn("加载新引擎缓存失败 - Key: {}", key, e);
            }
        });
    }

    /** 加载排队订单（状态0,5）到 QUEUE_CACHE，按创建时间升序 */
    private void loadQueueOrders(String symbol, List<ExOrder> allOrders) {
        List<ExOrder> queueOrders = allOrders.stream()
                .filter(order -> QUEUE_STATUSES.contains(order.getStatus()))
                .collect(Collectors.toList());
        if (!queueOrders.isEmpty()) {
            queueOrders.sort((o1, o2) -> {
                if (o1.getCtime() == null && o2.getCtime() == null) { return 0; }
                if (o1.getCtime() == null) { return 1; }
                if (o2.getCtime() == null) { return -1; }
                return o1.getCtime().compareTo(o2.getCtime());
            });
            QUEUE_CACHE.put(symbol, new java.util.concurrent.CopyOnWriteArrayList<>(queueOrders));
        }
    }

    /** 从 Redis 初始化所有交易对的最新价格 */
    private void initLatestPricesFromRedis(List<ConfigSymbol> openConfigSymbols) {
        if (openConfigSymbols == null || openConfigSymbols.isEmpty()) {
            return;
        }
        try {
            if (!redisLatestPriceService.testConnection()) {
                log.error("Redis连接失败，跳过价格初始化");
                return;
            }
            List<String> allSymbols = openConfigSymbols.stream()
                    .map(ConfigSymbol::getSymbol)
                    .collect(Collectors.toList());
            Map<String, java.math.BigDecimal> priceMap = redisLatestPriceService.batchGetLatestPrices(allSymbols);
            int successCount = 0;
            for (String symbol : allSymbols) {
                java.math.BigDecimal price = priceMap.get(symbol);
                if (price != null) {
                    PriceCache.updatePrice(symbol, price);
                    successCount++;
                }
            }
            log.info("Redis价格初始化完成 - 成功: {} / {}", successCount, allSymbols.size());
        } catch (Exception e) {
            log.error("从Redis初始化最新价格时发生错误", e);
        }
    }

    /** 多线程处理排队订单 */
    private void processQueueOrdersMultiThread() {
        if (QUEUE_CACHE.isEmpty()) {
            return;
        }
        log.info("开始多线程处理排队订单，共 {} 个交易对", QUEUE_CACHE.size());
        int threadPoolSize = Math.min(QUEUE_CACHE.size(), Math.max(2, Runtime.getRuntime().availableProcessors()));
        ExecutorService executorService = Executors.newFixedThreadPool(threadPoolSize);
        CountDownLatch latch = new CountDownLatch(QUEUE_CACHE.size());
        final int[] totalProcessed = {0};
        final int[] totalMatched = {0};

        for (Map.Entry<String, List<ExOrder>> entry : QUEUE_CACHE.entrySet()) {
            final String symbol = entry.getKey();
            final List<ExOrder> queueOrders = entry.getValue();
            executorService.submit(() -> {
                try {
                    if (queueOrders == null || queueOrders.isEmpty()) {
                        return;
                    }
                    int symbolProcessed = 0;
                    int symbolMatched = 0;
                    int symbolCancelled = 0;
                    for (ExOrder order : new ArrayList<>(queueOrders)) {
                        try {
                            symbolProcessed++;
                            if (OrderStatus.INIT.getCode().equals(order.getStatus())) {
                                if (matchingEngine.canProcess(order)) {
                                    TradeResult result = matchingEngine.processInitOrder(order);
                                    if (result.isMatched()) {
                                        symbolMatched++;
                                    }
                                }
                            } else if (OrderStatus.PENDING_CANCEL.getCode().equals(order.getStatus())) {
                                processCancelOrder(symbol, order);
                                symbolCancelled++;
                            }
                        } catch (Exception e) {
                            log.error("处理排队订单异常 - OrderId: {}, Symbol: {}", order.getId(), symbol, e);
                        }
                    }
                    log.info("排队订单处理完成 - Symbol: {}, 处理: {}, 成交: {}, 撤单: {}",
                            symbol, symbolProcessed, symbolMatched, symbolCancelled);
                    synchronized (totalProcessed) {
                        totalProcessed[0] += symbolProcessed;
                        totalMatched[0] += symbolMatched;
                    }
                } catch (Exception e) {
                    log.error("处理交易对 [{}] 的排队订单时发生严重错误", symbol, e);
                } finally {
                    latch.countDown();
                }
            });
        }

        try {
            boolean completed = latch.await(30, TimeUnit.MINUTES);
            if (!completed) {
                log.warn("排队订单处理超时（30分钟），总处理: {} 条，成功撮合: {} 条",
                        totalProcessed[0], totalMatched[0]);
            } else {
                log.info("排队订单处理完成！总处理: {} 条，成功撮合: {} 条", totalProcessed[0], totalMatched[0]);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.warn("等待排队订单处理完成时被中断");
        } finally {
            executorService.shutdown();
        }
    }

    /**
     * 处理撤单订单（状态5：pending_cancel）
     *
     * @param symbol 交易对
     * @param order  待撤单的订单
     */
    private void processCancelOrder(String symbol, ExOrder order) {
        try {
            // 1. 更新订单状态为已撤单
            order.setStatus(OrderStatus.CANCELED.getCode());
            order.setMtime(TimeUtils.now());

            // 2. 从活跃订单缓存中移除
            removeOrderFromCache(order);

            // 3. 发送撤单结果到 Kafka
            sendCancelResultToKafka(symbol, order);

        } catch (Exception e) {
            log.error("处理撤单失败 - Symbol: {}, OrderId: {}", symbol, order.getId(), e);
            throw e;
        }
    }

    /** 从新引擎缓存中移除订单 */
    private void removeOrderFromCache(ExOrder order) {
        if (order == null || order.getId() == null || order.getSymbol() == null || order.getSide() == null) {
            return;
        }
        try {
            String key = order.getSymbol().toUpperCase() + "_" + order.getSide();
            List<ExOrder> orders = NewMatchingEngine.getNewEngineCache().get(key);
            if (orders != null) {
                orders.removeIf(o -> o != null && o.getId() != null &&
                        java.util.Objects.equals(o.getId(), order.getId()));
            }
        } catch (Exception e) {
            log.error("从缓存中移除订单时发生错误 - OrderId: {}", order.getId(), e);
        }
    }

    /**
     * 发送撤单结果到 Kafka
     *
     * @param symbol        交易对
     * @param canceledOrder 被撤销的订单
     */
    private void sendCancelResultToKafka(String symbol, ExOrder canceledOrder) {
        if (symbol == null || canceledOrder == null) {
            return;
        }
        try {
            TradeResult cancelResult = TradeResult.createCancelResult(canceledOrder);
            String messageId = canceledOrder.getId() + "_" + System.currentTimeMillis() + "_"
                    + UUID.randomUUID().toString().replace("-", "").substring(0, 8);
            cancelResult.setMessageId(messageId);
            String topic = tradeTopic + symbol.toUpperCase();
            kafkaTemplate.send(topic, messageId, JSON.toJSONString(cancelResult));
            log.info("撤单结果已发送 Kafka - Symbol: {}, OrderId: {}", symbol, canceledOrder.getId());
        } catch (Exception e) {
            log.error("发送撤单结果到 Kafka 失败 - Symbol: {}, OrderId: {}", symbol, canceledOrder.getId(), e);
        }
    }

    /**
     * 为所有开放币对预创建 Kafka Topic（SPOT_MATCH_ / SPOT_TRADE_）
     * 确保消费者启动时元数据中已存在这些 Topic，topicPattern 订阅立即生效
     *
     * @param symbols 开放币对列表
     */
    private void ensureTopicsExist(List<String> symbols) {
        try (AdminClient adminClient = AdminClient.create(kafkaAdmin.getConfigurationProperties())) {
            Collection<NewTopic> newTopics = new ArrayList<>();
            for (String symbol : symbols) {
                String upperSymbol = symbol.toUpperCase();
                // 撮合输入 Topic
                newTopics.add(new NewTopic(Constant.KAFKA_TOPIC_MATCH + upperSymbol, 1, (short) 1));
                // 撮合结果 Topic
                newTopics.add(new NewTopic(Constant.KAFKA_TOPIC_TRADE + upperSymbol, 1, (short) 1));
            }
            // createTopics 对已存在的 Topic 会忽略，不会报错
            adminClient.createTopics(newTopics).all().get(10, TimeUnit.SECONDS);
            log.info("Kafka Topic 预创建完成 - 涉及币对: {}", symbols);
        } catch (Exception e) {
            // Topic 预创建失败不阻断启动流程，依赖 metadata.max.age.ms 兜底
            log.warn("Kafka Topic 预创建异常（不影响启动）: {}", e.getMessage());
        }
    }

    /** 清空所有缓存（供测试使用） */
    public static void clearCache() {
        QUEUE_CACHE.clear();
    }
}
