package com.market.manager;

import com.market.client.BinanceWebSocketClient;
import com.market.entity.ContractCoin;
import com.market.service.ContractCoinService;
import com.market.service.KlineService;
import com.market.utils.KlineUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.*;
import java.util.stream.Collectors;

/**
 * WebSocket 连接管理
 *
 * <p>首次启动全量连接；定时刷新时做增量 diff：
 * <ul>
 *   <li>新增 symbol → SUBSCRIBE 追加，不断开现有连接</li>
 *   <li>移除 symbol → UNSUBSCRIBE 取消</li>
 *   <li>无变化 → 直接跳过</li>
 * </ul>
 */
@Slf4j
@Component
public class WebSocketManager {


    @Autowired
    private BinanceWebSocketClient binanceClient;

    @Autowired
    private ContractCoinService contractCoinService;

    @Autowired
    private KlineService klineTableService;

    @Autowired
    private KlineUtils klineUtils;

    /** 当前已订阅的 stream 集合 */
    private volatile Set<String> lastStreamSet = new HashSet<>();

    @PostConstruct
    public void init() {
        refreshAndConnect();
    }

    @Scheduled(fixedDelayString = "${market.refresh-seconds:60}000")
    public void refreshAndConnect() {
        List<String> symbols = getEnabledSymbols();
        if (symbols.isEmpty()) {
            log.warn("数据库中没有启用的合约交易对");
            return;
        }

        // 确保每个 symbol 的 kline 分表存在
        symbols.forEach(klineTableService::ensureTable);

        Set<String> newStreamSet = new HashSet<>(buildStreams(symbols));

        if (!binanceClient.isConnected()) {
            // 连接断开（首次启动 or 重连）：全量建立连接
            log.info("重新连接，订阅 {} 个 symbol，{} 个 stream", symbols.size(), newStreamSet.size());
            binanceClient.connect(new ArrayList<>(newStreamSet));
            lastStreamSet = newStreamSet;
            return;
        }

        if (newStreamSet.equals(lastStreamSet)) {
            return;
        }

        // 增量 diff
        Set<String> added = new HashSet<>(newStreamSet);
        added.removeAll(lastStreamSet);

        Set<String> removed = new HashSet<>(lastStreamSet);
        removed.removeAll(newStreamSet);

        if (!removed.isEmpty()) {
            log.info("取消订阅 {} 个 stream: {}", removed.size(), removed);
            binanceClient.unsubscribe(new ArrayList<>(removed));
        }
        if (!added.isEmpty()) {
            log.info("新增订阅 {} 个 stream: {}", added.size(), added);
            binanceClient.subscribe(new ArrayList<>(added));
        }

        lastStreamSet = newStreamSet;
    }

    private List<String> getEnabledSymbols() {
        try {
            List<ContractCoin> coins = contractCoinService.listEnabled();
            // 重建 symbol → contractId 缓存，移除已禁用的交易对
            Set<String> activeSymbols = new HashSet<>();
            coins.stream()
                    .filter(c -> StringUtils.isNotBlank(c.getSymbol()) && c.getId() != null)
                    .forEach(c -> {
                        String sym = c.getSymbol().replace("/", "").toLowerCase();
                        klineUtils.SYMBOL_ID_MAP.put(sym, c.getId());
                        activeSymbols.add(sym);
                    });
            klineUtils.SYMBOL_ID_MAP.keySet().retainAll(activeSymbols);
            return coins.stream()
                    .map(ContractCoin::getSymbol)
                    .filter(StringUtils::isNotBlank)
                    .map(s -> s.replace("/", "").toLowerCase())
                    .distinct()
                    .collect(Collectors.toList());
        } catch (Exception e) {
            log.error("查询合约交易对失败", e);
            return new ArrayList<>();
        }
    }

    private List<String> buildStreams(List<String> symbols) {
        List<String> streams = new ArrayList<>();
        for (String symbol : symbols) {
            for (String interval : KlineUtils.PERIODS) {
                streams.add(KlineUtils.klineStream(symbol, interval));
            }
            streams.add(symbol + KlineUtils.STREAM_AGG_TRADE);
            streams.add(symbol + KlineUtils.STREAM_MARK_PRICE);
            streams.add(symbol + KlineUtils.STREAM_MINI_TICKER);
        }
        return streams;
    }
}
