package com.market.manager;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.market.model.binance.SpotKlineMessage;
import com.market.model.binance.SpotTickerMessage;
import com.market.model.binance.SpotTradeMessage;
import com.market.service.SpotDataProcessService;
import com.market.utils.SpotConstant;
import io.netty.channel.*;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.websocketx.*;
import io.netty.handler.timeout.IdleStateEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * 现货 Binance WebSocket Netty 消息处理器
 * <p>
 * 参照合约 BinanceWebSocketHandler 实现，收到消息后直接在 Netty I/O 线程内处理，
 * 节流逻辑在 Handler 层完成。
 *
 * @author system
 * @date 2026-05-10
 */
public class BinanceSpotWebSocketHandler extends SimpleChannelInboundHandler<Object> {

    private static final Logger log = LoggerFactory.getLogger(BinanceSpotWebSocketHandler.class);

    /**
     * per-symbol 单线程执行器：Netty I/O 线程只做解析和分发，业务逻辑在此执行器内并行处理。
     * 同一 symbol 的消息顺序执行，不同 symbol 完全并行互不阻塞。
     */
    private static final Map<String, ExecutorService> SYMBOL_EXECUTOR_MAP = new ConcurrentHashMap<>();

    /** 逐笔成交节流：key=symbol → 上次处理时间戳，500ms 一次 */
    private static final Cache<String, Long> TRADE_THROTTLE = Caffeine.newBuilder()
            .expireAfterWrite(30, TimeUnit.SECONDS)
            .maximumSize(5000)
            .build();

    /** 24h MiniTicker 节流：key=symbol → 上次处理时间戳，1s 一次 */
    private static final Cache<String, Long> TICKER_THROTTLE = Caffeine.newBuilder()
            .expireAfterWrite(30, TimeUnit.SECONDS)
            .maximumSize(5000)
            .build();

    /** WebSocket 握手器 */
    private final WebSocketClientHandshaker handshaker;
    /** JSON 序列化 */
    private final ObjectMapper objectMapper;
    /** 现货行情处理服务 */
    private final SpotDataProcessService dataProcessService;
    /** WebSocket 握手完成 Future */
    private ChannelPromise handshakeFuture;

    public BinanceSpotWebSocketHandler(WebSocketClientHandshaker handshaker,
                                       ObjectMapper objectMapper,
                                       SpotDataProcessService dataProcessService) {
        this.handshaker = handshaker;
        this.objectMapper = objectMapper;
        this.dataProcessService = dataProcessService;
    }

    /**
     * 返回握手 Future，供 connect() 调用方同步等待握手完成
     *
     * @return 握手 ChannelFuture
     */
    public ChannelFuture handshakeFuture() {
        return handshakeFuture;
    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) {
        handshakeFuture = ctx.newPromise();
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        handshaker.handshake(ctx.channel());
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) {
        // 连接断开后由 BinanceSpotWebSocketManager 的定时任务检测 isConnected()==false 并重连
        log.warn("现货 WebSocket 连接断开: {}", ctx.channel().remoteAddress());
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            log.warn("现货 WebSocket 读空闲超时，主动关闭以触发重连");
            ctx.close();
        } else {
            super.userEventTriggered(ctx, evt);
        }
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        Channel ch = ctx.channel();

        // 握手阶段：完成 HTTP → WebSocket 升级
        if (!handshaker.isHandshakeComplete()) {
            handshaker.finishHandshake(ch, (FullHttpResponse) msg);
            if (ctx.pipeline().get(HttpObjectAggregator.class) != null) {
                ctx.pipeline().remove(HttpObjectAggregator.class);
            }
            log.info("现货 WebSocket 握手完成: {} | handlers: {}",
                    ch.remoteAddress(), ctx.pipeline().names());
            handshakeFuture.setSuccess();
            return;
        }

