package com.zehong.system.task;

import com.serotonin.modbus4j.ModbusMaster;
import com.serotonin.modbus4j.exception.ModbusInitException;
import com.serotonin.modbus4j.exception.ModbusTransportException;
import com.zehong.common.utils.StringUtils;
import com.zehong.system.domain.PalletDeviceBinding;
import com.zehong.system.domain.TEquipmentAlarmData;
import com.zehong.system.domain.TStoreyInfo;
import com.zehong.system.mapper.PalletDeviceBindingMapper;
import com.zehong.system.mapper.TStoreyInfoMapper;
import com.zehong.system.modbus.util.Modbus4jUtils;
import com.zehong.system.service.ITEquipmentAlarmDataService;
import com.zehong.system.service.websocket.RobotArmWebSocketHandler;
import org.quartz.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * @author lenovo
 * @date 2025/12/10
 * @description 老化第二阶段 读  继电器状态 脉冲状态 模组状态 SIM卡状态 网络状态 并再写入 1执行自检
 */
@Component
public class AgingStageTwoProcessJob  implements Job {

    private static final Logger log = LoggerFactory.getLogger(AgingStageTwoProcessJob.class);
    // -------------------------- 常量配置（统一管理，避免魔法值）--------------------------
    // 超时控制：必须小于Cron周期（假设Cron为5分钟，这里留1分钟缓冲）
    private static final int TOTAL_TASK_TIMEOUT_SEC = 240; // 任务总超时：4分钟
    private static final int SINGLE_DEVICE_TIMEOUT_SEC = 10; // 单个设备超时：10秒
    @Resource
    private RobotArmWebSocketHandler robotArmWebSocketHandler;
    @Resource
    private ITEquipmentAlarmDataService alarmDataService;

