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

import cn.hutool.core.collection.CollUtil;
import com.alibaba.fastjson.JSON;
import com.njcn.gather.detection.handler.SocketDevResponseService;
import com.njcn.gather.detection.lock.DetectionLock;
import com.njcn.gather.detection.lock.DetectionLockManager;
import com.njcn.gather.detection.pojo.enums.ResultEnum;
import com.njcn.gather.detection.pojo.enums.SourceOperateCodeEnum;
import com.njcn.gather.detection.pojo.param.PreDetectionParam;
import com.njcn.gather.detection.pojo.vo.DevLineTestResult;
import com.njcn.gather.detection.pojo.vo.WebSocketVO;
import com.njcn.gather.detection.util.socket.CnSocketUtil;
import com.njcn.gather.detection.util.socket.FormalTestManager;
import com.njcn.gather.detection.util.socket.SocketManager;
import com.njcn.gather.detection.util.socket.websocket.WebServiceManager;
import com.njcn.gather.device.pojo.vo.PreDetection;
import com.njcn.gather.report.pojo.enums.PowerIndexEnum;
import com.njcn.gather.script.pojo.po.SourceIssue;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.TimeoutException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.net.ConnectException;
import java.net.ProtocolException;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;


/**
 * Netty设备客户端处理器
 * <p>负责处理与被检测设备的Socket通信，包括：</p>
 * <ul>
 *     <li>通道生命周期管理（建立、断开）</li>
 *     <li>消息接收和处理</li>
 *     <li>心跳超时处理</li>
 *     <li>异常处理和恢复</li>
 * </ul>
 *
 * @Description: 设备客户端业务处理器
 * @Author: wr
 * @Date: 2024/12/10 14:16
 */

@Slf4j
@RequiredArgsConstructor
public class NettyDevClientHandler extends SimpleChannelInboundHandler<String> {

    /**
     * 短闪检测超时时间：20分钟+100秒（1300秒）
     */
    private static final long SHORT_FLICKER_TIMEOUT = 1300L;

    /**
     * 长闪检测超时时间：120分钟+30分
     */
    private static final long LONG_FLICKER_TIMEOUT = 120 * 60 + 30 * 60;

    /**
     * 统计数据检测超时时间：3分钟+100秒（190秒）
     */
    private static final long STATISTICS_TIMEOUT = 190L;

    /**
     * 实时数据检测超时时间：1分钟*3
     */
    private static final long REALTIME_TIMEOUT = 60 * 3L;

    /**
     * 暂停操作超时时间：10分钟（600秒）
     */
    private static final long STOP_TIMEOUT = 600L;

    private final PreDetectionParam param;

    private final SocketDevResponseService socketResponseService;


