package com.zehong.system.modbus.handler;

import com.serotonin.modbus4j.ModbusMaster;
import com.serotonin.modbus4j.exception.ModbusInitException;
import com.serotonin.modbus4j.exception.ModbusTransportException;
import com.zehong.system.domain.PalletDeviceBinding;
import com.zehong.system.domain.TEquipmentAlarmData;
import com.zehong.system.mapper.PalletDeviceBindingMapper;
import com.zehong.system.modbus.business.DeviceStatusReaderAndTimeSetter;
import com.zehong.system.modbus.handler.dto.DeviceStatusReaderDto;
import com.zehong.system.modbus.util.Modbus4jUtils;
import com.zehong.system.service.ITEquipmentAlarmDataService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.net.Socket;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Predicate;

/**
 * @author lenovo
 * @date 2025/6/27
 * @description 读取数据后根据状态判断设置时间
 */
@Component
public class ModbusResultHandler implements Consumer<DeviceStatusReaderDto> {

    private static final Logger log = LoggerFactory.getLogger(ModbusResultHandler.class);
    @Resource
    private ITEquipmentAlarmDataService alarmDataService;

    @Resource
    private PalletDeviceBindingMapper palletDeviceBindingMapper;

    // 线程池：非静态，Spring管理，容器关闭时关闭
    private final ExecutorService executorService;
    // -------------------------- 构造函数（初始化线程池） --------------------------
    public ModbusResultHandler() {
        // 线程池配置：核心线程数=CPU核数，最大线程数=CPU核数*2，队列有界（避免OOM）
        this.executorService = new ThreadPoolExecutor(
                Runtime.getRuntime().availableProcessors(),
                Runtime.getRuntime().availableProcessors() * 2,
                60, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(100), // 队列大小100，超过则触发拒绝策略
                r -> new Thread(r, "modbus-result-handler-" + System.currentTimeMillis()),
                new ThreadPoolExecutor.CallerRunsPolicy() // 队列满时由调用线程执行，避免任务丢失
        );
    }

    // -------------------------- 核心处理逻辑 --------------------------
    @Override
    public void accept(DeviceStatusReaderDto deviceStatusReaderDto) {
        // 校验dto不为null
        if (deviceStatusReaderDto == null) {
            log.error("ModbusResultHandler接收的DeviceStatusReaderDto为null，跳过处理");
            return;
        }
        // 提交任务到线程池，并用Future捕获异常（避免异常被吞噬）
        executorService.submit(() -> {
            try {
                handleData(deviceStatusReaderDto);
            } catch (Throwable e) {
                // 捕获所有异常，记录详细日志（关键：避免线程池任务异常被吞噬）
                log.error("ModbusResultHandler处理数据异常：dto={}", deviceStatusReaderDto, e);
                // 记录告警，感知异常
                recordAlarm(null,
                        "ip:" + deviceStatusReaderDto.getIp() + ",port:" + deviceStatusReaderDto.getPort(),
                        "结果处理器异常：" + e.getMessage());
            }
        });
    }