    @Resource
    private TStoreyInfoMapper tStoreyInfoMapper;
    @Resource
    private Scheduler scheduler;
    @Resource
    private PalletDeviceBindingMapper palletDeviceBindingMapper;
    // 全局线程池 - 避免重复创建
    private static final ExecutorService GLOBAL_DEVICE_EXECUTOR = new ThreadPoolExecutor(
            50, 100, 60, TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(500),
            r -> new Thread(r, "final-global-modbus-device"),
            new ThreadPoolExecutor.CallerRunsPolicy()
    );
    @Override
    public void execute(JobExecutionContext context) {
        // 1. 初始化变量，避免空指针
        JobDataMap data;
        String fPowerOutageIp;
        Long fStoreyId = null;
        int fPowerOutagePort;
        TStoreyInfo tStoreyInfo = null;
        long startTime = System.currentTimeMillis();

        try {
            // 2. 提取并校验所有参数
            data = context.getJobDetail().getJobDataMap();
            if (data == null) {
                log.info("JobDataMap为空，终止执行");
                return;
            }
            fStoreyId = data.getLong("fStoreyId");
            fPowerOutageIp = data.getString("fPowerOutageIp");
            fPowerOutagePort = data.getInt("fPowerOutagePort");

            if(StringUtils.isBlank(fPowerOutageIp)) {
                log.info("参数缺失：fStoreyId={}, ip={}, port={}，终止执行", fStoreyId, fPowerOutageIp, fPowerOutagePort);
                return;
            }

            // 4. 查询设备信息
            tStoreyInfo = tStoreyInfoMapper.selectTStoreyInfoById(fStoreyId);
            if (tStoreyInfo == null) {
                log.info("未查询到设备信息：fStoreyId={}，终止执行", fStoreyId);
                return;
            }

            // 5. 执行业务逻辑（Modbus写操作，单独捕获异常）
            String storeyCode = tStoreyInfo.getfStoreyCode();
            if (StringUtils.isBlank(storeyCode)) {
                log.info("设备编码为空：fStoreyId={}，终止执行", fStoreyId);
                return;
            }

//             20251206 读取 继电器状态 脉冲状态 模组状态 SIM卡状态 网络状态
//             并行处理3个端口
            List<CompletableFuture<Void>> portFutures = Arrays.asList(
                    processPort(tStoreyInfo, 501, Arrays.asList(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27)),
                    processPort(tStoreyInfo, 502, Arrays.asList(28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54)),
                    processPort(tStoreyInfo, 503, Arrays.asList(55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72))
            );
            // 等待所有端口完成，带总超时
            CompletableFuture<Void> allPorts = CompletableFuture.allOf(
                    portFutures.toArray(new CompletableFuture[0])
            );

            allPorts.get(TOTAL_TASK_TIMEOUT_SEC, TimeUnit.SECONDS);

            log.info("AgingStageTwoProcessJob 任务执行成功: fStoreyId={}, 耗时={}ms",
                    tStoreyInfo.getfStoreyId(), System.currentTimeMillis() - startTime);

            log.info("=== AgingStageTwoProcessJob 执行完成：fStoreyId={} ===", fStoreyId);

        } catch (Throwable e) {
            // 9. 捕获所有异常（包括Error）
            log.error("=== AgingStageTwoProcessJob 致命异常：fStoreyId={} ===", fStoreyId, e);
            // 记录告警（即使设备信息为空，也尝试记录）
            try {
                if (tStoreyInfo != null && StringUtils.isNotBlank(tStoreyInfo.getfStoreyCode())) {
                    recordAlarm(tStoreyInfo, "老化第二阶段致命异常：" + e.getMessage());
                } else {
                    TEquipmentAlarmData alarm = new TEquipmentAlarmData();
                    alarm.setfAlarmType("03");
                    alarm.setfEquipmentCode(fStoreyId + "");
                    alarm.setfAlarmData("老化第二阶段异常：" + e.getMessage());
                    alarm.setfCreateTime(new Date());
                    alarmDataService.insertTEquipmentAlarmData(alarm);
                }
            } catch (Exception alarmEx) {
                log.error("=== 告警记录失败 ===", alarmEx);
            }
            // 禁止抛出任何异常！！！
        } finally {
            log.error("=== AgingStageTwoProcessJob finally：fStoreyId={} ===", fStoreyId);
        }
    }
    /**
     * 处理单个端口的所有设备
     */
    private CompletableFuture<Void> processPort(TStoreyInfo storeyInfo, int port, List<Integer> deviceIds) {
        return CompletableFuture.runAsync(() -> {
            String ip = storeyInfo.getfIp();
            String storeyIdStr = storeyInfo.getfStoreyId().toString();

            log.info("开始端口通信: ip={}, port={}, 设备数={}", ip, port, deviceIds.size());

            AtomicInteger errorCount = new AtomicInteger(0);

            // 并行处理该端口的所有设备
            List<CompletableFuture<Boolean>> deviceFutures = deviceIds.stream()
                    .map(deviceId -> processDeviceWithWrite(ip, port, deviceId, errorCount))
                    .collect(Collectors.toList());

            try {
                // 等待该端口所有设备完成
                CompletableFuture<Void> allDevices = CompletableFuture.allOf(
                        deviceFutures.toArray(new CompletableFuture[0])
                );

                // 端口超时 = 设备数 * 单设备超时 / 并发因子
                int portTimeout = Math.max(30, deviceIds.size() * SINGLE_DEVICE_TIMEOUT_SEC / 5);
                allDevices.get(portTimeout, TimeUnit.SECONDS);

            } catch (TimeoutException e) {
                log.warn("端口{}通信超时: ip={}, fStoreyId={}", port, ip, storeyIdStr);
                recordAlarm(storeyInfo, "端口" + port + "通信超时");
            } catch (Exception e) {
                log.error("端口{}通信异常: ip={}, fStoreyId={}", port, ip, storeyIdStr, e);
            }

            if (errorCount.get() > 0) {
                log.warn("端口{}部分设备失败: 失败数={}, fStoreyId={}",
                        port, errorCount.get(), storeyIdStr);
            }

            log.info("端口通信完成: ip={}, port={}, fStoreyId={}", ip, port, storeyIdStr);
        }, GLOBAL_DEVICE_EXECUTOR);
    }