    /**
     * 当通道连接建立时的处理逻辑
     * <p>将关闭原有连接，并将新连接注册到SocketManager中</p>
     *
     * @param ctx 通道上下文
     * @throws Exception 连接异常
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        log.info("客户端通道已建立: {}", ctx.channel().id());

        // 检查是否存在同一用户的老连接
        Channel channel = SocketManager.getChannelByUserId(param.getUserPageId() + CnSocketUtil.DEV_TAG);
        if (Objects.nonNull(channel)) {
            try {
                // 关闭老连接避免连接泄漏
                channel.close().sync();
            } catch (InterruptedException e) {
                log.error("关闭通道异常", e);
            }
        }
        // 注册新的连接到用户管理器
        SocketManager.addUser(param.getUserPageId() + CnSocketUtil.DEV_TAG, ctx.channel());
    }

    /**
     * 当通道断开时的清理工作
     * <p>关闭连接，清理用户映射，退出源设备发送</p>
     *
     * @param ctx 通道上下文
     * @throws Exception 关闭异常
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        log.warn("设备通讯客户端断线");
        ctx.close();
        String key = param.getUserPageId() + CnSocketUtil.DEV_TAG;
        if (SocketManager.consumeActiveClose(key)) {
            return;
        }
        SocketManager.removeUser(key);
        CnSocketUtil.quitSendSource(param);
        // 设备主动断开 → 本次检测视为结束，释放检测锁
        DetectionLockManager.getInstance()
                .releaseIfMatchPage(param.getUserPageId(), "DEV_CHANNEL_INACTIVE");
    }

    /**
     * 处理从服务端接收到的消息
     * <p>将消息交给SocketDevResponseService进行具体处理</p>
     *
     * @param ctx 通道上下文
     * @param msg 接收到的消息
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) {
        log.info("devhandler接收server端数据: {}", msg);
        try {
            socketResponseService.deal(param, msg);
        } catch (Exception e) {
            log.error("处理服务端消息异常", e);
            CnSocketUtil.quitSend(param);
            // 业务消息处理异常 → 退出并释放检测锁
            DetectionLockManager.getInstance()
                    .releaseIfMatchPage(param.getUserPageId(), "DEV_READ_EXCEPTION");
        }
    }


    /**
     * 用户事件回调方法，主要用于处理心跳超时
     * <p>当触发READER_IDLE事件时，根据当前状态进行超时处理</p>
     *
     * @param ctx 通道上下文
     * @param evt 用户事件对象
     */
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
        // 检查是否为读取空闲事件（心跳超时）
        if (evt instanceof IdleStateEvent && ((IdleStateEvent) evt).state() == IdleState.READER_IDLE) {
            log.warn("devHandler触发读超时函数: {}", LocalDateTime.now());
            // 根据是否有停止标志采取不同的超时处理策略
            if (!FormalTestManager.hasStopFlag) {
                // 正常检测中的超时处理
                handleReadTimeout();
            } else {
                // 暂停状态下的超时处理
                handleStopTimeout();
            }
        }
    }


    /**
     * 处理器被添加到管道时的回调
     *
     * @param ctx 通道上下文
     */
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) {
        log.info("有通道准备接入: {}", ctx.channel());
    }


    /**
     * 异常捕获处理
     * <p>捕获并处理各种类型的异常，执行清理工作</p>
     *
     * @param ctx   通道上下文
     * @param cause 异常原因
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        log.error("捕获到设备服务异常", cause);
        handleSpecificException(cause);
        // 统一清理工作
        CnSocketUtil.quitSend(param);
        CnSocketUtil.quitSendSource(param);
        socketResponseService.backCheckState(param);
        ctx.close();
        // 通道异常 → 释放检测锁
        DetectionLockManager.getInstance()
                .releaseIfMatchPage(param.getUserPageId(), "DEV_EXCEPTION");
    }

    /**
     * 处理特定类型的异常
     * <p>根据异常类型进行相应的日志记录和错误通知</p>
     *
     * @param cause 异常对象
     */
    private void handleSpecificException(Throwable cause) {
        if (cause instanceof ConnectException) {
            log.error("连接socket服务端异常");
        } else if (cause instanceof IOException) {
            log.error("IO异常: {}", cause.getMessage());
            WebServiceManager.sendDetectionErrorMessage(param.getUserPageId(), SourceOperateCodeEnum.DEVICE_ERROR);
        } else if (cause instanceof TimeoutException) {
            log.error("操作超时: {}", cause.getMessage());
        } else if (cause instanceof ProtocolException) {
            log.error("协议异常: {}", cause.getMessage());
        } else {
            log.error("未知异常: {}", cause.getMessage());
            WebServiceManager.sendDetectionErrorMessage(param.getUserPageId(), SourceOperateCodeEnum.DEVICE_ERROR);
        }
    }


    /**
     * 处理读取超时事件
     * <p>检查源列表，更新超时计数器，判断是否超时并处理</p>
     */
    private void handleReadTimeout() {
        if (CollUtil.isNotEmpty(SocketManager.getSourceList())) {
            // 获取当前正在检测的源问题（取第一个）
            SourceIssue sourceIssue = SocketManager.getSourceList().get(0);
            // 更新该源问题的超时计数器
            updateTimeoutCounter(sourceIssue);
            // 根据检测类型判断是否已超时
            if (isTimeout(sourceIssue)) {
                handleTimeout(sourceIssue);
            }
        } else {
            // 源列表为空，认为是常规步骤的超时，默认一分钟超时
            CnSocketUtil.quitSend(param);
            WebServiceManager.sendDetectionErrorMessage(param.getUserPageId(), SourceOperateCodeEnum.SOCKET_TIMEOUT);
            socketResponseService.backCheckState(param);
            // 常规步骤读超时兜底 → 释放检测锁
            DetectionLockManager.getInstance()
                    .releaseIfMatchPage(param.getUserPageId(), "DEV_READ_TIMEOUT");
        }
    }

    /**
     * 处理暂停操作的超时事件
     * <p>当检测被暂停时，统计暂停时间，超过限制后发送超时通知</p>
     */
    private void handleStopTimeout() {
        FormalTestManager.stopTime += 60;
        log.warn("当前进入暂停操作超时函数，停止时间: {}", FormalTestManager.stopTime);
        if (FormalTestManager.stopTime > STOP_TIMEOUT) {
            CnSocketUtil.quitSend(param);
            WebServiceManager.sendDetectionErrorMessage(param.getUserPageId(), SourceOperateCodeEnum.FORMAL_REAL.getValue(), SourceOperateCodeEnum.STOP_TIMEOUT);
            // R4 释放：暂停 10 分钟超时视同放弃本次检测
            DetectionLock cur = DetectionLockManager.getInstance().getCurrent();
            if (cur != null) {
                DetectionLockManager.getInstance().releaseIfHeldBy(cur.getUserId(), "PAUSE_TIMEOUT");
            }
            // 重置 FormalTestManager 状态，避免下次进入误判
            FormalTestManager.clearPauseState();
        }
    }

    /**
     * 更新指定源问题的超时计数器
     * <p>每次调用时增加60秒，用于统计累计超时时间</p>
     *
     * @param sourceIssue 源问题对象
     */
    private void updateTimeoutCounter(SourceIssue sourceIssue) {
        Integer index = sourceIssue.getIndex();
        if (SocketManager.clockMap.containsKey(index)) {
            SocketManager.clockMap.put(index, SocketManager.clockMap.get(index) + 60L);
        } else {
            SocketManager.clockMap.put(index, 60L);
        }
    }

    /**
     * 根据检测类型判断是否已超时
     * <p>不同检测类型有不同的超时阈值：</p>
     * <ul>
     *     <li>闪变检测：20分钟</li>
     *     <li>统计数据：3分钟</li>
     *     <li>实时数据：1分钟</li>
     * </ul>
     *
     * @param sourceIssue 源问题对象
     * @return true 如果已超时，false 否则
     */
    private boolean isTimeout(SourceIssue sourceIssue) {
        long currentTime = SocketManager.clockMap.get(sourceIssue.getIndex());
        String type = sourceIssue.getType();
        String otherType = sourceIssue.getOtherType();

        if (PowerIndexEnum.LF.getKey().equals(otherType)) {
            return currentTime >= LONG_FLICKER_TIMEOUT;
        } else if (PowerIndexEnum.F.getKey().equals(type)) {
            return currentTime >= SHORT_FLICKER_TIMEOUT;
        } else if (PowerIndexEnum.VOLTAGE.getKey().equals(type) || PowerIndexEnum.HP.getKey().equals(type)) {
            // 统计数据类型（电压、谐波）：中等时间，3分钟超时
            return currentTime >= STATISTICS_TIMEOUT;
        } else {
            // 实时数据类型：短时间，1分钟超时
            return currentTime >= REALTIME_TIMEOUT;
        }
    }

    /**
     * 执行超时处理操作
     * <p>记录超时日志，退出发送，发送超时结果，恢复检测状态</p>
     *
     * @param sourceIssue 源问题对象
     */
    private void handleTimeout(SourceIssue sourceIssue) {
        log.warn("超时处理 - {} 已超时，关闭连接", sourceIssue.getType());
        CnSocketUtil.quitSend(param);
        timeoutSend(sourceIssue);
        socketResponseService.backCheckState(param);
        // 单项检测超时本质等于整轮中止（已 quitSend），释放检测锁
        DetectionLockManager.getInstance()
                .releaseIfMatchPage(param.getUserPageId(), "DEV_ITEM_TIMEOUT");
    }

    /**
     * 发送超时结果
     * <p>为所有设备创建超时的检测结果，并通过WebSocket发送给客户端</p>
     *
     * @param sourceIssue 源问题对象
     */
    private void timeoutSend(SourceIssue sourceIssue) {
        List<DevLineTestResult> devListRes = FormalTestManager.devList.stream()
                .map(this::createTimeoutResult)
                .collect(Collectors.toList());

        WebSocketVO<List<DevLineTestResult>> socketVO = new WebSocketVO<>();
        socketVO.setRequestId(sourceIssue.getType() + CnSocketUtil.END_TAG);
        socketVO.setOperateCode(sourceIssue.getType());
        socketVO.setData(devListRes);

        WebServiceManager.sendMsg(param.getUserPageId(), JSON.toJSONString(socketVO));
    }

    /**
     * 为指定设备创建超时的检测结果
     * <p>将所有监测点的结果设置为超时标志值</p>
     *
     * @param dev 设备对象
     * @return 设备检测结果
     */
    private DevLineTestResult createTimeoutResult(PreDetection dev) {
        DevLineTestResult devLineTestResult = new DevLineTestResult();
        devLineTestResult.setDeviceId(dev.getDevId());
        devLineTestResult.setDeviceName(dev.getDevName());

        Integer[] resultFlags = dev.getMonitorList().stream()
                .map(monitor -> ResultEnum.NETWORK_TIMEOUT.getValue())
                .toArray(Integer[]::new);
        devLineTestResult.setChnResult(resultFlags);

        return devLineTestResult;
    }

}
