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

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.njcn.gather.detection.handler.SocketContrastResponseService;
import com.njcn.gather.detection.handler.SocketDevResponseService;
import com.njcn.gather.detection.handler.SocketSourceResponseService;
import com.njcn.gather.detection.lock.DetectionLockManager;
import com.njcn.gather.detection.pojo.enums.SourceResponseCodeEnum;
import com.njcn.gather.detection.pojo.param.ContrastDetectionParam;
import com.njcn.gather.detection.pojo.param.PreDetectionParam;
import com.njcn.gather.detection.pojo.vo.SocketDataMsg;
import com.njcn.gather.detection.util.socket.CnSocketUtil;
import com.njcn.gather.detection.util.socket.SocketManager;
import com.njcn.gather.detection.util.socket.websocket.WebServiceManager;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.CharsetUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

/**
 * Netty客户端工具类
 * 用于建立与检测设备和程控源设备的Socket通信连接
 * 支持心跳检测、断线重连和异常处理
 *
 * @Description: 心跳检测服务端 对应的服务端在netty-server 包下的NettyClient
 * @Author: wr
 * @Date: 2024/12/10 14:16
 */

@Slf4j
@Component
public class NettyClient {

    @Resource
    private SocketSourceResponseService socketSourceResponseService;

    @Resource
    private SocketDevResponseService socketDevResponseService;

    @Resource
    private SocketContrastResponseService socketContrastResponseService;

    /**
     * 静态实例，用于保持向后兼容
     */
    private static NettyClient instance;


    @PostConstruct
    public void init() {
        instance = this;
    }

    /**
     * 创建程控源Handler实例（Spring管理方式）
     * 自动注入SocketSourceResponseService，统一使用CnSocketUtil.SOURCE_TAG
     *
     * @param param 检测参数
     * @return NettySourceClientHandler 程控源处理器实例
     */
    public NettySourceClientHandler createSourceHandler(PreDetectionParam param) {
        return new NettySourceClientHandler(param, socketSourceResponseService);
    }

    /**
     * 创建被检设备Handler实例（Spring管理方式）
     * 自动注入SocketDevResponseService
     *
     * @param param 检测参数
     * @return NettyDevClientHandler 被检设备处理器实例
     */
    public NettyDevClientHandler createDeviceHandler(PreDetectionParam param) {
        return new NettyDevClientHandler(param, socketDevResponseService);
    }

    /**
     * 智能连接程控源设备（新增方法）
     * 自动创建Handler并建立连接
     *
     * @param ip    程控源IP地址
     * @param port  程控源端口
     * @param param 检测参数
     * @param msg   初始消息
     */
    public void connectToSource(String ip, Integer port, PreDetectionParam param, String msg) {
        NettySourceClientHandler handler = createSourceHandler(param);
        executeSocketConnection(ip, port, param, msg, handler);
    }

    /**
     * 智能连接被检设备（新增方法）
     * 自动创建Handler并建立连接
     *
     * @param ip    被检设备IP地址
     * @param port  被检设备端口
     * @param param 检测参数
     * @param msg   初始消息
     */
    public void connectToDevice(String ip, Integer port, PreDetectionParam param, String msg) {
        NettyDevClientHandler handler = createDeviceHandler(param);
        executeSocketConnection(ip, port, param, msg, handler);
    }

    /**
     * 智能连接比对被检设备（新增方法）
     * 自动创建Handler并建立连接
     *
     * @param ip    被检设备IP地址
     * @param port  被检设备端口
     * @param param 检测参数
     * @param msg   初始消息
     *              静态方法：智能连接程控源设备（兼容性包装）
     */
    private void connectToContrast(String ip, Integer port, ContrastDetectionParam param, String msg) {
        PreDetectionParam preDetectionParam = new PreDetectionParam();
        preDetectionParam.setUserPageId(param.getLoginName());
        preDetectionParam.setTestItemList(param.getTestItemList());
        preDetectionParam.setDevIds(param.getDevIds());
        preDetectionParam.setUserId(param.getUserId());
        NettyContrastClientHandler handler = new NettyContrastClientHandler();
        handler.param = preDetectionParam;
        handler.socketContrastResponseService = socketContrastResponseService;
        executeSocketConnection(ip, port, preDetectionParam, msg, handler);
    }

    /**
     * 静态方法：智能连接程控源设备（兼容性包装）
     */
    public static void connectToContrastDeviceStatic(String ip, Integer port, ContrastDetectionParam param, String msg) {
        if (instance != null) {
            instance.connectToContrast(ip, port, param, msg);
        } else {
            log.error("NettyClient未初始化，无法创建比对设备通讯连接");
        }

    }


