package com.zehong.system.modbus.business;

import com.serotonin.modbus4j.ModbusFactory;
import com.serotonin.modbus4j.ModbusMaster;
import com.serotonin.modbus4j.exception.ModbusInitException;
import com.serotonin.modbus4j.exception.ModbusTransportException;
import com.serotonin.modbus4j.ip.IpParameters;
import com.serotonin.modbus4j.msg.ModbusResponse;
import com.serotonin.modbus4j.msg.ReadHoldingRegistersRequest;
import com.serotonin.modbus4j.msg.ReadHoldingRegistersResponse;
import com.zehong.system.domain.TEquipmentAlarmData;
import com.zehong.system.modbus.handler.dto.DeviceStatusReaderDto;
import com.zehong.system.modbus.util.Modbus4jUtils;
import com.zehong.system.service.ITEquipmentAlarmDataService;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Predicate;

/**
 * @author lenovo
 * @date 2025/6/27
 * @description modbus4j 单独提取出来的 读设备状态并设置时间
 */
@Component
public class DeviceStatusReaderAndTimeSetter {

    // 常量改为public以便外部访问
    public static final int START_ADDRESS = 0;
    public static final int REGISTER_COUNT = 10;
    public static final int TARGET_VALUE = 1;
    public static final int MAX_RETRIES = 3;
    public static final int RETRY_DELAY = 500;
    public static final int TIMEOUT_MINUTES = 5;
    // 工厂
    private static final ModbusFactory modbusFactory = new ModbusFactory();

