package com.market.client;

import com.fasterxml.jackson.databind.ObjectMapper;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.DefaultHttpHeaders;
import io.netty.handler.codec.http.HttpClientCodec;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketClientHandshaker;
import io.netty.handler.codec.http.websocketx.WebSocketClientHandshakerFactory;
import io.netty.handler.codec.http.websocketx.WebSocketVersion;
import io.netty.handler.proxy.Socks5ProxyHandler;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PreDestroy;
import java.net.InetSocketAddress;
import java.net.URI;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 币安合约 WebSocket 客户端（Netty 实现）
 *
 */
@Slf4j
@Component
public class BinanceWebSocketClient {

    @Autowired
    private com.market.websocket.FrontendWebSocketHandler frontendWsHandler;

    @Autowired
    private com.market.service.KlineService klineService;

    @Autowired
    private com.market.utils.KlineUtils klineUtils;

    @Autowired
    private com.market.service.impl.FundingRateServiceImpl fundingRateService;

    @Autowired
    @Qualifier("jdkRedisTemplate")
    private org.springframework.data.redis.core.RedisTemplate<String, Object> redisTemplate;

    private final ObjectMapper objectMapper = new ObjectMapper();
    private final AtomicInteger reqId = new AtomicInteger(1);

    private NioEventLoopGroup group;
    private Channel channel;

    // ------------------------------------------------------------------ 连接管理

    public boolean isConnected() {
        return channel != null && channel.isActive();
    }

    /** 首次连接：以初始 streams 拼入 URL 建立连接 */
    public void connect(List<String> streams) {
        if (streams == null || streams.isEmpty()) {
            log.warn("没有要订阅的 stream");
            return;
        }
        String url = "wss://fstream.binance.com/market/stream?streams=" + String.join("/", streams);
        connectInternal(url);
    }

    private void connectInternal(String url) {
        disconnect();
        try {
            URI uri = URI.create(url);
            String host = uri.getHost();
            int port = 443;

            SslContext sslContext = SslContextBuilder.forClient().build();

            WebSocketClientHandshaker handshaker = WebSocketClientHandshakerFactory.newHandshaker(
                    uri, WebSocketVersion.V13, null, true, new DefaultHttpHeaders(), 10 * 1024 * 1024);

            BinanceWebSocketHandler handler = new BinanceWebSocketHandler(
                    handshaker, objectMapper, frontendWsHandler, klineService, klineUtils, fundingRateService, redisTemplate);

            group = new NioEventLoopGroup();
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .option(ChannelOption.TCP_NODELAY, true)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast(sslContext.newHandler(ch.alloc(), host, port));
                            pipeline.addLast(new HttpClientCodec());
                            pipeline.addLast(new HttpObjectAggregator(10 * 1024 * 1024));
                            // 30s 没收到数据触发 IdleStateEvent，handler 中关闭 channel 触发重连
                            pipeline.addLast(new IdleStateHandler(30, 0, 0, TimeUnit.SECONDS));
                            pipeline.addLast(handler);
                        }
                    });

            channel = bootstrap.connect(host, port).sync().channel();
            handler.handshakeFuture().sync();
            log.info("币安 WebSocket 已连接: {}", url);
        } catch (Exception e) {
            log.error("WebSocket 连接失败: {}", e.getMessage(), e);
            disconnect();
        }
    }

    // ------------------------------------------------------------------ 增量订阅

    /** 在已有连接上追加订阅 */
    public void subscribe(List<String> streams) {
        sendCommand("SUBSCRIBE", streams);
    }

    /** 在已有连接上取消订阅 */
    public void unsubscribe(List<String> streams) {
        sendCommand("UNSUBSCRIBE", streams);
    }

    private void sendCommand(String method, List<String> params) {
        if (!isConnected() || params == null || params.isEmpty()) return;
        try {
            Map<String, Object> msg = new LinkedHashMap<>();
            msg.put("method", method);
            msg.put("params", params);
            msg.put("id", reqId.getAndIncrement());
            channel.writeAndFlush(new TextWebSocketFrame(objectMapper.writeValueAsString(msg)));
            log.info("{} {} 个 stream: {}", method, params.size(), params);
        } catch (Exception e) {
            log.error("发送 {} 失败", method, e);
        }
    }

    // ------------------------------------------------------------------ 断开

    public void disconnect() {
        if (channel != null) {
            try { channel.close().sync(); } catch (Exception ignored) {}
            channel = null;
        }
        if (group != null) {
            group.shutdownGracefully();
            group = null;
        }
    }

    @PreDestroy
    public void destroy() {
        disconnect();
    }
}
