package com.zehong.system.task;

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.ip.tcp.TcpMaster;
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.domain.TStoreyInfo;
import com.zehong.system.mapper.TStoreyInfoMapper;
import com.zehong.system.modbus.handler.ModbusResultHandler;
import com.zehong.system.modbus.handler.dto.DeviceStatusReaderDto;
import com.zehong.system.modbus.util.Modbus4jUtils;
import com.zehong.system.service.ITEquipmentAlarmDataService;
import org.apache.commons.lang3.StringUtils;
import org.quartz.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
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.Date;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Predicate;

/**
 * 设备通信Job（修复版：显式暴露错误，避免Trigger变ERROR）
 */
@Component
@DisallowConcurrentExecution // 禁止同一任务并行执行（必须保留）
public class DeviceCommunicationJob implements Job {
    private static final Logger log = LoggerFactory.getLogger(DeviceCommunicationJob.class);

    // -------------------------- 常量配置（统一管理，避免魔法值）--------------------------
    // 超时控制：必须小于Cron周期（假设Cron为5分钟，这里留1分钟缓冲）
    private static final int TOTAL_TASK_TIMEOUT_SEC = 240; // 任务总超时：4分钟
    private static final int SINGLE_PORT_TIMEOUT_SEC = 60; // 单个端口超时：1分钟
    private static final int SINGLE_DEVICE_TIMEOUT_SEC = 10; // 单个设备超时：10秒
    // Modbus配置：取消内置重试，统一用自定义重试
    private static final int MODBUS_CONN_TIMEOUT_MS = 3000; // 连接超时：3秒
    private static final int CUSTOM_RETRY_TIMES = 1; // 自定义重试次数：1次
    private static final int RETRY_DELAY_MS = 200; // 重试间隔：200ms
    // Modbus寄存器配置
    private static final int REG_START_ADDR = 0;
    private static final int REG_READ_COUNT = 10;

    // 工厂（单例）
    private static final ModbusFactory modbusFactory = new ModbusFactory();
    // -------------------------- 依赖注入 --------------------------
    @Resource
    private ITEquipmentAlarmDataService alarmDataService;
    @Resource
    private TStoreyInfoMapper tStoreyInfoMapper;
    @Autowired
    private ModbusResultHandler resultHandler;

    // -------------------------- 核心执行逻辑 --------------------------
    @Override
    public void execute(JobExecutionContext context) {
        // 关键：不隐藏任何致命异常，让Quartz感知错误（但捕获后包装为JobExecutionException）
        long taskStartTime = System.currentTimeMillis();
        String storeyIdStr = getStoreyIdFromContext(context);
        ExecutorService deviceExecutor = null; // 线程池：每次任务新建，结束后关闭

        try {
            // 1. 初始化线程池（每次任务新建，避免泄漏）
            deviceExecutor = new ThreadPoolExecutor(
                    3, 3, // 核心=最大=3（对应3个端口，1:1映射）
                    0, TimeUnit.SECONDS,
                    new LinkedBlockingQueue<>(20), // 队列20个（足够72个设备）
                    r -> new Thread(r, "modbus-port-pool-" + storeyIdStr), // 线程名含设备ID，便于追踪
                    new ThreadPoolExecutor.AbortPolicy() // 队列满时抛异常，显式暴露问题
            );

            // 2. 执行核心逻辑（带总超时）
            boolean executeSuccess = executeWithTotalTimeout(context, deviceExecutor, taskStartTime);
            if (!executeSuccess) {
                log.info("DeviceCommunicationJob执行超时：fStoreyId={}，总耗时={}ms",storeyIdStr,TOTAL_TASK_TIMEOUT_SEC);
            }

            log.info("DeviceCommunicationJob执行成功：fStoreyId={}，总耗时={}ms",
                    storeyIdStr, System.currentTimeMillis() - taskStartTime);

        } catch (JobExecutionException e) {
            // 主动抛出的任务异常：Quartz会记录日志，但Trigger状态仍为NORMAL（需配置Misfire）
            log.error("DeviceCommunicationJob执行异常（已主动抛出，避免Trigger变ERROR）：fStoreyId={}",
                    storeyIdStr, e);

        } catch (Throwable e) {
            // 未预期的致命异常：包装为JobExecutionException，显式暴露
            String errMsg = "DeviceCommunicationJob发生未预期致命异常：fStoreyId=" + storeyIdStr;
            log.error(errMsg, e);
            recordAlarm(null, storeyIdStr, errMsg + "：" + e.getMessage());

        } finally {
            // 关键：无论成功/失败，关闭线程池，避免资源泄漏
            if (deviceExecutor != null && !deviceExecutor.isShutdown()) {
                deviceExecutor.shutdownNow(); // 强制关闭，中断所有未完成任务
                try {
                    // 等待线程池关闭，避免JVM退出前资源未释放
                    if (!deviceExecutor.awaitTermination(5, TimeUnit.SECONDS)) {
                        log.warn("线程池关闭超时，强制终止：fStoreyId={}", storeyIdStr);
                    }
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                    log.error("线程池关闭被中断：fStoreyId={}", storeyIdStr, ie);
                }
            }
            log.debug("DeviceCommunicationJob最终清理完成：fStoreyId={}", storeyIdStr);
        }
    }

