package com.njcn.gather.detection.util.socket.websocket;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.AttributeKey;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;

/**
 * WebSocket服务端管道初始化器
 * 
 * 职责：
 * 1. 为每个新的WebSocket连接配置处理器链（Pipeline）
 * 2. 按正确顺序添加各种Handler，确保数据流正确处理
 * 3. 配置HTTP到WebSocket的协议升级
 * 4. 设置心跳检测和异常处理机制
 * 
 * 处理流程：
 * HTTP请求 → HTTP编解码 → 分块处理 → 消息聚合 → 协议升级 → 心跳检测 → 业务处理 → 异常处理
 * 
 * @Description: webSocket服务端自定义配置
 * @Author: wr
 * @Date: 2024/12/10 14:20
 */
@Slf4j
public class WebSocketInitializer extends ChannelInitializer<SocketChannel> {

    /**
     * WebSocket访问路径
     */
    private static final String WEBSOCKET_PATH = "/hello";
    
    /**
     * HTTP消息最大聚合大小：512KB
     * 用于WebSocket握手和消息传输
     */
    private static final int MAX_CONTENT_LENGTH = 512 * 1024;
    
    /**
     * 心跳检测间隔：13秒
     * 13秒内没有收到客户端消息则触发空闲事件
     */
    private static final int READER_IDLE_TIME_SECONDS = 13;

    /**
     * 为每个新连接初始化处理器管道
     * 注意：Handler的添加顺序非常重要，决定了数据的处理流向
     * 
     * @param ch 新建立的Socket通道
     * @throws Exception 初始化过程中的异常
     */
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        
        // 1. HTTP协议处理器
        // HttpServerCodec = HttpRequestDecoder + HttpResponseEncoder
        // 负责HTTP请求解码和HTTP响应编码
        pipeline.addLast("http-codec", new HttpServerCodec());
        
        // 2. 分块写入处理器
        // 用于处理大文件的分块传输，防止内存溢出
        // 支持ChunkedInput，如ChunkedFile、ChunkedNioFile等
        pipeline.addLast("chunked-write", new ChunkedWriteHandler());
        
        // 3. HTTP消息聚合器
        // 将分片的HTTP消息重新组装成完整的FullHttpRequest或FullHttpResponse
        // WebSocket握手需要完整的HTTP请求，所以这个Handler必须添加
        pipeline.addLast("http-aggregator", new HttpObjectAggregator(MAX_CONTENT_LENGTH));
        
        // 4. WebSocket URL预处理器
        // 在WebSocket握手之前处理URL参数，验证用户ID
        pipeline.addLast("websocket-preprocessor", new WebSocketPreprocessor());
        
        // 5. WebSocket协议升级处理器
        // 处理WebSocket握手，将HTTP协议升级为WebSocket协议
        // 只有访问指定路径(WEBSOCKET_PATH)的请求才会被升级
        // 升级后会移除HTTP相关的Handler，添加WebSocket相关的Handler
        pipeline.addLast("websocket-protocol", new WebSocketServerProtocolHandler(WEBSOCKET_PATH));
        
        // 6. 空闲状态检测器
        // 检测连接的空闲状态，用于心跳机制
        // readerIdleTime: 读空闲时间，writerIdleTime: 写空闲时间，allIdleTime: 读写空闲时间
        pipeline.addLast("idle-state", new IdleStateHandler(READER_IDLE_TIME_SECONDS, 0, 0, TimeUnit.SECONDS));
        
        // 7. 自定义WebSocket业务处理器
        // 处理WebSocket帧，实现具体的业务逻辑
        // 包括心跳处理、消息路由、连接管理等
        pipeline.addLast("websocket-handler", new WebSocketHandler());
        
        // 7. 全局异常处理器
        // 处理整个管道中未被捕获的异常，作为最后的异常处理兜底
        pipeline.addLast("exception-handler", new GlobalExceptionHandler());
    }

    /**
     * WebSocket预处理器
     * 在WebSocket握手之前验证URL参数并清理URL
     */
    private static class WebSocketPreprocessor extends ChannelInboundHandlerAdapter {
        
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            if (msg instanceof FullHttpRequest) {
                FullHttpRequest request = (FullHttpRequest) msg;
                String uri = request.uri();
                
                log.debug("WebSocket预处理器收到HTTP请求：{}", uri);
                
                // 验证并提取userId
                String userId = extractUserId(uri);
                if (userId == null || userId.trim().isEmpty()) {
                    log.warn("WebSocket连接被拒绝：缺少userId参数, uri: {}", uri);
                    FullHttpResponse response = new DefaultFullHttpResponse(
                        HttpVersion.HTTP_1_1, 
                        HttpResponseStatus.BAD_REQUEST
                    );
                    ctx.writeAndFlush(response).addListener(f -> ctx.close());
                    return;
                }
                
                // 将userId存储到Channel属性中
                ctx.channel().attr(AttributeKey.<String>valueOf("userId")).set(userId);
                
                // 清理URL参数
                if (uri.contains("?")) {
                    String cleanUri = uri.substring(0, uri.indexOf("?"));
                    request.setUri(cleanUri);
                    log.debug("URL已清理，原始: {}, 清理后: {}, userId: {}", uri, cleanUri, userId);
                }
            }
            
            // 继续传递给下一个Handler
            super.channelRead(ctx, msg);
        }
        
        private String extractUserId(String uri) {
            if (!uri.contains("name=")) {
                return null;
            }
            int start = uri.indexOf("name=") + 5;
            int end = uri.indexOf("&", start);
            if (end == -1) {
                return uri.substring(start);
            } else {
                return uri.substring(start, end);
            }
        }
    }

    /**
     * 全局异常处理器
     * 作为管道中的最后一个Handler，捕获所有未处理的异常
     */
    private static class GlobalExceptionHandler extends ChannelInboundHandlerAdapter {
        
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
            // 记录异常详情，便于问题排查
            log.error("WebSocket连接发生未处理异常，远程地址：{}，异常信息：{}", 
                     ctx.channel().remoteAddress(), cause.getMessage(), cause);
            
            // 优雅关闭连接
            if (ctx.channel().isActive()) {
                ctx.close();
            }
        }
        
        @Override
        public void channelInactive(ChannelHandlerContext ctx) throws Exception {
            log.debug("WebSocket连接断开，远程地址：{}", ctx.channel().remoteAddress());
            super.channelInactive(ctx);
        }
    }
}