    /**
     * 实际数据处理（修复空指针、资源泄漏、异常捕获）
     */
    private void handleData(DeviceStatusReaderDto dto) {
        // 1. 提取参数并校验（避免NPE）
        Integer deviceId = dto.getDeviceId();
        int[] data = dto.getRegisterData();
        String ip = dto.getIp();
        Integer port = dto.getPort();

        if (deviceId == null || data == null || StringUtils.isBlank(ip) || port == null) {
            log.error("DeviceStatusReaderDto参数不完整：deviceId={}, ip={}, port={}, data={}",
                    deviceId, ip, port, Arrays.toString(data));
            recordAlarm(null, "ip:" + ip + ",port:" + port, "参数不完整：deviceId/ip/port/data为空");
            return;
        }

        // 2. 查询数据库并校验（避免NPE）
        PalletDeviceBinding palletDeviceBinding;
        try {
            palletDeviceBinding = palletDeviceBindingMapper.selectByTrayIdAndIndex(ip, deviceId);
        } catch (Exception e) {
            log.error("查询PalletDeviceBinding异常：ip={}, deviceId={}", ip, deviceId, e);
            recordAlarm(null, "ip:" + ip + ",port:" + port + ",deviceId:" + deviceId,
                    "查询绑定关系异常：" + e.getMessage());
            return;
        }

        if (palletDeviceBinding == null) {
            log.error("未查询到PalletDeviceBinding：ip={}, deviceId={}", ip, deviceId);
            recordAlarm(null, "ip:" + ip + ",port:" + port + ",deviceId:" + deviceId,
                    "未找到设备绑定关系");
            return;
        }

        // 3. Modbus操作（修复资源泄漏，捕获所有异常）
        ModbusMaster master = null;
        try {
            // 获取ModbusMaster
            master = Modbus4jUtils.createModbusMaster(ip, port);

            log.info(">>> 回调处理[{}]: 接收到数据：deviceId={}, data={}",
                    Thread.currentThread().getName(), deviceId, Arrays.toString(data));

            // 4. 校验数据长度并处理
            if (data.length >= 2) {
                // 更新设备状态
                palletDeviceBinding.setStatus(String.valueOf(data[1]));

                // 校验状态是否允许写时间
                if (data[1] == 1 || data[1] == 3 || data[1] == 4) {
                    writeCurrentTimeToDevice(master, deviceId, palletDeviceBinding, ip, port);
                } else {
                    log.warn("设备状态不允许写时间：deviceId={}, status={}", deviceId, data[1]);
                    recordAlarm(palletDeviceBinding, "设备状态不允许写时间：状态=" + data[1]);
                }
            } else {
                log.warn("设备数据长度不足：deviceId={}, dataLength={}", deviceId, data.length);
                recordAlarm(palletDeviceBinding, "设备数据长度不足：" + data.length + "（需至少2个寄存器）");
            }

        } catch (ModbusInitException e) {
            log.error("Modbus初始化异常：ip={}, port={}", ip, port, e);
            palletDeviceBinding.setStatus("5");
            recordAlarm(palletDeviceBinding, "Modbus初始化失败：" + e.getMessage());
        } catch (ModbusTransportException e) {
            log.error("Modbus传输异常：ip={}, port={}, deviceId={}", ip, port, deviceId, e);
            palletDeviceBinding.setStatus("5");
            recordAlarm(palletDeviceBinding, "Modbus写时间失败：" + e.getMessage());
        } catch (Exception e) {
            // 捕获其他异常（如NPE、数据溢出）
            log.error("处理Modbus数据异常：ip={}, port={}, deviceId={}", ip, port, deviceId, e);
            palletDeviceBinding.setStatus("5");
            recordAlarm(palletDeviceBinding, "数据处理异常：" + e.getMessage());
        } finally {
            // 关键：关闭Modbus连接，避免资源泄漏
            destroyModbusMaster(master, deviceId);
            // 更新数据库（单独捕获异常，避免覆盖连接关闭的异常）
            try {
                palletDeviceBindingMapper.updatePalletDeviceBinding(palletDeviceBinding);
                log.debug("更新PalletDeviceBinding成功：deviceId={}", deviceId);
            } catch (Exception e) {
                log.error("更新PalletDeviceBinding异常：deviceId={}", deviceId, e);
                recordAlarm(palletDeviceBinding, "更新设备状态异常：" + e.getMessage());
            }
        }
    }

    /**
     * 写入当前时间到设备（拆分方法，降低复杂度）
     */
    private void writeCurrentTimeToDevice(ModbusMaster master, int deviceId,
                                          PalletDeviceBinding binding, String ip, int port) throws ModbusTransportException, ModbusInitException {
        Calendar cal = Calendar.getInstance();
        int year = cal.get(Calendar.YEAR);
        int month = cal.get(Calendar.MONTH) + 1;
        int day = cal.get(Calendar.DATE);
        int hour = cal.get(Calendar.HOUR_OF_DAY);
        int minute = cal.get(Calendar.MINUTE);

        // 校验年份是否超过Short范围（避免数据溢出）
        if (year > Short.MAX_VALUE) {
            log.error("年份超过Short最大值：year={}, max={}", year, Short.MAX_VALUE);
            recordAlarm(binding, "写入年份异常：年份超过Short最大值（" + Short.MAX_VALUE + "）");
            return;
        }

        // 写入年份
        if (Modbus4jUtils.writeRegister(master, deviceId, 4, (short) year)) {
            binding.setRecordYear(String.valueOf(year));
        } else {
            recordAlarm(binding, "写入年份失败");
        }

        // 写入月份
        if (Modbus4jUtils.writeRegister(master, deviceId, 5, (short) month)) {
            binding.setRecordMonth(String.valueOf(month));
        } else {
            recordAlarm(binding, "写入月份失败");
        }

        // 写入日期
        if (Modbus4jUtils.writeRegister(master, deviceId, 6, (short) day)) {
            binding.setRecordDate(String.valueOf(day));
        } else {
            recordAlarm(binding, "写入日期失败");
        }

        // 写入小时
        if (Modbus4jUtils.writeRegister(master, deviceId, 7, (short) hour)) {
            binding.setRecordHour(String.valueOf(hour));
        } else {
            recordAlarm(binding, "写入小时失败");
        }

        // 写入分钟
        if (Modbus4jUtils.writeRegister(master, deviceId, 8, (short) minute)) {
            binding.setRecordMinute(String.valueOf(minute));
        } else {
            recordAlarm(binding, "写入分钟失败");
        }
    }