    /**
     * 静态方法：智能连接程控源设备（兼容性包装）
     */
    public static void connectToSourceStatic(String ip, Integer port, PreDetectionParam param, String msg) {
        if (instance != null) {
            instance.connectToSource(ip, port, param, msg);
        } else {
            log.error("NettyClient未初始化，无法创建程控源连接");
        }
    }

    /**
     * 静态方法：智能连接被检设备（兼容性包装）
     */
    public static void connectToDeviceStatic(String ip, Integer port, PreDetectionParam param, String msg) {
        if (instance != null) {
            instance.connectToDevice(ip, port, param, msg);
        } else {
            log.error("NettyClient未初始化，无法创建被检设备连接");
        }
    }


    /**
     * 内部重构后的实现 - 拆分职责但不暴露给外部
     * 执行完整的Socket连接建立流程：
     * 1. 创建事件循环组
     * 2. 配置Bootstrap启动器
     * 3. 设置管道处理链
     * 4. 建立连接并处理结果
     *
     * @param ip      目标服务器IP地址
     * @param port    目标服务器端口号
     * @param param   检测参数对象
     * @param msg     连接成功后发送的初始消息
     * @param handler 业务处理器（区分程控源和被检设备）
     */
    private static void executeSocketConnection(String ip, Integer port,
                                                PreDetectionParam param, String msg, SimpleChannelInboundHandler<String> handler) {
        // 创建NIO事件循环组，用于处理网络I/O事件
        NioEventLoopGroup group = createEventLoopGroup();

        try {
            // 配置客户端启动器
            Bootstrap bootstrap = configureBootstrap(group);
            // 创建管道初始化器，配置编解码和业务处理链
            ChannelInitializer<NioSocketChannel> initializer = createChannelInitializer(param, handler);
            bootstrap.handler(initializer);
            // 同步连接到目标服务器
            ChannelFuture channelFuture = bootstrap.connect(ip, port).sync();
            // 处理连接结果（成功或失败）
            handleConnectionResult(channelFuture, param, handler, group, msg);
        } catch (Exception e) {
            // 处理连接过程中的异常
            handleConnectionException(e, param, handler, group);
        }
    }

    /**
     * 创建NIO事件循环组
     * 用于管理网络I/O操作的线程池，处理连接、读写等异步事件
     *
     * @return NioEventLoopGroup 事件循环组实例
     */
    private static NioEventLoopGroup createEventLoopGroup() {
        return new NioEventLoopGroup();
    }

    /**
     * 配置Bootstrap客户端启动器
     * 设置连接超时、通道类型等基础参数
     *
     * @param group 事件循环组
     * @return Bootstrap 配置好的启动器
     */
    private static Bootstrap configureBootstrap(NioEventLoopGroup group) {
        return new Bootstrap()
                // 绑定事件循环组
                .group(group)
                // 连接超时5秒
                .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
                // 使用NIO Socket通道
                .channel(NioSocketChannel.class);
    }

    /**
     * 创建通道初始化器
     * 当新连接建立时，初始化该连接的处理管道
     *
     * @param param   检测参数，用于配置心跳处理器
     * @param handler 业务处理器
     * @return ChannelInitializer 通道初始化器
     */
    private static ChannelInitializer<NioSocketChannel> createChannelInitializer(
            PreDetectionParam param, SimpleChannelInboundHandler<String> handler) {
        return new ChannelInitializer<NioSocketChannel>() {
            @Override
            protected void initChannel(NioSocketChannel ch) {
                setupPipeline(ch.pipeline(), param, handler);
            }
        };
    }

    /**
     * 配置管道处理链
     * 按顺序添加各种处理器，构成完整的数据处理流水线：
     * 1. LineBasedFrameDecoder：按行分割数据，解决TCP粘包拆包问题
     * 2. StringDecoder/StringEncoder：字符串编解码器
     * 3. HeartbeatHandler：心跳处理器，维持连接活跃
     * 4. IdleStateHandler：空闲检测器（仅被检设备需要）
     * 5. 业务处理器：具体的业务逻辑处理
     *
     * @param pipeline 管道对象
     * @param param    检测参数
     * @param handler  业务处理器
     */
    private static void setupPipeline(ChannelPipeline pipeline,
                                      PreDetectionParam param, SimpleChannelInboundHandler<String> handler) {
        // 基础编解码器：处理数据格式转换和粘包拆包
        // 按行分割，最大20KB
        pipeline.addLast(new LineBasedFrameDecoder(10240 * 2))
                // 字节转字符串
                .addLast(new StringDecoder(CharsetUtil.UTF_8))
                // 字符串转字节
                .addLast(new StringEncoder(CharsetUtil.UTF_8));

        // 心跳处理器：根据设备类型选择不同的标签
        String tag = getDeviceTag(handler);
        pipeline.addLast(new HeartbeatHandler(param, tag));
        // 空闲检测器：仅被检设备和比对被检设备需要，60秒无读操作触发空闲事件
        if (!isSourceHandler(handler)) {
            pipeline.addLast(new IdleStateHandler(60, 0, 0, TimeUnit.SECONDS));
        }

        // 业务处理器：处理具体的检测业务逻辑
        pipeline.addLast(handler);
    }