    @Resource
    private ITEquipmentAlarmDataService alarmDataService;

    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DeviceStatusReaderAndTimeSetter.class);
    /**
     * 读取设备寄存器（线程安全版）
     */
    private int[] readDeviceRegisters(ModbusMaster master, int deviceId )
            throws ModbusTransportException {

        // 创建读取请求
        ReadHoldingRegistersRequest request = Modbus4jUtils.getReadHoldingRegistersRequest(deviceId,START_ADDRESS, REGISTER_COUNT);

        // 发送请求并获取响应
        ModbusResponse response = master.send(request);

        // 检查响应类型
        if (!(response instanceof ReadHoldingRegistersResponse)) {
            throw new IllegalArgumentException("Invalid response type: " + response.getClass().getName());
        }

        ReadHoldingRegistersResponse regResponse = (ReadHoldingRegistersResponse) response;
        short[] signedValues = regResponse.getShortData();

        // 转换为无符号整数
        int[] unsignedValues = new int[signedValues.length];
        for (int i = 0; i < signedValues.length; i++) {
            unsignedValues[i] = signedValues[i] & 0xFFFF;
        }

        return unsignedValues;
    }
    /**
     * 带自定义条件的重试读取（线程安全版）
     */
    private int[] readWithConditionalRetry(ModbusMaster threadMaster,String ip, int port, int deviceId,
                                                  Predicate<int[]> conditionChecker)
            throws InterruptedException {

        TEquipmentAlarmData alarmData;

        int[] result = null;
        int attempt = 0;
        boolean conditionMet = false;

        if(threadMaster != null) {

            while (attempt <= MAX_RETRIES && !conditionMet) {
                attempt++;
                try {
                    log.info("当前 - 尝试第:{}次 读取设备ip:{} port:{} device:{}的数据", attempt, ip,port,deviceId);

                    // 读取数据
                    result = readDeviceRegisters(threadMaster, deviceId);

                    // 使用自定义条件检查
                    if (conditionChecker.test(result)) {
                        log.info("当前  设备ip:{} port:{} device:{}的数据条件满足", ip,port,deviceId);
                        conditionMet = true;
                    } else if (attempt <= MAX_RETRIES) {
                        log.info("当前  设备ip:{} port:{} device:{}的数据条件未满足，等待重试...", ip,port,deviceId);
                        Thread.sleep(RETRY_DELAY);
                    }
                } catch (Exception e) {
                    alarmData = new TEquipmentAlarmData();
                    alarmData.setfAlarmType("04"); //01.老化柜 02.机械臂 03.老化层 04.点位
                    alarmData.setfEquipmentCode("ip:" +  ip + ",port:" +  port + ",deviceId:" + deviceId);
                    alarmData.setfAlarmData("读取失败");
                    alarmDataService.insertTEquipmentAlarmData(alarmData);

                    if (attempt <= MAX_RETRIES) {
                        Thread.sleep(RETRY_DELAY);
                    }
                }
            }

            // 如果达到最大重试次数仍未满足条件
            if (!conditionMet) {
                log.info("当前  设备ip:{} port:{} device:{}的尝试次数达到最大:{}", ip,port,deviceId,MAX_RETRIES);
            }
        }
        return result != null ? result : new int[REGISTER_COUNT];
    }

    /**
     * 创建新的Modbus连接
     */
    private static ModbusMaster createModbusMaster(String ip, int port) throws ModbusInitException {
        IpParameters params = new IpParameters();
        params.setHost(ip);
        params.setPort(port);

        ModbusMaster master = modbusFactory.createTcpMaster(params, true);
        master.setTimeout(3000);
        master.setRetries(1);
        master.init();

        return master;
    }
    /**
     * 启动多设备监控（核心方法）
     */
    public void startMultiDeviceMonitoring(
            String ip,
            int port,
            List<Integer> deviceIds,
            Consumer<DeviceStatusReaderDto> resultHandler,
            Predicate<int[]> stopCondition ) {
        if (deviceIds == null || deviceIds.isEmpty()) {
            System.out.println("⚠️ 警告: 设备ID列表为空，不执行监控");
            return;
        }

        final CountDownLatch latch = new CountDownLatch(deviceIds.size());
        ExecutorService executor = Executors.newFixedThreadPool(deviceIds.size());

        for (int deviceId : deviceIds) {
            final int devId = deviceId;
            executor.submit(() -> {
                ModbusMaster threadMaster = null;
                try {

                    // 1 获取 线程专有的Modbus连接
                    threadMaster = createModbusMaster(ip, port);

                    // 2 初始化线程专有的Modbus连接 并尝试读取数据
                    // 为什么传了 master 还传 ip 和 port ，因为 master 要读完后才释放，而 ip 和 port 是为了log用
                    int[] result = readWithConditionalRetry(threadMaster,ip, port, devId, stopCondition);

                    // 创建包含完整信息的结果对象
                    DeviceStatusReaderDto deviceStatusReaderDto = new DeviceStatusReaderDto(ip, port, devId, result);
                    // 3 设置回调数据
                    resultHandler.accept(deviceStatusReaderDto);
                } catch (ModbusInitException e) {
                    TEquipmentAlarmData alarmData = new TEquipmentAlarmData();
                    alarmData.setfAlarmType("03"); //01.老化柜 02.机械臂 03.老化层 04.点位
                    alarmData.setfEquipmentCode("ip:" +  ip + ",port:" +  port);
                    alarmData.setfAlarmData("定时任务巡检:Modbus初始化失败");
                    alarmDataService.insertTEquipmentAlarmData(alarmData);
                } catch (Exception e) {
                    TEquipmentAlarmData alarmData = new TEquipmentAlarmData();
                    alarmData.setfAlarmType("03"); //01.老化柜 02.机械臂 03.老化层 04.点位
                    alarmData.setfEquipmentCode("ip:" +  ip + ",port:" +  port);
                    alarmData.setfAlarmData("监控任务异常:" + e.getMessage());
                    alarmDataService.insertTEquipmentAlarmData(alarmData);
                } finally {
                    if (threadMaster != null) threadMaster.destroy();
                    latch.countDown();
                    log.info("当前 - 设备ip:{} port:{} device:{}的监控任务完成", ip,port,deviceId);
                }
            });
        }

        executor.shutdown();
        try {
            if (!executor.awaitTermination(TIMEOUT_MINUTES, TimeUnit.MINUTES)) {
                TEquipmentAlarmData alarmData = new TEquipmentAlarmData();
                alarmData.setfAlarmType("03"); //01.老化柜 02.机械臂 03.老化层 04.点位
                alarmData.setfEquipmentCode("ip:" +  ip + ",port:" +  port);
                alarmData.setfAlarmData("警告: 部分设备监控任务超时未完成");
                alarmDataService.insertTEquipmentAlarmData(alarmData);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}
