package com.market.client;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.market.model.CoinThumb;
import com.market.service.KlineService;
import com.market.utils.KlineUtils;
import com.market.websocket.FrontendWebSocketHandler;
import io.netty.channel.*;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.websocketx.*;
import io.netty.handler.timeout.IdleStateEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;

import java.math.BigDecimal;
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;

/**
 * Netty WebSocket 消息处理器
 *
 * <p>收到币安推送后直接按 symbol 路由推送给已订阅的前端 session，
 */
@Slf4j
public class BinanceWebSocketHandler extends SimpleChannelInboundHandler<Object> {

    private static final String REDIS_PRICE_KEY = "NEW_COIN_PRICE:";

    private final WebSocketClientHandshaker handshaker;
    private final ObjectMapper objectMapper;
    private final FrontendWebSocketHandler frontendWsHandler;
    private final KlineService klineService;
    private final KlineUtils klineUtils;
    private final com.market.service.impl.FundingRateServiceImpl fundingRateService;
    private final RedisTemplate<String, Object> redisTemplate;
    private ChannelPromise handshakeFuture;

    public BinanceWebSocketHandler(WebSocketClientHandshaker handshaker,
                                   ObjectMapper objectMapper,
                                   FrontendWebSocketHandler frontendWsHandler,
                                   KlineService klineService,
                                   KlineUtils klineUtils,
                                   com.market.service.impl.FundingRateServiceImpl fundingRateService,
                                   RedisTemplate<String, Object> redisTemplate) {
        this.handshaker = handshaker;
        this.objectMapper = objectMapper;
        this.frontendWsHandler = frontendWsHandler;
        this.klineService = klineService;
        this.klineUtils = klineUtils;
        this.fundingRateService = fundingRateService;
        this.redisTemplate = redisTemplate;
    }

    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) {
        log.warn("WebSocket 连接断开: {}", ctx.channel().remoteAddress());
        // channel 关闭后 WebSocketManager 的定时任务会检测到 isConnected()==false 并自动重连
    }

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

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        Channel ch = ctx.channel();
        if (!handshaker.isHandshakeComplete()) {
            handshaker.finishHandshake(ch, (FullHttpResponse) msg);
            // WebSocket 升级完成后，HttpObjectAggregator 不再需要
            // 保留它会导致部分 Netty 版本静默丢弃后续 WebSocketFrame
            if (ctx.pipeline().get(io.netty.handler.codec.http.HttpObjectAggregator.class) != null) {
                ctx.pipeline().remove(io.netty.handler.codec.http.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.info("收到 Binance 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 void handleText(String text) {
        try {
            JsonNode root = objectMapper.readTree(text);
            JsonNode data = root.has("data") ? root.get("data") : root;

            // !miniTicker@arr 返回数组，逐条处理
            if (data.isArray()) {
                for (JsonNode item : data) {
                    dispatchEvent(item);
                }
                return;
            }
            dispatchEvent(data);
        } catch (Exception e) {
            log.warn("解析消息失败: {}", text.length() > 200 ? text.substring(0, 200) + "..." : text, e);
        }
    }

    /**
     * 提取 symbol 后投递到 per-symbol 单线程执行器，Netty I/O 线程立即返回
     *
     * @param data 事件 JSON 节点
     */
    private void dispatchEvent(JsonNode data) {
        String eventType = data.has("e") ? data.get("e").asText() : "";
        String symbol = "kline".equals(eventType)
                ? data.path("k").path("s").asText("").toLowerCase()
                : data.path("s").asText("").toLowerCase();
        if (symbol.isEmpty()) {
            return;
        }
        SYMBOL_EXECUTOR_MAP
                .computeIfAbsent(symbol, k -> new ThreadPoolExecutor(
                        8, 8, 60L, TimeUnit.SECONDS,
                        new LinkedBlockingQueue<>(512),
                        r -> new Thread(r, "contract-ev-" + k),
                        new ThreadPoolExecutor.DiscardOldestPolicy()))
                .execute(() -> {
                    try {
                        handleEvent(data);
                    } catch (Exception e) {
                        log.warn("合约处理事件失败: symbol={}", symbol, e);
                    }
                });
    }

    private void handleEvent(JsonNode data) throws Exception {
        String eventType = data.has("e") ? data.get("e").asText() : "";
        switch (eventType) {
            case "kline": {
                JsonNode k = data.get("k");
                String symbol = k.get("s").asText().toLowerCase();
                // 币安返回 "1M"（月线），内部统一用 "1mon" 存储，避免 MySQL 大小写不敏感冲突
                String interval = "1M".equals(k.get("i").asText()) ? "1mon" : k.get("i").asText();
                klineService.processKline(symbol, interval, k);
                if ("1m".equals(interval)) {
                    pushDepth(symbol, new BigDecimal(k.get("c").asText()));
                }
                frontendWsHandler.push(KlineUtils.klineTopic(symbol, interval), data);
                break;
            }
            case "aggTrade": {
                String symbol = data.get("s").asText().toLowerCase();
                long now = System.currentTimeMillis();
                Long lastTime = klineUtils.LAST_AGG_TRADE_TIME.getIfPresent(symbol);
                if (lastTime == null || now - lastTime >= KlineUtils.AGG_TRADE_THROTTLE_MS) {
                    klineUtils.LAST_AGG_TRADE_TIME.put(symbol, now);
                    String priceStr = data.get("p").asText();
                    // 每次成交都写 Redis 最新价
                    saveLatestPrice(symbol, priceStr);
                    frontendWsHandler.push(KlineUtils.tradeTopic(symbol), data);
                    // 向 market-contract 频道推送 CoinThumb，驱动引擎价格刷新
                    publishContractPrice(symbol, new BigDecimal(priceStr), now);
                }
                break;
            }
            case "24hrMiniTicker": {
                String symbol = data.get("s").asText().toLowerCase();
                long nowT = System.currentTimeMillis();
                Long lastTicker = klineUtils.LAST_TICKER_TIME.getIfPresent(symbol);
                if (lastTicker == null || nowT - lastTicker >= KlineUtils.THROTTLE_1S_MS) {
                    klineUtils.LAST_TICKER_TIME.put(symbol, nowT);
                    frontendWsHandler.push(KlineUtils.ticketTopic(symbol), data);
                }
                break;
            }
            case "markPriceUpdate": {
                String symbol = data.get("s").asText().toLowerCase();
                frontendWsHandler.push(KlineUtils.markTopic(symbol), data);
                // 资金费率 1 分钟更新一次 Redis
                if (data.has("r")) {
                    fundingRateService.tryUpdate(symbol, data.get("r").asText());
                }
                break;
            }
            default:
                break;
        }
    }

    /**
     * 将最新成交价封装为 CoinThumb 发布到 Redis market-contract 频道，
     * contract-swap-api 的 MarketListener 订阅该频道并驱动引擎价格刷新。
     *
     * <p>symbol 格式从小写无斜杠（btcusdt）转换为含斜杠大写（BTC/USDT），
     * 与引擎 matchMap 中的 key 格式保持一致。
     */
    private void publishContractPrice(String symbolLower, BigDecimal price, long nowMs) {
        try {
            // btcusdt → BTC/USDT
            String upper = symbolLower.toUpperCase();
            String symbol = upper.endsWith("USDT")
                    ? upper.substring(0, upper.length() - 4) + "/USDT"
                    : upper;

            CoinThumb thumb = new CoinThumb();
            thumb.setSymbol(symbol);
            thumb.setOpen(price);
            thumb.setHigh(price);
            thumb.setLow(price);
            thumb.setClose(price);
            thumb.setTime(nowMs / 1000);

            // Jackson ObjectNode → JSON 字符串，与 dage market-api 的 publish 格式一致
            String json = objectMapper.writeValueAsString(thumb);
            redisTemplate.convertAndSend("market-contract", json);
        } catch (Exception e) {
            log.warn("推送 market-contract 失败: symbol={}", symbolLower, e);
        }
    }

    private void saveLatestPrice(String symbol, String price) {
        try {
            Long contractId = klineUtils.SYMBOL_ID_MAP.get(symbol);
            if (contractId == null) return;
            redisTemplate.opsForHash().put(REDIS_PRICE_KEY, contractId.toString(), price);
        } catch (Exception e) {
            log.warn("最新成交价写 Redis 失败: symbol={}", symbol, e);
        }
    }

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

    private static final java.util.Random RANDOM = new java.util.Random();

    /**
     * 根据 close 价格生成 20 档买卖盘口，推送给 depth_mark 订阅者。
     * 每档步长 = close 最小小数位单位（如 close=43521.50 → 步长=0.01）。
     * 每档数量随机 1000~2000 U。
     */
    private void pushDepth(String symbol, BigDecimal close) {
        try {
            int levels = 20;
            BigDecimal step = BigDecimal.ONE.scaleByPowerOfTen(-close.scale());

            ArrayNode bids = objectMapper.createArrayNode();
            ArrayNode asks = objectMapper.createArrayNode();

            for (int i = 1; i <= levels; i++) {
                BigDecimal offset = step.multiply(BigDecimal.valueOf(i));
                BigDecimal bidPrice = close.subtract(offset);
                BigDecimal askPrice = close.add(offset);
                BigDecimal bidQty = BigDecimal.valueOf(1000 + RANDOM.nextInt(1001))
                        .divide(close, close.scale() + 4, java.math.RoundingMode.HALF_UP);
                BigDecimal askQty = BigDecimal.valueOf(1000 + RANDOM.nextInt(1001))
                        .divide(close, close.scale() + 4, java.math.RoundingMode.HALF_UP);

                ArrayNode bid = objectMapper.createArrayNode();
                bid.add(bidPrice.toPlainString());
                bid.add(bidQty.toPlainString());
                bids.add(bid);

                ArrayNode ask = objectMapper.createArrayNode();
                ask.add(askPrice.toPlainString());
                ask.add(askQty.toPlainString());
                asks.add(ask);
            }

            ObjectNode depth = objectMapper.createObjectNode();
            depth.put("e", "depth");
            depth.put("s", symbol);
            depth.set("bids", bids);
            depth.set("asks", asks);
            depth.put("ts", System.currentTimeMillis());

            frontendWsHandler.push(KlineUtils.depthTopic(symbol), depth);
        } catch (Exception e) {
            log.warn("生成盘口失败: {}", symbol, e);
        }
    }

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