    /**
     * 判断是否为程控源处理器
     * 程控源设备和被检设备使用不同的处理器和配置
     *
     * @param handler 业务处理器
     * @return boolean true:程控源处理器, false:被检设备处理器
     */
    private static boolean isSourceHandler(SimpleChannelInboundHandler<String> handler) {
        return handler instanceof NettySourceClientHandler;
    }

    /**
     * 获取设备标签
     * 用于在SocketManager中区分不同类型的设备连接
     *
     * @param handler 业务处理器
     * @return String 设备标签（"_Source" 或 "_Dev"）
     */
    private static String getDeviceTag(SimpleChannelInboundHandler<String> handler) {
        String tag;
        if (handler instanceof NettySourceClientHandler) {
            tag = CnSocketUtil.SOURCE_TAG;
        } else if (handler instanceof NettyDevClientHandler) {
            tag = CnSocketUtil.DEV_TAG;
        } else {
            tag = CnSocketUtil.CONTRAST_DEV_TAG;
        }
        return tag;
    }

    /**
     * 获取名称
     * 用于在SocketManager中区分不同类型的设备连接
     *
     * @param handler 业务处理器
     * @return String 设备标签（"程控源设备" 或 "被检设备"）
     */
    private static String getDeviceType(SimpleChannelInboundHandler<String> handler) {
        String deviceType;
        if (handler instanceof NettySourceClientHandler) {
            deviceType = "程控源设备";
        } else if (handler instanceof NettyDevClientHandler) {
            deviceType = "被检设备";
        } else {
            deviceType = "比对被检设备";
        }
        return deviceType;
    }

    /**
     * 处理连接结果
     * 为连接Future添加监听器，异步处理连接成功或失败的情况
     *
     * @param channelFuture 连接Future对象
     * @param param         检测参数
     * @param handler       业务处理器
     * @param group         事件循环组
     * @param msg           初始消息
     */
    private static void handleConnectionResult(ChannelFuture channelFuture,
                                               PreDetectionParam param, SimpleChannelInboundHandler<String> handler,
                                               NioEventLoopGroup group, String msg) {
        channelFuture.addListener((ChannelFutureListener) ch -> {
            if (!ch.isSuccess()) {
                onConnectionFailure(param, handler, group);
            } else {
                onConnectionSuccess(channelFuture, param, handler, group, msg);
            }
        });
    }

    /**
     * 连接失败处理
     * 输出失败信息、优雅关闭事件循环组、通知前端、释放检测锁
     *
     * @param param   检测参数，用于定位锁与前端通知
     * @param handler 业务处理器，用于区分设备类型
     * @param group   事件循环组
     */
    private static void onConnectionFailure(PreDetectionParam param,
                                            SimpleChannelInboundHandler<String> handler, NioEventLoopGroup group) {
        String deviceType = getDeviceType(handler);
        log.info("连接{}服务端失败...", deviceType);
        group.shutdownGracefully();
        // 异步建连失败时前端原本静默，补一次通知避免用户黑屏等待
        notifyFrontendError(param, handler);
        // 释放检测锁：抢锁后由 controller 异步发起的建连若失败，无法走 controller 兜底
        if (param != null && param.getUserPageId() != null) {
            DetectionLockManager.getInstance()
                    .releaseIfMatchPage(param.getUserPageId(), "ASYNC_CONNECT_FAILED");
        }
    }

    /**
     * 连接成功处理
     * 执行连接成功后的初始化操作：
     * 1. 管理Socket连接会话（注册EventLoopGroup到SocketManager）
     * 2. 注册Channel到SocketManager，实现统一的连接管理
     * 3. 通过SocketManager发送初始消息，统一消息发送入口
     *
     * @param channelFuture 连接Future对象
     * @param param         检测参数
     * @param handler       业务处理器
     * @param group         事件循环组
     * @param msg           初始消息
     */
    private static void onConnectionSuccess(ChannelFuture channelFuture,
                                            PreDetectionParam param, SimpleChannelInboundHandler<String> handler,
                                            NioEventLoopGroup group, String msg) {
        String deviceType = getDeviceType(handler);
        log.info("连接{}服务端成功...", deviceType);
        // 管理连接会话，将EventLoopGroup注册到SocketManager
        manageSocketConnection(param, handler, group);

        // 将Channel也注册到SocketManager，便于统一消息发送
        String userId = param.getUserPageId() + getDeviceTag(handler);
        SocketManager.addUser(userId, channelFuture.channel());

        // 通过SocketManager发送初始消息，统一消息发送入口
        SocketManager.sendMsg(userId, msg);
    }