    /**
     * 处理单个设备（读取 + 条件写入）
     */
    private CompletableFuture<Boolean> processDeviceWithWrite(String ip, int port, int deviceId, AtomicInteger errorCount) {
        return CompletableFuture.supplyAsync(() -> {
            PalletDeviceBinding binding;
            ModbusMaster master = null;
            try {
                // 1. 读取设备数据
                int[] result = Modbus4jUtils.readDeviceWithRetry(ip, port, deviceId);

                // 2. 查询设备绑定信息
                binding = palletDeviceBindingMapper.selectByTrayIdAndIndex(ip, deviceId);
                if (binding == null) {
                    log.warn("未找到设备绑定: ip={}, deviceId={}", ip, deviceId);
                    recordAlarm(null, "ip:" + ip + ",port:" + port + ",deviceId:" + deviceId, "未找到设备绑定");
                    errorCount.incrementAndGet();
                    return false;
                }
                binding.setRelayStatus(result[10]);
                binding.setPulseStatus(result[11]);
                binding.setModuleStatus(result[12]);
                binding.setSimCardStatus(result[13]);
                binding.setNetworkStatus(result[14]);
               //  重用之前的master连接进行写操作
                master = Modbus4jUtils.createModbusMaster(ip, port);
                writeSelfCheckStatus(master, deviceId, binding);

                // 5. 更新数据库
                palletDeviceBindingMapper.updatePalletDeviceBinding(binding);

                log.debug("设备{}处理完成: ip={}, port={}, status={}", deviceId, ip, port, result[1]);
                return true;

            } catch (Exception e) {
                log.info("设备{}处理异常: ip={}, port={}", deviceId, ip, port, e);
                errorCount.incrementAndGet();
                return false;
            }finally {
                Modbus4jUtils.destroyModbusMaster(master, deviceId);
            }
        }, GLOBAL_DEVICE_EXECUTOR);
    }
    /**
     * 写入自检状态 让设备开始自检
     */
    private void writeSelfCheckStatus(ModbusMaster master, int deviceId,
                                      PalletDeviceBinding binding){
        // 20251206 写完时间写自检，写自检就在时间后边写就行，不管时间写不写成功
        try {
            Modbus4jUtils.writeRegister(master, deviceId, 15, (short) 1);
            binding.setWriteSelfCheckStatus(1);
        } catch (Exception e) {
            binding.setWriteSelfCheckStatus(0);
        }
    }

    /**
     * 记录告警（兼容设备信息为空的场景）
     */
    private void recordAlarm(TStoreyInfo storeyInfo, String equipmentCode, String alarmData) {
        try {
            TEquipmentAlarmData alarm = new TEquipmentAlarmData();
            alarm.setfAlarmType("03"); // 老化层告警
            alarm.setfEquipmentCode(storeyInfo != null ? storeyInfo.getfStoreyCode() : equipmentCode);
            alarm.setfAlarmData(alarmData);
            alarm.setfCreateTime(new Date());
            alarmDataService.insertTEquipmentAlarmData(alarm);
            log.debug("告警记录成功：设备编码={}，内容={}", alarm.getfEquipmentCode(), alarmData);
        } catch (Exception e) {
            log.error("告警记录失败：设备编码={}，内容={}", equipmentCode, alarmData, e);
        }
    }
    // 辅助方法：记录告警（抽离，避免代码重复）
    private void recordAlarm(TStoreyInfo tStoreyInfo, String alarmMsg) {
        try {
            TEquipmentAlarmData alarm = new TEquipmentAlarmData();
            alarm.setfAlarmType("03");
            alarm.setfEquipmentCode(tStoreyInfo.getfStoreyCode());
            alarm.setfAlarmData(alarmMsg);
            alarm.setfCreateTime(new Date());
            alarmDataService.insertTEquipmentAlarmData(alarm);
            log.info("告警记录完成：{}", tStoreyInfo.getfStoreyCode());
        } catch (Exception e) {
            log.error("告警记录失败：{}", tStoreyInfo.getfStoreyCode(), e);
        }
    }

    private void cleanUpJobs(Long fStoreyId,JobExecutionContext context) throws SchedulerException {
        // 清理通信任务
        JobKey commJobKey = new JobKey("COMM_" + fStoreyId, "DEVICE_TASKS");
        if (scheduler.checkExists(commJobKey)) {
            boolean commDeleted = scheduler.deleteJob(commJobKey);
            log.info("通信任务清理结果：{}（{}）", commDeleted ? "成功" : "失败", commJobKey.getName());
        }
        // 清理自身任务
        JobKey selfJobKey = context.getJobDetail().getKey();
        if (scheduler.checkExists(selfJobKey)) {
            boolean selfDeleted = scheduler.deleteJob(selfJobKey);
            log.info("自身任务清理结果：{}（{}）", selfDeleted ? "成功" : "失败", selfJobKey.getName());
        }
    }

    // ... 业务逻辑方法
    private void executeBusinessLogic(String fPowerOutageIp, int fPowerOutagePort,int registerOffsets) {

        // 10 层
        ModbusMaster master;
        try {
            master = Modbus4jUtils.createModbusMaster(fPowerOutageIp, fPowerOutagePort);
            Boolean aBoolean = Modbus4jUtils.writeCoil(master, 1,registerOffsets,false );
        } catch (ModbusInitException | ModbusTransportException e) {
            throw new RuntimeException(e);
        }
    }
    private void notifyCommandsUpdate() {
        robotArmWebSocketHandler.broadcastCommandUpdate();
    }
}
