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


import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LoggingHandler;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

import javax.annotation.PreDestroy;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;


/**
 * WebSocket服务端核心类
 * 
 * 职责：
 * 1. 启动基于Netty的WebSocket服务器
 * 2. 管理服务器生命周期（启动/关闭）
 * 3. 提供高性能的WebSocket通信支持
 * 
 * 特性：
 * - 使用ApplicationRunner确保在Spring容器完全启动后再启动WebSocket服务
 * - 使用CompletableFuture异步启动，避免阻塞Spring Boot主线程
 * - 支持优雅关闭，确保资源正确释放
 * - 完善的异常处理和日志记录
 * 
 * @Description: websocket服务端
 * @Author: wr
 * @Date: 2024/12/10 13:59
 */
@Component
@RequiredArgsConstructor
@Slf4j
public class WebSocketService implements ApplicationRunner {

    /**
     * WebSocket服务器监听端口
     * 默认7777端口，可通过配置文件webSocket.port自定义
     * 客户端连接地址：ws://host:port/hello?name=userId
     */
    @Value("${webSocket.port:7777}")
    int port;

    /**
     * Netty Boss线程组
     * 专门负责接受新的客户端连接请求
     * 通常配置1个线程即可，因为接受连接的操作相对简单
     */
    EventLoopGroup bossGroup;

    /**
     * Netty Worker线程组  
     * 专门负责处理已建立连接的I/O操作和业务逻辑
     * 默认线程数 = CPU核心数 * 2，用于并发处理多个客户端
     */
    EventLoopGroup workerGroup;

    /**
     * 服务器通道引用
     * 保存绑定端口后的Channel，用于服务器关闭时释放资源
     */
    private Channel serverChannel;

    /**
     * 异步启动任务的Future对象
     * 用于管理WebSocket服务器的异步启动过程
     * 可以用来取消启动任务或检查启动状态
     */
    private CompletableFuture<Void> serverFuture;



    /**
     * Spring Boot应用启动完成后自动调用此方法
     * 使用ApplicationRunner确保在所有Bean初始化完成后再启动WebSocket服务
     */
    @Override
    public void run(ApplicationArguments args){
        // 使用CompletableFuture异步启动WebSocket服务，避免阻塞Spring Boot主线程
        // 这样可以让应用快速启动完成，WebSocket服务在后台异步启动
        serverFuture = CompletableFuture.runAsync(this::startWebSocketServer)
                .exceptionally(throwable -> {
                    // 如果启动过程中发生异常，记录日志但不影响应用启动
                    log.error("WebSocket服务启动异常", throwable);
                    return null;
                });
    }

    /**
     * 启动WebSocket服务器的核心方法
     * 此方法会一直阻塞直到服务器关闭，所以需要在异步线程中执行
     */
    private void startWebSocketServer() {
        try {
            // 1. 创建线程组
            // bossGroup: 专门负责接受新的客户端连接请求
            // 可以自定义线程的数量，这里使用默认值（通常为1个线程）
            bossGroup = new NioEventLoopGroup(1);
            
            // workerGroup: 专门负责处理已建立连接的I/O操作
            // 默认创建的线程数量 = CPU 处理器数量 * 2，用于处理业务逻辑
            workerGroup = new NioEventLoopGroup();
            
            // 2. 配置服务器启动参数
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .handler(new LoggingHandler())
                    // 网络配置参数
                    .option(ChannelOption.SO_BACKLOG, 128)
                    // TCP连接建立超时时间5秒
                    .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
                    // 子通道配置（针对每个客户端连接）
                    // 启用TCP keepalive机制，检测死连接
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    .childHandler(new WebSocketInitializer());
            
            // 3. 绑定端口并启动服务器
            ChannelFuture future = serverBootstrap.bind(port).sync();
            // 保存服务器通道引用，用于后续关闭操作
            serverChannel = future.channel();
            // 4. 监听绑定结果并记录日志
            future.addListener(f -> {
                if (future.isSuccess()) {
                    log.info("webSocket服务启动成功，端口：{}", port);
                } else {
                    log.error("webSocket服务启动失败，端口：{}", port);
                }
            });
            
            // 5. 等待服务器关闭
            // 这里会一直阻塞，直到serverChannel被外部关闭
            // 这就是为什么需要在异步线程中执行此方法的原因
            future.channel().closeFuture().sync();
            
        } catch (InterruptedException e) {
            // 如果线程被中断（比如应用关闭），记录日志并恢复中断状态
            log.error("WebSocket服务启动过程中被中断", e);
            Thread.currentThread().interrupt();  // 恢复中断状态
        } catch (Exception e) {
            // 捕获其他所有异常，记录日志并抛出运行时异常
            log.error("WebSocket服务启动失败", e);
            throw new RuntimeException("WebSocket服务启动失败", e);
        } finally {
            // 无论成功还是失败，都要清理资源
            shutdownGracefully();
        }
    }


    /**
     * 优雅关闭Netty线程组资源
     * 私有方法，用于在服务器启动异常时清理资源
     */
    private void shutdownGracefully() {
        // 优雅关闭接收连接的线程组
        if (bossGroup != null) {
            bossGroup.shutdownGracefully();
        }
        // 优雅关闭处理I/O的线程组
        if (workerGroup != null) {
            workerGroup.shutdownGracefully();
        }
    }

    /**
     * Spring容器销毁时自动调用此方法释放资源
     * 使用@PreDestroy确保在应用关闭时优雅地关闭WebSocket服务
     */
    @PreDestroy
    public void destroy() throws InterruptedException {
        log.info("正在关闭WebSocket服务...");
        
        // 步骤1: 首先关闭服务器通道，停止接受新的连接请求
        // 这样可以确保不会有新的客户端连接进来
        if (serverChannel != null) {
            try {
                // 等待最多5秒让服务器通道关闭
                serverChannel.close().awaitUninterruptibly(5, TimeUnit.SECONDS);
                log.debug("服务器通道已关闭");
            } catch (Exception e) {
                log.warn("关闭服务器通道时发生异常", e);
            }
        }
        
        // 步骤2: 关闭bossGroup线程组
        // bossGroup负责接受连接，现在可以安全关闭了
        if (bossGroup != null) {
            try {
                // 优雅关闭：静默期0秒，超时时间5秒
                // 静默期0秒意味着立即开始关闭，超时5秒后强制关闭
                bossGroup.shutdownGracefully(0, 5, TimeUnit.SECONDS).sync();
                log.debug("bossGroup线程组已关闭");
            } catch (InterruptedException e) {
                log.warn("关闭bossGroup时被中断", e);
                Thread.currentThread().interrupt();  // 恢复中断状态
            }
        }
        
        // 步骤3: 关闭workerGroup线程组  
        // workerGroup负责处理I/O，需要等待现有连接处理完成
        if (workerGroup != null) {
            try {
                // 等待现有任务完成，但最多等待5秒
                workerGroup.shutdownGracefully(0, 5, TimeUnit.SECONDS).sync();
                log.debug("workerGroup线程组已关闭");
            } catch (InterruptedException e) {
                log.warn("关闭workerGroup时被中断", e);
                Thread.currentThread().interrupt();  // 恢复中断状态
            }
        }
        
        // 步骤4: 取消异步启动任务（如果还在运行）
        // 这可以避免在应用关闭后还有线程在后台运行
        if (serverFuture != null && !serverFuture.isDone()) {
            // true表示允许中断正在执行的任务
            boolean cancelled = serverFuture.cancel(true);
            if (cancelled) {
                log.debug("异步启动任务已取消");
            }
        }

        log.info("webSocket服务已销毁");
    }

}