    /**
     * 带总超时的核心执行逻辑
     */
    private boolean executeWithTotalTimeout(JobExecutionContext context, ExecutorService deviceExecutor, long taskStartTime) throws Exception {
        String storeyIdStr = getStoreyIdFromContext(context);
        TStoreyInfo storeyInfo = validateAndGetStoreyInfo(storeyIdStr);

        // 用自定义线程池执行端口通信（避免混用CompletableFuture默认线程池）
        CountDownLatch portLatch = new CountDownLatch(3); // 3个端口，计数器3
        AtomicInteger portErrorCount = new AtomicInteger(0);

        // 端口501通信
        deviceExecutor.submit(() -> {
            try {
                executeSinglePort(storeyInfo.getfIp(), 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),
                        storeyIdStr, storeyInfo);
            } catch (Exception e) {
                portErrorCount.incrementAndGet();
                log.error("端口501通信异常：fStoreyId={}", storeyIdStr, e);
            } finally {
                portLatch.countDown();
            }
        });

        // 端口502通信
        deviceExecutor.submit(() -> {
            try {
                executeSinglePort(storeyInfo.getfIp(), 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),
                        storeyIdStr, storeyInfo);
            } catch (Exception e) {
                portErrorCount.incrementAndGet();
                log.error("端口502通信异常：fStoreyId={}", storeyIdStr, e);
            } finally {
                portLatch.countDown();
            }
        });

        // 端口503通信
        deviceExecutor.submit(() -> {
            try {
                executeSinglePort(storeyInfo.getfIp(), 503,
                        Arrays.asList(55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72),
                        storeyIdStr, storeyInfo);
            } catch (Exception e) {
                portErrorCount.incrementAndGet();
                log.error("端口503通信异常：fStoreyId={}", storeyIdStr, e);
            } finally {
                portLatch.countDown();
            }
        });

        // 等待所有端口完成，带总超时
        boolean allPortCompleted = portLatch.await(SINGLE_PORT_TIMEOUT_SEC, TimeUnit.SECONDS);
        long totalCost = System.currentTimeMillis() - taskStartTime;

        // 检查总耗时是否超任务超时
        if (totalCost > TOTAL_TASK_TIMEOUT_SEC * 1000) {
            log.warn("任务总耗时超阈值：fStoreyId={}，耗时={}ms（阈值={}ms）",
                    storeyIdStr, totalCost, TOTAL_TASK_TIMEOUT_SEC * 1000);
            recordAlarm(storeyInfo, "任务总超时：" + totalCost + "ms（阈值" + TOTAL_TASK_TIMEOUT_SEC + "秒）");
            return false;
        }

        // 检查端口错误数
        if (portErrorCount.get() > 0) {
            String errMsg = "部分端口通信失败：fStoreyId=" + storeyIdStr + "，失败端口数=" + portErrorCount.get();
            log.error(errMsg);
            recordAlarm(storeyInfo, errMsg);
            // 此处不抛出异常，仅记录告警，避免Trigger变ERROR
        }

        return allPortCompleted;
    }

    /**
     * 验证参数并获取设备信息（参数错误直接抛出异常）
     */
    private TStoreyInfo validateAndGetStoreyInfo(String storeyIdStr) {
        // 1. 校验storeyIdStr
        if (StringUtils.isBlank(storeyIdStr)) {
            String errMsg = "fStoreyId参数为空，任务终止";
            log.error(errMsg);
            recordAlarm(null, "unknown", errMsg);
        }

        // 2. 转换storeyId
        Long storeyId = null;
        try {
            storeyId = Long.parseLong(storeyIdStr);
        } catch (NumberFormatException e) {
            String errMsg = "fStoreyId格式错误：" + storeyIdStr;
            log.error(errMsg, e);
            recordAlarm(null, storeyIdStr, errMsg);
        }

        // 3. 查询设备信息
        TStoreyInfo storeyInfo = tStoreyInfoMapper.selectTStoreyInfoById(storeyId);
        if (storeyInfo == null) {
            String errMsg = "未查询到设备信息：fStoreyId=" + storeyId;
            log.error(errMsg);
            recordAlarm(null, storeyIdStr, errMsg);
        }

        // 4. 校验设备IP
        assert storeyInfo != null;
        if (StringUtils.isBlank(storeyInfo.getfIp())) {
            String errMsg = "设备IP为空：fStoreyId=" + storeyId;
            log.error(errMsg);
            recordAlarm(storeyInfo, errMsg);
        }

        return storeyInfo;
    }

    /**
     * 单个端口通信（含设备级超时和重试）
     */
    private void executeSinglePort(String ip, int port, List<Integer> deviceIds,
                                   String storeyIdStr, TStoreyInfo storeyInfo) throws Exception {
        log.info("开始端口通信：ip={}，port={}，设备数={}，fStoreyId={}",
                ip, port, deviceIds.size(), storeyIdStr);

        CountDownLatch deviceLatch = new CountDownLatch(deviceIds.size());
        AtomicInteger deviceErrorCount = new AtomicInteger(0);

        for (int deviceId : deviceIds) {
            // 每个设备用独立线程执行，带超时
            Executors.newSingleThreadExecutor(r ->
                    new Thread(r, "modbus-device-" + storeyIdStr + "-" + port + "-" + deviceId)
            ).submit(() -> {
                try {
                    // 单个设备通信（带超时）
                    executeSingleDeviceWithTimeout(ip, port, deviceId, storeyIdStr);
                } catch (Exception e) {
                    deviceErrorCount.incrementAndGet();
                    log.error("设备通信异常：ip={}，port={}，deviceId={}，fStoreyId={}",
                            ip, port, deviceId, storeyIdStr, e);
                    recordAlarm(storeyInfo, "设备" + deviceId + "通信异常：" + e.getMessage());
                } finally {
                    deviceLatch.countDown();
                }
            });
        }

        // 等待该端口所有设备完成，带超时
        boolean allDeviceCompleted = deviceLatch.await((long) SINGLE_DEVICE_TIMEOUT_SEC * deviceIds.size() / 10, TimeUnit.SECONDS);
        if (!allDeviceCompleted) {
            String errMsg = "端口" + port + "部分设备超时：ip=" + ip + "，未完成设备数=" + deviceLatch.getCount() + "，fStoreyId=" + storeyIdStr;
            log.error(errMsg);
            recordAlarm(storeyInfo, errMsg);
        }

        if (deviceErrorCount.get() > 0) {
            String errMsg = "端口" + port + "部分设备通信失败：ip=" + ip + "，失败数=" + deviceErrorCount.get() + "，fStoreyId=" + storeyIdStr;
            log.error(errMsg);
        }

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

    /**
     * 单个设备通信（带超时和重试）
     */
    private void executeSingleDeviceWithTimeout(String ip, int port, int deviceId, String storeyIdStr) {
        // 单个设备通信超时：SINGLE_DEVICE_TIMEOUT_SEC秒
        Future<?> deviceFuture = Executors.newSingleThreadExecutor().submit(() -> {
            try {
                // 带重试的设备通信
                int[] result = readDeviceWithRetry(ip, port, deviceId, storeyIdStr);
                // 处理结果（若有）
                if (resultHandler != null) {
                    resultHandler.accept(new DeviceStatusReaderDto(ip, port, deviceId, result));
                }
            } catch (Exception e) {
                log.info("设备" + deviceId + "通信异常");
            }
        });

        try {
            deviceFuture.get(SINGLE_DEVICE_TIMEOUT_SEC, TimeUnit.SECONDS);
        } catch (TimeoutException e) {
            deviceFuture.cancel(true);
            String errMsg = "设备" + deviceId + "通信超时（>" + SINGLE_DEVICE_TIMEOUT_SEC + "秒）：ip=" + ip + "，port=" + port;
            log.info(errMsg);
        } catch (Exception e) {
            String errMsg = "设备" + deviceId + "通信异常：ip=" + ip + "，port=" + port;
            log.info(errMsg);
        }
    }

    /**
     * 带重试的设备寄存器读取（取消Modbus内置重试，统一自定义重试）
     */
    private int[] readDeviceWithRetry(String ip, int port, int deviceId, String storeyIdStr) throws Exception {
        ModbusMaster master = null;
        int[] result = null;
        boolean readSuccess = false;

        for (int retry = 0; retry <= CUSTOM_RETRY_TIMES; retry++) {
            try {
                // 1. 创建Modbus连接（取消内置重试，master.setRetries(0)）
                master = createModbusMaster(ip, port);
                // 2. 读取寄存器
                result = readDeviceRegisters(master, deviceId);
                // 3. 检查结果（若有停止条件）
                Predicate<int[]> stopCondition = ModbusResultHandler.createDefaultStopCondition();
                if (stopCondition.test(result)) {
                    readSuccess = true;
                    log.debug("设备读取成功（满足停止条件）：ip={}，port={}，deviceId={}，重试次数={}，fStoreyId={}",
                            ip, port, deviceId, retry, storeyIdStr);
                    break;
                } else if (retry < CUSTOM_RETRY_TIMES) {
                    log.debug("设备读取结果不满足条件，准备重试：ip={}，port={}，deviceId={}，重试次数={}，fStoreyId={}",
                            ip, port, deviceId, retry, storeyIdStr);
                    Thread.sleep(RETRY_DELAY_MS);
                }
            } catch (Exception e) {
                if (retry < CUSTOM_RETRY_TIMES) {
                    log.warn("设备读取异常，准备重试：ip={}，port={}，deviceId={}，重试次数={}，fStoreyId={}",
                            ip, port, deviceId, retry, storeyIdStr, e);
                    Thread.sleep(RETRY_DELAY_MS);
                } else {
                    String errMsg = "设备读取重试耗尽（共" + CUSTOM_RETRY_TIMES + "次）：ip=" + ip + "，port=" + port + "，deviceId=" + deviceId;
                    log.error(errMsg, e);
                }
            } finally {
                // 每次重试后销毁连接，避免资源泄漏
                destroyModbusMaster(master, deviceId);
            }
        }

        if (!readSuccess) {
            String errMsg = "设备读取未满足停止条件（重试" + CUSTOM_RETRY_TIMES + "次）：ip=" + ip + "，port=" + port + "，deviceId=" + deviceId;
            log.error(errMsg);
        }

        return result;
    }

    // -------------------------- Modbus工具方法（显式抛出异常）--------------------------
    /**
     * 创建Modbus连接（取消内置重试，统一自定义重试）
     */
    private ModbusMaster createModbusMaster(String ip, int port) throws ModbusInitException {
        IpParameters params = new IpParameters();
        params.setHost(ip);
        params.setPort(port);

        TcpMaster master = (TcpMaster) modbusFactory.createTcpMaster(params, true);
        master.setTimeout(MODBUS_CONN_TIMEOUT_MS);
        master.setRetries(0); // 取消内置重试，避免和自定义重试冲突
        master.init();
        log.debug("Modbus连接创建成功：ip={}，port={}，master={}", ip, port, master);
        return master;
    }

    /**
     * 读取设备寄存器（异常直接抛出）
     */
    private int[] readDeviceRegisters(ModbusMaster master, int deviceId) throws ModbusTransportException {
        ReadHoldingRegistersRequest request = Modbus4jUtils.getReadHoldingRegistersRequest(
                deviceId, REG_START_ADDR, REG_READ_COUNT);
        ModbusResponse response = master.send(request);

        if (!(response instanceof ReadHoldingRegistersResponse)) {
            log.warn("无效Modbus响应类型：" + response.getClass().getName() + "，deviceId=" + deviceId);
        }

        assert response instanceof ReadHoldingRegistersResponse;
        ReadHoldingRegistersResponse regResp = (ReadHoldingRegistersResponse) response;
        short[] signedVals = regResp.getShortData();
        int[] unsignedVals = new int[signedVals.length];
        for (int i = 0; i < signedVals.length; i++) {
            unsignedVals[i] = signedVals[i] & 0xFFFF; // 转换为无符号整数
        }

        log.trace("设备寄存器读取结果：deviceId={}，值={}", deviceId, Arrays.toString(unsignedVals));
        return unsignedVals;
    }

    /**
     * 销毁Modbus连接（反射失败直接抛出异常，显式暴露问题）
     */
    private void destroyModbusMaster(ModbusMaster master, int deviceId) {
        if (master == null) return;

        try {
            // 反射获取Socket（若失败，直接抛异常，不隐藏）
            Socket socket = getUnderlyingSocket(master);
            if (socket != null && !socket.isClosed()) {
                socket.close();
                log.trace("设备{}：Modbus底层Socket已关闭", deviceId);
            }
        } catch (Exception e) {
            String errMsg = "设备" + deviceId + "：销毁Modbus Socket异常（可能导致连接泄漏）";
            log.error(errMsg, e);
            // 此处不抛出异常（避免中断销毁流程），但记录严重告警
            recordAlarm(null, String.valueOf(deviceId), errMsg + "：" + e.getMessage());
        } finally {
            try {
                master.destroy();
                log.trace("设备{}：ModbusMaster已销毁", deviceId);
            } catch (Exception e) {
                log.error("设备{}：销毁ModbusMaster异常", deviceId, e);
            }
        }
    }

    /**
     * 反射获取TcpMaster的Socket（失败直接抛出异常，显式暴露问题）
     */
    private Socket getUnderlyingSocket(ModbusMaster master) {
        if (!(master instanceof TcpMaster)) {
            log.info("TcpMaster类型转换失败：" + master.getClass().getName());
        }

        TcpMaster tcpMaster = (TcpMaster) master;
        try {
            // 反射获取socket字段（根据你的TcpMaster源码确认字段名，必须正确）
            Field socketField = TcpMaster.class.getDeclaredField("socket");
            socketField.setAccessible(true);
            Socket socket = (Socket) socketField.get(tcpMaster);

            if (socket == null) {
                log.warn("TcpMaster的socket字段为null（未建立连接）");
            }
            return socket;
        } catch (NoSuchFieldException e) {
            log.info("反射获取Socket失败：TcpMaster中不存在'socket'字段（版本不匹配");
        } catch (IllegalAccessException e) {
            log.info("反射获取Socket失败：无访问权限（可能被安全管理器拦截）");
        }
        return null;
    }

    // -------------------------- 辅助方法（日志/告警）--------------------------
    /**
     * 从JobContext中获取fStoreyId（失败返回unknown）
     */
    private String getStoreyIdFromContext(JobExecutionContext context) {
        try {
            JobDataMap data = context.getJobDetail().getJobDataMap();
            return data != null ? data.getString("fStoreyId") : "unknown";
        } catch (Exception e) {
            log.error("从JobContext获取fStoreyId失败", e);
            return "unknown";
        }
    }

    /**
     * 记录告警（兼容设备信息为空的场景）
     */
    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 storeyInfo, String alarmData) {
        recordAlarm(storeyInfo, storeyInfo != null ? storeyInfo.getfStoreyCode() : "unknown", alarmData);
    }
}