package com.zehong.system.task;

import com.serotonin.modbus4j.ModbusMaster;
import com.serotonin.modbus4j.exception.ModbusTransportException;
import com.zehong.system.domain.PalletDeviceBinding;
import com.zehong.system.domain.SysRealTimeAdRange;
import com.zehong.system.domain.TEquipmentAlarmData;
import com.zehong.system.domain.TStoreyInfo;
import com.zehong.system.mapper.PalletDeviceBindingMapper;
import com.zehong.system.mapper.SysRealTimeAdRangeMapper;
import com.zehong.system.mapper.TStoreyInfoMapper;
import com.zehong.system.modbus.util.Modbus4jUtils;
import com.zehong.system.service.ITEquipmentAlarmDataService;
import org.apache.commons.lang3.StringUtils;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Arrays;
import java.util.Calendar;
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/11/21
 * @description 71 小时后执行的任务
 */
@Component
@DisallowConcurrentExecution // 禁止同一任务并行执行（必须保留）
public class PrepareFinalExecutionJob 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_DEVICE_TIMEOUT_SEC = 10; // 单个设备超时：10秒

    // 全局线程池 - 避免重复创建
    private static final ExecutorService GLOBAL_DEVICE_EXECUTOR = new ThreadPoolExecutor(
            50, 100, 60, TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(500),
            r -> new Thread(r, "prepare-global-modbus-device"),
            new ThreadPoolExecutor.CallerRunsPolicy()
    );
    // -------------------------- 依赖注入 --------------------------
    @Resource
    private ITEquipmentAlarmDataService alarmDataService;
    @Resource
    private TStoreyInfoMapper tStoreyInfoMapper;
    @Resource
    private PalletDeviceBindingMapper palletDeviceBindingMapper;

    @Resource
    private SysRealTimeAdRangeMapper sysRealTimeAdRangeMapper;


    // -------------------------- 核心执行逻辑 --------------------------
    @Override
    public void execute(JobExecutionContext context) {
        String storeyIdStr = getStoreyIdFromContext(context);
        long startTime = System.currentTimeMillis();

        try {
            TStoreyInfo storeyInfo = validateAndGetStoreyInfo(storeyIdStr);
            if(storeyInfo == null) {
                log.info("无效的storeyId: {}", storeyIdStr);
                recordAlarm(null, storeyIdStr, "PrepareFinalExecutionJob无效的storeyId");
                return;
            }

            // 并行处理3个端口
            List<CompletableFuture<Void>> portFutures = Arrays.asList(
                    processPort(storeyInfo, 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(storeyInfo, 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(storeyInfo, 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("PrepareFinalExecutionJob任务执行成功: fStoreyId={}, 耗时={}ms",
                    storeyIdStr, System.currentTimeMillis() - startTime);

        } catch (TimeoutException e) {
            log.info("任务执行超时: fStoreyId={}", storeyIdStr);
            recordAlarm(null, storeyIdStr, "任务执行超时");
        } catch (Exception e) {
            log.info("任务执行异常: fStoreyId={}", storeyIdStr, e);
            recordAlarm(null, storeyIdStr, "任务执行异常: " + e.getMessage());
        }
    }
    /**
     * 处理单个端口的所有设备
     */
    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(() -> {
            ModbusMaster master = null;
            PalletDeviceBinding binding = 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;
                }

                // 3. 更新设备状态
                binding.setStatus(String.valueOf(result[1]));

                // 3. 更新浓度值
                binding.setConcentration(String.valueOf(result[0]));

                // 4. 更新AD值
                int realAd = result[2];
                binding.setRealTimeAd(realAd);
                SysRealTimeAdRange topOne = sysRealTimeAdRangeMapper.getTopOne();
                if (topOne != null && realAd >= topOne.getRealTimeAdMin() && realAd <= topOne.getRealTimeAdMax()) {
                    binding.setRealTimeStatus("1");
                } else {
                    binding.setRealTimeStatus("0");
                }

                // 4. 条件写入时间
                if (result[1] == 1 || result[1] == 3 || result[1] == 4) {
                    // 重用之前的master连接进行写操作
                    master = Modbus4jUtils.createModbusMaster(ip, port);
                    checkAndUpdateTime(master, deviceId, binding);
                } else {
                    binding.setStatus(result[1] +"");
                }

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

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

            } catch (Exception e) {
                log.error("设备{}处理异常: ip={}, port={}", deviceId, ip, port, e);
                if(binding == null) {
                    binding =  palletDeviceBindingMapper.selectByTrayIdAndIndex(ip, deviceId);
                }
                binding.setStatus("5");
                binding.setWriteTimeStatus("0");
                binding.setRealTimeStatus("0");
                palletDeviceBindingMapper.updatePalletDeviceBinding(binding);
                errorCount.incrementAndGet();
                return false;
            } finally {
                Modbus4jUtils.destroyModbusMaster(master, deviceId);
            }
        }, GLOBAL_DEVICE_EXECUTOR);
    }


    /**
     * 验证参数并获取设备信息（参数错误直接抛出异常）
     */
    private TStoreyInfo validateAndGetStoreyInfo(String storeyIdStr) {
        if (StringUtils.isBlank(storeyIdStr)) {
            log.error("fStoreyId参数为空");
            return null;
        }

        try {
            Long storeyId = Long.parseLong(storeyIdStr);
            TStoreyInfo storeyInfo = tStoreyInfoMapper.selectTStoreyInfoById(storeyId);

            if (storeyInfo == null || StringUtils.isBlank(storeyInfo.getfIp())) {
                log.error("设备信息无效: fStoreyId={}", storeyIdStr);
                return null;
            }

            return storeyInfo;
        } catch (NumberFormatException e) {
            log.error("fStoreyId格式错误: {}", storeyIdStr);
            return null;
        }
    }

    /**
     * 检查并更新时间
     */
    private void checkAndUpdateTime(ModbusMaster master, int deviceId,PalletDeviceBinding binding) {

        try {
            int[] timeRegisters = Modbus4jUtils.readDeviceRegisters(master, deviceId);
            if (timeRegisters.length < 9) {
                log.warn("设备{}时间寄存器读取失败或数据长度不足", deviceId);
                binding.setWriteTimeStatus("0");
                binding.setRealTimeStatus("0");
                recordAlarmByBinding(binding, "设备时间寄存器读取失败");
                return;
            }
            int deviceYear = timeRegisters[4];
            int deviceMonth = timeRegisters[5];
            int deviceDay = timeRegisters[6];
            int deviceHour = timeRegisters[7];
            int deviceMinute = timeRegisters[8];

            log.info("设备{}当前时间: {}-{}-{} {}:{}",
                    deviceId, deviceYear, deviceMonth, deviceDay, deviceHour, deviceMinute);

            // 2. 获取系统当前时间
            Calendar systemCal = Calendar.getInstance();
            int systemYear = systemCal.get(Calendar.YEAR);
            int systemMonth = systemCal.get(Calendar.MONTH) + 1; // Calendar月份从0开始
            int systemDay = systemCal.get(Calendar.DATE);
            int systemHour = systemCal.get(Calendar.HOUR_OF_DAY);
            int systemMinute = systemCal.get(Calendar.MINUTE);
            log.info("系统当前时间: {}-{}-{} {}:{}",
                    systemYear, systemMonth, systemDay, systemHour, systemMinute);

            // 3. 比较时间差异
            if (isTimeConsistent(deviceYear, deviceMonth, deviceDay, deviceHour, deviceMinute,
                    systemYear, systemMonth, systemDay, systemHour, systemMinute)) {
                // 时间一致
                handleTimeConsistent(deviceId, binding);
            } else {
                // 时间不一致
                handleTimeInconsistent(master, deviceId, binding, systemCal);
            }

        } catch (ModbusTransportException e) {

            binding.setWriteTimeStatus("0");
            binding.setRealTimeStatus("0");
            log.error("设备{}时间写入异常", deviceId, e);
            recordAlarmByBinding(binding, "设备时间写入异常: " + e.getMessage());
        }

    }
    /**
     * 判断设备时间与系统时间是否一致（差异在2分钟内）
     * 考虑跨年、跨月等边界情况
     */
    private boolean isTimeConsistent(int deviceYear, int deviceMonth, int deviceDay,
                                     int deviceHour, int deviceMinute,
                                     int systemYear, int systemMonth, int systemDay,
                                     int systemHour, int systemMinute) {
        // 1. 首先检查年份差异（考虑跨年）
        if (Math.abs(deviceYear - systemYear) > 1) {
            log.debug("年份差异超过1年: 设备{}年 vs 系统{}年", deviceYear, systemYear);
            return false;
        }

        // 2. 转换为总分钟数进行比较（考虑跨年、跨月、跨日）
        long deviceTotalMinutes = calculateTotalMinutes(deviceYear, deviceMonth, deviceDay, deviceHour, deviceMinute);
        long systemTotalMinutes = calculateTotalMinutes(systemYear, systemMonth, systemDay, systemHour, systemMinute);

        long minuteDiff = Math.abs(deviceTotalMinutes - systemTotalMinutes);

        log.debug("时间差异比较: 设备分钟数={}, 系统分钟数={}, 差异={}分钟",
                deviceTotalMinutes, systemTotalMinutes, minuteDiff);

        // 3. 差异在2分钟内认为一致
        return minuteDiff <= 2;
    }

    /**
     * 处理时间一致的情况
     */
    private void handleTimeConsistent(int deviceId, PalletDeviceBinding binding) {
        log.info("设备{}时间与系统时间一致，无需更新", deviceId);
        binding.setRunTimeStatus("1"); // 时间状态正常
        log.info("设备{}时间检查完成：时间一致", deviceId);
    }
    /**
     * 处理时间不一致的情况 - 更新设备时间
     */
    private void handleTimeInconsistent(ModbusMaster master, int deviceId,
                                        PalletDeviceBinding binding, Calendar systemCal) {
        log.info("设备{}时间与系统时间不一致，开始更新时间", deviceId);
        try {

            // 1 如果时间状态为空，则写入时间
            if(binding.getWriteTimeStatus() == null) {
                int year = systemCal.get(Calendar.YEAR);
                int month = systemCal.get(Calendar.MONTH) + 1;
                int day = systemCal.get(Calendar.DATE);
                int hour = systemCal.get(Calendar.HOUR_OF_DAY);
                int minute = systemCal.get(Calendar.MINUTE);

                // 写入时间寄存器
                boolean success;
                success = Modbus4jUtils.writeRegister(master, deviceId, 4, (short) year);
                success = success && Modbus4jUtils.writeRegister(master, deviceId, 5, (short) month);
                success = success && Modbus4jUtils.writeRegister(master, deviceId, 6, (short) day);
                success = success && Modbus4jUtils.writeRegister(master, deviceId, 7, (short) hour);
                success = success && Modbus4jUtils.writeRegister(master, deviceId, 8, (short) minute);

                if (success) {
                    binding.setRecordYear(String.valueOf(year));
                    binding.setRecordMonth(String.valueOf(month));
                    binding.setRecordDate(String.valueOf(day));
                    binding.setRecordHour(String.valueOf(hour));
                    binding.setRecordMinute(String.valueOf(minute));
                    binding.setRunTimeStatus("1");
                    log.info("设备{}时间更新成功: {}-{}-{} {}:{}",
                            deviceId, year, month, day, hour, minute);
                } else {
                    binding.setRunTimeStatus("0");
                    log.error("设备{}时间更新失败", deviceId);
                    recordAlarmByBinding(binding, "设备时间更新失败");
                }
                // 2. 如果时间状态为1，则更新 时间运行状态  为异常 状态
            } else if("1".equals(binding.getWriteTimeStatus())) {
                log.info("设备{}时间状态为1，则更新 时间运行状态  为异常 状态", deviceId);
                binding.setRunTimeStatus("0");
            } // 3. 写时间状态  异常状态；不管
        } catch (Exception e) {
            binding.setRunTimeStatus("0");
            log.error("设备{}时间更新异常", deviceId, e);
            recordAlarmByBinding(binding, "设备时间更新异常: " + e.getMessage());
        }
    }

    /**
     * 计算从参考时间点（如2000年）开始的总分钟数
     * 用于准确比较时间差异，考虑跨年跨月
     */
    private long calculateTotalMinutes(int year, int month, int day, int hour, int minute) {
        // 使用2000-01-01 00:00:00作为参考点
        Calendar cal = Calendar.getInstance();
        cal.set(Calendar.YEAR, 2000);
        cal.set(Calendar.MONTH, Calendar.JANUARY);
        cal.set(Calendar.DAY_OF_MONTH, 1);
        cal.set(Calendar.HOUR_OF_DAY, 0);
        cal.set(Calendar.MINUTE, 0);
        cal.set(Calendar.SECOND, 0);
        cal.set(Calendar.MILLISECOND, 0);

        long baseTimeInMillis = cal.getTimeInMillis();

        // 设置目标时间
        cal.set(Calendar.YEAR, year);
        cal.set(Calendar.MONTH, month - 1); // 转换为Calendar月份（0-based）
        cal.set(Calendar.DAY_OF_MONTH, day);
        cal.set(Calendar.HOUR_OF_DAY, hour);
        cal.set(Calendar.MINUTE, minute);
        cal.set(Calendar.SECOND, 0);
        cal.set(Calendar.MILLISECOND, 0);

        long targetTimeInMillis = cal.getTimeInMillis();

        // 返回总分钟数
        return (targetTimeInMillis - baseTimeInMillis) / (1000 * 60);
    }

    /**
     * 写入当前时间到设备
     */
    private void writeCurrentTimeToDevice(ModbusMaster master, int deviceId,
                                          PalletDeviceBinding binding) {
        try {
            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);

            // 写入时间寄存器
            boolean success;
            success = Modbus4jUtils.writeRegister(master, deviceId, 4, (short) year);
            success &= Modbus4jUtils.writeRegister(master, deviceId, 5, (short) month);
            success &= Modbus4jUtils.writeRegister(master, deviceId, 6, (short) day);
            success &= Modbus4jUtils.writeRegister(master, deviceId, 7, (short) hour);
            success &= Modbus4jUtils.writeRegister(master, deviceId, 8, (short) minute);

            if (success) {
                binding.setRecordYear(String.valueOf(year));
                binding.setRecordMonth(String.valueOf(month));
                binding.setRecordDate(String.valueOf(day));
                binding.setRecordHour(String.valueOf(hour));
                binding.setRecordMinute(String.valueOf(minute));
                log.debug("设备{}时间写入成功", deviceId);
            } else {
                binding.setWriteTimeStatus("0");
                recordAlarmByBinding(binding, "设备时间写入失败");
            }
        } catch (Exception e) {
            binding.setWriteTimeStatus("0");
            log.error("设备{}时间写入异常", deviceId, e);
            recordAlarmByBinding(binding, "设备时间写入异常: " + e.getMessage());
        }
    }

    // -------------------------- 辅助方法（日志/告警）--------------------------
    /**
     * 从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);
    }


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

    private void recordAlarmByBingding(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);
        }
    }
}