    /**
     * 销毁ModbusMaster（关闭连接，避免资源泄漏）
     */
    private void destroyModbusMaster(ModbusMaster master, int deviceId) {
        if (master == null) return;

        try {
            // 1. 反射关闭Socket（适配Modbus4j 3.0.3）
            if (master instanceof com.serotonin.modbus4j.ip.tcp.TcpMaster) {
                com.serotonin.modbus4j.ip.tcp.TcpMaster tcpMaster = (com.serotonin.modbus4j.ip.tcp.TcpMaster) master;
                Field socketField = com.serotonin.modbus4j.ip.tcp.TcpMaster.class.getDeclaredField("socket");
                socketField.setAccessible(true);
                Socket socket = (Socket) socketField.get(tcpMaster);
                if (socket != null && !socket.isClosed()) {
                    socket.close();
                    log.debug("设备{}: Modbus Socket已关闭", deviceId);
                }
            }
        } catch (Exception e) {
            log.error("设备{}: 关闭Modbus Socket异常", deviceId, e);
        } finally {
            // 2. 调用destroy()方法
            try {
                master.destroy();
                log.debug("设备{}: ModbusMaster已销毁", deviceId);
            } catch (Exception e) {
                log.error("设备{}: 销毁ModbusMaster异常", deviceId, e);
            }
        }
    }

    /**
     * 统一告警记录（修复字段错误，确保写入成功）
     */
    private void recordAlarm(PalletDeviceBinding binding, String alarmMsg) {
        String equipmentCode = binding != null ? binding.getDeviceCode() : "unknown";
        recordAlarm(binding, equipmentCode, alarmMsg);
    }

    private void recordAlarm(PalletDeviceBinding binding, String equipmentCode, String alarmMsg) {
        try {
            TEquipmentAlarmData alarm = new TEquipmentAlarmData();
            alarm.setfAlarmType("04"); // 04.点位告警
            alarm.setfEquipmentCode(equipmentCode);
            alarm.setfAlarmData(alarmMsg);
            alarm.setfCreateTime(new Date()); // 修复字段错误：用fCreateTime而非createTime
            alarmDataService.insertTEquipmentAlarmData(alarm);
            log.debug("告警记录成功：equipmentCode={}, msg={}", equipmentCode, alarmMsg);
        } catch (Exception e) {
            log.error("告警记录失败：equipmentCode={}, msg={}", equipmentCode, alarmMsg, e);
        }
    }

    /**
     * Spring容器关闭时关闭线程池（避免资源泄漏）
     */
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        if (executorService != null && !executorService.isShutdown()) {
            executorService.shutdown();
            // 等待线程池关闭（最多等30秒）
            if (!executorService.awaitTermination(30, TimeUnit.SECONDS)) {
                executorService.shutdownNow();
            }
            log.info("ModbusResultHandler线程池已关闭");
        }
    }

    // 字符串工具方法（避免依赖Apache Commons）
    private static class StringUtils {
        public static boolean isBlank(String str) {
            return str == null || str.trim().isEmpty();
        }
    }
    // 创建通用的停止条件（可选）
    public static Predicate<int[]> createDefaultStopCondition() {
        return values -> values.length >= 2 && values[1] == DeviceStatusReaderAndTimeSetter.TARGET_VALUE;
    }
}