    /**
     * 管理Socket连接会话
     * 将新建立的连接注册到SocketManager中进行统一管理：
     * 1. 检查并关闭同用户同设备类型的旧连接，避免资源泄露
     * 2. 将新连接注册到SocketManager，便于后续管理和查找
     * <p>
     * 连接Key格式：{userPageId}_{deviceTag}
     * 例如：zhangsan_test_Source（程控源） / zhangsan_test_Dev（被检设备）
     *
     * @param param   检测参数，包含用户页面ID
     * @param handler 业务处理器，用于区分设备类型
     * @param group   事件循环组，表示具体的连接资源
     */
    private static void manageSocketConnection(PreDetectionParam param,
                                               SimpleChannelInboundHandler<String> handler, NioEventLoopGroup group) {
        // 构建连接标识：用户ID + 设备标签
        String key = param.getUserPageId() + getDeviceTag(handler);

        // 关闭旧连接：同一用户同一设备类型只能有一个活跃连接
        NioEventLoopGroup existingGroup = SocketManager.getGroupByUserId(key);
        if (ObjectUtil.isNotNull(existingGroup)) {
            try {
                existingGroup.shutdownGracefully().sync();
            } catch (InterruptedException e) {
                // 恢复中断状态
                Thread.currentThread().interrupt();
            }
        }

        // 注册新连接到SocketManager
        SocketManager.addGroup(key, group);
    }

    /**
     * 处理连接异常
     * 当连接建立过程中发生异常时的统一处理流程：
     * 1. 关闭相关资源，防止资源泄露
     * 2. 执行设备相关的退出操作
     * 3. 通过WebSocket向前端通知错误信息
     *
     * @param e       异常对象
     * @param param   检测参数
     * @param handler 业务处理器
     * @param group   事件循环组
     */
    private static void handleConnectionException(Exception e, PreDetectionParam param,
                                                  SimpleChannelInboundHandler<String> handler, NioEventLoopGroup group) {
        log.info("连接socket服务端发送异常: {}", e.getMessage());

        // 关闭事件循环组资源
        group.shutdownGracefully();

        // 执行设备相关的退出操作
        executeQuitOperations(param, handler);

        // 通过WebSocket通知前端页面
        notifyFrontendError(param, handler);

        // 释放检测锁，理由同 onConnectionFailure
        if (param != null && param.getUserPageId() != null) {
            DetectionLockManager.getInstance()
                    .releaseIfMatchPage(param.getUserPageId(), "ASYNC_CONNECT_EXCEPTION");
        }
    }

    /**
     * 执行退出操作
     * 根据不同的设备处理器类型执行相应的退出指令：
     * - NettyDevClientHandler：被检设备处理器，需要发送程控源退出指令
     * - 其他非程控源处理器：发送通用退出指令
     * - NettySourceClientHandler：程控源处理器，无需额外退出操作
     *
     * @param param   检测参数
     * @param handler 业务处理器
     */
    private static void executeQuitOperations(PreDetectionParam param,
                                              SimpleChannelInboundHandler<String> handler) {
        if (handler instanceof NettyDevClientHandler) {
            // 被检设备异常时，发送程控源退出指令
            CnSocketUtil.quitSendSource(param);
        }
        // 程控源处理器异常时无需额外操作
    }

    /**
     * 通知前端错误信息
     * 构建错误消息对象并通过WebSocket发送给前端页面
     * 前端可以根据操作码（Source/Dev）显示相应的错误提示
     *
     * @param param   检测参数，包含用户页面ID
     * @param handler 业务处理器，用于确定操作码
     */
    private static void notifyFrontendError(PreDetectionParam param,
                                            SimpleChannelInboundHandler<String> handler) {
        // 构建错误消息对象
        SocketDataMsg socketDataMsg = new SocketDataMsg();
        // 消息类型
        socketDataMsg.setType("aaa");
        // 错误码
        socketDataMsg.setCode(SourceResponseCodeEnum.SOCKET_ERROR.getCode());
        // 错误消息
        socketDataMsg.setData(SourceResponseCodeEnum.SOCKET_ERROR.getMessage());
        // 请求ID标识
        socketDataMsg.setRequestId("connect");
        // 设置操作码：程控源为"Source"，被检设备为"Dev"
        String devTag = getDeviceTag(handler).substring(1);
        socketDataMsg.setOperateCode(devTag);
        // 通过WebSocket发送错误信息到前端页面
        WebServiceManager.sendMsg(param.getUserPageId(), JSON.toJSONString(socketDataMsg));
    }

}