        if (msg instanceof TextWebSocketFrame) {
            handleText(((TextWebSocketFrame) msg).text());
        } else if (msg instanceof PingWebSocketFrame) {
            log.debug("现货收到 Ping，回复 Pong");
            ctx.writeAndFlush(new PongWebSocketFrame(((PingWebSocketFrame) msg).content().retain()));
        } else if (msg instanceof CloseWebSocketFrame) {
            log.info("现货收到 Close 帧，关闭连接");
            ch.close();
        } else {
            log.warn("现货收到未知帧类型: {}", msg.getClass().getSimpleName());
        }
    }

    // ------------------------------------------------------------------ private

    /**
     * 解析顶层 JSON，提取 symbol 后投递到 per-symbol 执行器。
     * Netty I/O 线程只做轻量解析（readTree + 字段读取），立即返回。
     */
    private void handleText(String text) {
        try {
            JsonNode root = objectMapper.readTree(text);
            JsonNode data = root.has(SpotConstant.FIELD_BINANCE_DATA)
                    ? root.get(SpotConstant.FIELD_BINANCE_DATA) : root;

            // 提取 symbol 用于路由，kline 事件的 symbol 在 k.s，其余在 s
            String eventType = data.has(SpotConstant.FIELD_BINANCE_EVENT)
                    ? data.get(SpotConstant.FIELD_BINANCE_EVENT).asText() : "";
            String symbol = "kline".equals(eventType)
                    ? data.path("k").path("s").asText("").toLowerCase()
                    : data.path("s").asText("").toLowerCase();

            if (symbol.isEmpty()) {
                return;
            }
            // 投递到 per-symbol 单线程执行器，Netty I/O 线程立即返回
            final JsonNode finalData = data;
            dispatchToSymbol(symbol, () -> {
                try {
                    handleEvent(finalData);
                } catch (Exception e) {
                    log.warn("现货处理事件失败: symbol={}", symbol, e);
                }
            });
        } catch (Exception e) {
            log.warn("现货解析消息失败: {}",
                    text.length() > 200 ? text.substring(0, 200) + "..." : text, e);
        }
    }

    /**
     * 将任务投递到指定 symbol 的专属单线程执行器。
     * 队列满时丢弃最旧的任务（行情数据允许丢弃旧帧）。
     *
     * @param symbol 交易对（小写）
     * @param task   待执行任务
     */
    private static void dispatchToSymbol(String symbol, Runnable task) {
        SYMBOL_EXECUTOR_MAP
                .computeIfAbsent(symbol, k -> new ThreadPoolExecutor(
                        8, 8, 60L, TimeUnit.SECONDS,
                        new LinkedBlockingQueue<>(512),
                        r -> new Thread(r, "spot-ev-" + k),
                        new ThreadPoolExecutor.DiscardOldestPolicy()))
                .execute(task);
    }

    /**
     * 事件路由：按 eventType 分发到对应处理逻辑，trade/miniTicker 在此做节流过滤
     *
     * @param data 事件 JSON 节点
     */
    private void handleEvent(JsonNode data) throws Exception {
        String eventType = data.has(SpotConstant.FIELD_BINANCE_EVENT)
                ? data.get(SpotConstant.FIELD_BINANCE_EVENT).asText() : "";

        switch (eventType) {
            case "kline": {
                // K 线不节流，每条消息都处理
                dataProcessService.processKlineMessage(
                        objectMapper.treeToValue(data, SpotKlineMessage.class));
                break;
            }
            case "trade": {
                // 逐笔成交 500ms 节流
                String symbol = data.get("s").asText().toLowerCase();
                long now = System.currentTimeMillis();
                Long last = TRADE_THROTTLE.getIfPresent(symbol);
                if (last == null || now - last >= SpotConstant.THROTTLE_TRADE_MS) {
                    TRADE_THROTTLE.put(symbol, now);
                    dataProcessService.processTradeMessage(
                            objectMapper.treeToValue(data, SpotTradeMessage.class));
                }
                break;
            }
            case "24hrMiniTicker": {
                // 24h MiniTicker 1s 节流
                String symbol = data.get("s").asText().toLowerCase();
                long now = System.currentTimeMillis();
                Long last = TICKER_THROTTLE.getIfPresent(symbol);
                if (last == null || now - last >= SpotConstant.THROTTLE_TICKER_MS) {
                    TICKER_THROTTLE.put(symbol, now);
                    dataProcessService.processTickerMessage(
                            objectMapper.treeToValue(data, SpotTickerMessage.class));
                }
                break;
            }
            default:
                log.debug("现货未知事件类型: {}", eventType);
                break;
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        log.error("现货 WebSocket 异常", cause);
        if (!handshakeFuture.isDone()) {
            handshakeFuture.setFailure(cause);
        }
        ctx.close();
    }
}
