package com.zehong.system.task;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.util.concurrent.RateLimiter;
import com.serotonin.modbus4j.ModbusMaster;
import com.serotonin.modbus4j.exception.ErrorResponseException;
import com.serotonin.modbus4j.exception.ModbusInitException;
import com.serotonin.modbus4j.exception.ModbusTransportException;
import com.zehong.common.constant.Constants;
import com.zehong.common.constant.RoboticArmConstans;
import com.zehong.common.utils.DateUtils;
import com.zehong.common.utils.http.HttpUtils;
import com.zehong.system.domain.*;
import com.zehong.system.domain.vo.AgingDeviceResultVO;
import com.zehong.system.mapper.RobotArmCommandMapper;
import com.zehong.system.mapper.TStoreyInfoMapper;
import com.zehong.system.mapper.TTrayInfoMapper;
import com.zehong.system.modbus.util.Modbus4jUtils;
import com.zehong.system.service.*;
import com.zehong.system.service.websocket.RobotArmWebSocketHandler;
import com.zehong.system.task.event.CheckPowerOnCommandEvent;
import com.zehong.system.task.event.IotStatusEvent;
import com.zehong.system.task.event.PowerOffCommandEvent;
import com.zehong.system.task.event.ReceivedNbCodeEvent;
import com.zehong.system.task.util.IotDataAndTokenUtil;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author lenovo
 * @date 2025/6/17
 * @description 事件接收器
 */
@Component
public class AllCommandHandler {
    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AllCommandHandler.class);

    @Resource
    private ITEquipmentAlarmDataService alarmDataService;

    @Resource
    private TStoreyInfoMapper tStoreyInfoMapper;

    @Resource
    private TTrayInfoMapper tTrayInfoMapper;

    @Resource
    private ITTrayInfoService trayInfoService;

    @Resource
    private DeviceTaskScheduler deviceTaskScheduler;

    @Resource
    private ISysConfigService configService;

    @Resource
    private RobotArmCommandMapper robotArmCommandMapper;
    @Resource
    private RobotArmWebSocketHandler robotArmWebSocketHandler;

    @Resource
    private IPalletDeviceBindingService palletDeviceBindingService;

    @Resource
    private IReceivedNbFailureHistoryService receivedNbFailureHistoryService;

    @Resource
    private IotDataAndTokenUtil iotDataAndTokenUtil;
    @Resource
    private ISysConfigService sysConfigService;

    // 使用常量定义索引，避免魔法数字
    private static final int AGING_STAGE_INDEX = 3;
    private static final int DEFAULT_HOURS = 72;

    // 根据 IOT 接口文档的 QPS 限制调整，如果接口说限制 50 QPS，就设 45 留余量
    private static final RateLimiter RATE_LIMITER = RateLimiter.create(2);

// 如果想更平滑（防止突发流量冲击），可以带预热期：
// private static final RateLimiter RATE_LIMITER = RateLimiter.create(20.0, 3, TimeUnit.SECONDS);
    /**
     * check是否启动 ，没启动就启动下 开始老化
     *
     * @param event event
     */
    @Async("threadPoolTaskExecutor")
    @EventListener(CheckPowerOnCommandEvent.class)
    public void handleCheckPowerOnCommand(CheckPowerOnCommandEvent event) {

        log.info("handleCheckPowerOnCommand 方法开始处理");

        String fip = event.getIp();
        int fport = event.getPort();
        int registerOffset = event.getRegisterOffset();
        int layer = event.getLayer();

        String storeyCode = event.getDeviceCode() + "-" + event.getLayer();

        log.info("需要发送上电指令 - 设备:{} 层:{} ip:{} 端口号:{}", event.getDeviceCode(), layer, fip, fport);
        ModbusMaster master;
        try {
            master = Modbus4jUtils.createModbusMaster(fip, fport);
            Boolean aBoolean = Modbus4jUtils.readCoilStatus(master, 1, registerOffset);

            log.info("当前 - 设备:{} 层:{} -的 状态是:{}", event.getDeviceCode(), event.getLayer(), aBoolean);
            if (!aBoolean) {
                Modbus4jUtils.writeCoil(master, 1, registerOffset, true);

                // 把预计老化结束时间保存到数据库中，因为可能会不同的老化时间
                Date estimatedEndTime = getAgingEndTime();

                log.info("预计老化结束时间:{}", estimatedEndTime);
                TStoreyInfo tStoreyInfo = tStoreyInfoMapper.selectTStoreyInfoByCode(storeyCode);
                tStoreyInfo.setfStatus("1");
                tStoreyInfo.setfAgingStartTime(new Date());
                tStoreyInfo.setEstimatedEndTime(estimatedEndTime);
                tStoreyInfoMapper.updateTStoreyInfo(tStoreyInfo);

                // 同时 把 托盘的 状态更新
                TTrayInfo tTrayInfo = new TTrayInfo();
                tTrayInfo.setfStatus("1");
                tTrayInfo.setfStoreyCode(storeyCode);
                trayInfoService.updateStatusByStoreCode(tTrayInfo);

                deviceTaskScheduler.scheduleDeviceMonitoring(tStoreyInfo.getfStoreyId(), fip, fport);
            }

        } catch (ModbusInitException | ModbusTransportException | ErrorResponseException e) {
            // 记录异常
            TEquipmentAlarmData alarmData = new TEquipmentAlarmData();
            alarmData.setfAlarmType("03"); // 老化层
            alarmData.setfEquipmentCode(event.getDeviceCode());
            alarmData.setfAlarmData("上电或者是启动任务检测时异常: " + e.getMessage());
            alarmDataService.insertTEquipmentAlarmData(alarmData);
        }

    }
    public Date getAgingEndTime() {
        List<SysConfig> sysConfigs = configService.selectAgingStageTime();
        int milliseconds = getAgingMilliseconds(sysConfigs);
        return DateUtils.addMinutes(new Date(), milliseconds);
    }

    private int getAgingMilliseconds(List<SysConfig> sysConfigs) {
        try {
            if (CollectionUtils.isNotEmpty(sysConfigs) &&
                    sysConfigs.size() > AGING_STAGE_INDEX) {

                SysConfig config = sysConfigs.get(AGING_STAGE_INDEX);
                if (config != null && StringUtils.isNotBlank(config.getConfigValue())) {
                    return Integer.parseInt(config.getConfigValue());
                }
            }
        } catch (NumberFormatException e) {
            log.warn("老化时间配置解析异常，使用默认值", e);
        }

        // 返回默认值（72小时转换为毫秒）
        return DEFAULT_HOURS * 60 * 60 * 1000;
    }

    @Async("threadPoolTaskExecutor")// 异步执行
    @EventListener(PowerOffCommandEvent.class)
    public void handlePowerOffCommand(PowerOffCommandEvent event) {
        String storeyCode = event.getDeviceCode() + "-" + event.getLayer();
        try {
            String ip = event.getIp();
            int port = event.getPort();
            int registerOffset = event.getRegisterOffset();
            log.info("需要发送断电指令 - 设备:{} 层:{}", event.getDeviceCode(), event.getLayer());
            TStoreyInfo tStoreyInfo = tStoreyInfoMapper.selectTStoreyInfoByCode(storeyCode);
            if (tStoreyInfo == null) {
                TEquipmentAlarmData alarmData = new TEquipmentAlarmData();
                // 记录异常数据
                alarmData.setfAlarmType("01"); //01.老化柜 02.机械臂 03.老化层 04.点位
                alarmData.setfEquipmentCode(event.getDeviceCode());
                alarmData.setfAlarmData("下属" + storeyCode + "号老化层不存在");
                alarmDataService.insertTEquipmentAlarmData(alarmData);
            } else {
                // 下料后断电
                ModbusMaster master = Modbus4jUtils.createModbusMaster(ip, port);
                Modbus4jUtils.writeCoil(master, 1, registerOffset, false);
                log.info("已发送断电指令 - 设备:{} 层:{}", event.getDeviceCode(), event.getLayer());
                master.destroy();

                tStoreyInfoMapper.unbindByCode(storeyCode);

                // 清理 托盘 和 层的关联关系
                tTrayInfoMapper.clearStoreyCodeByStoreyCode(storeyCode);
            }
        } catch (ModbusInitException | ModbusTransportException e) {
            log.error("断电指令执行失败 - 设备:{} 层:{}", event.getDeviceCode(), event.getLayer(), e);

            //把这个指令状态 改成 断电失败，不影响下面的指令执行
            robotArmCommandMapper.updateStatusPowerOffFailed(storeyCode);
            // 通过WebSocket广播更新
            robotArmWebSocketHandler.broadcastCommandUpdate();

            // 记录异常
            TEquipmentAlarmData alarmData = new TEquipmentAlarmData();
            alarmData.setfAlarmType("03"); // 老化层
            alarmData.setfEquipmentCode(event.getDeviceCode());
            alarmData.setfAlarmData("断电指令执行失败: " + e.getMessage());
            alarmDataService.insertTEquipmentAlarmData(alarmData);
        }
    }

    /**
     * 接收到IOT状态
     * @param event event
     */
    /**
     * 接收到IOT状态
     */
    @Async("threadPoolTaskExecutor")
    @EventListener(IotStatusEvent.class)
    public void handleIotStatus(IotStatusEvent event) {
        String storeyCode = event.getStoreyCode();
        if (storeyCode == null) {
            return;
        }

        // 1. 查询数据
        List<PalletDeviceBinding> bindings = palletDeviceBindingService.listByStoreyCode(storeyCode);
        if (bindings.isEmpty()) {
            return;
        }

        // 2. 按主板码分组，并过滤掉无效数据（同时提前处理无需调接口的）
        // // 只留 nbCode 有效的 // 网络状态为 1
        Map<String, List<PalletDeviceBinding>> groupByMotherboard = bindings.stream()
                .filter(b -> StringUtils.isNotBlank(b.getNbCode()) && b.getNetworkStatus() == 1)
                .collect(Collectors.groupingBy(PalletDeviceBinding::getMotherboardCode));

        // 处理 nbCode 为空的（直接设状态为 2，加入待更新列表）
        List<PalletDeviceBinding> needUpdateList = new ArrayList<>();
        for (PalletDeviceBinding binding : bindings) {
            if (StringUtils.isBlank(binding.getNbCode())) {
                binding.setIotStatus(2);
                needUpdateList.add(binding);
            }
            // 顺便处理网络状态异常（但不要覆盖后续 IOT 查询结果，网络异常只是前置标记）
            // 注意：如果网络状态 !=1，但 IOT 查询成功，应该以 IOT 为准，这里先不 set，留给后面判断
        }

        if (groupByMotherboard.isEmpty()) {
            // 如果没有需要调接口的，直接批量更新后返回
            if (!needUpdateList.isEmpty()) {
                palletDeviceBindingService.batchUpdateIotStatus(needUpdateList);
            }
            return;
        }

        // 3. 获取 IOT Token（只获取一次）
        String iotToken = iotDataAndTokenUtil.getIOTToken();
        if (StringUtils.isBlank(iotToken)) {
            throw new RuntimeException("IOT token未获取");
        }

        // 4. 构造通用请求头
        Map<String, String> headers = new HashMap<>();
        headers.put("Authorization", iotToken);
        headers.put("Content-Type", "application/json");
        String iotApiUrl = sysConfigService.directSelectConfigByKey(Constants.IOT_DEVICES_SCAN);

        // 5. 循环处理每个主板码（带限流）
        for (Map.Entry<String, List<PalletDeviceBinding>> entry : groupByMotherboard.entrySet()) {
            String motherboardCode = entry.getKey();
            List<PalletDeviceBinding> sameBoardDevices = entry.getValue();
            // 取任意一个设备的 nbCode（因为同一主板下的 nbCode 理论上一致，但为了严谨取第一个非空的）
            String nbCode = sameBoardDevices.stream()
                    .map(PalletDeviceBinding::getNbCode)
                    .filter(StringUtils::isNotBlank)
                    .findFirst()
                    .orElse(null);

            if (nbCode == null) {
                continue;
            }

            // ★★★ Guava 限流（阻塞等待令牌） ★★★
            RATE_LIMITER.acquire();

            // 调用 IOT 接口
            Map<String, Object> params = new HashMap<>();
            params.put("number", nbCode);
            String result = null;
            try {
                result = HttpUtils.get(iotApiUrl, headers, params);
            } catch (Exception e) {
                log.error("调用IOT接口异常, motherboardCode: {}, nbCode: {}", motherboardCode, nbCode, e);
            }

            // 6. 根据结果设置状态（应用到该主板下的所有设备）
            Integer statusToSet;
            if (StringUtils.isNotBlank(result)) {
                Map<String, String> dataMap = iotDataAndTokenUtil.processIotData(result);
                if (dataMap != null && !dataMap.isEmpty()) {
                    statusToSet = 1; // IOT数据正常
                } else {
                    statusToSet = 0; // IOT数据为空
                }
            } else {
                statusToSet = 0; // 请求失败
            }

            // 将状态设置给该组所有设备，并加入待更新列表
            for (PalletDeviceBinding device : sameBoardDevices) {
                device.setIotStatus(statusToSet);
                needUpdateList.add(device);
            }
        }

        // 7. 批量持久化所有改动（一次性提交）
        if (!needUpdateList.isEmpty()) {
            palletDeviceBindingService.batchUpdateIotStatus(needUpdateList);
        }
    }

    /**
     * 接收到NB码
     * @param event event
     */
    @Async("threadPoolTaskExecutor")
    @EventListener(ReceivedNbCodeEvent.class)
    @Transactional(rollbackFor = Exception.class)
    public void handleReceivedNbCode(ReceivedNbCodeEvent event) {

        log.info("接收到NB码时间开始:{}", DateUtils.getTime());

        List<PalletDeviceBinding> palletDeviceBindings = null;

        int times = 0;
        if(event.getTrayId()!=null){
            // 判断一下是否获取过NB码了，如果获取过NB码了，则不处理
            int i = tTrayInfoMapper.checkReceivedNbCodeByTrayId(event.getTrayId());
            if(i == 1){
                return;
            }
            palletDeviceBindings = palletDeviceBindingService.listByTrayId(event.getTrayId());
            times = 1;
        }

        if(StringUtils.isNotBlank(event.getStoreyCode()) && palletDeviceBindings == null){
            // 判断一下是否获取过NB码了，如果获取过NB码了，则不处理
            int i = tTrayInfoMapper.checkReceivedNbCodeByStoreyCode(event.getStoreyCode());
            if(i == 1){
                return;
            }
            palletDeviceBindings = palletDeviceBindingService.listByStoreyCode(event.getStoreyCode());
            times = 2;
        }

        if(palletDeviceBindings ==  null)
            return;

        //获取不为空的 设备号
        List<String> collect = palletDeviceBindings.stream().filter(item -> item.getMotherboardCode() != null)
                .collect(Collectors.toList()).stream().map(PalletDeviceBinding::getMotherboardCode).collect(Collectors.toList());

        if(collect.size() == 0)
            return;

        String mesReceivedNbCodeAddress = configService.directSelectConfigByKey(RoboticArmConstans.MES_RECEIVED_NB_CODE_ADDRESS);

        if(StringUtils.isBlank(mesReceivedNbCodeAddress))
            return;

        String motherboardCodes = String.join(",", collect);

        ReceivedNbFailureHistory receivedNbFailureHistory = new ReceivedNbFailureHistory();
        receivedNbFailureHistory.setTimes(times);
        receivedNbFailureHistory.setTrayCode(palletDeviceBindings.get(0).getfTrayCode());
        receivedNbFailureHistory.setStatus("成功");

        try {
            String result = HttpUtils.sendPost(mesReceivedNbCodeAddress, motherboardCodes);
            if(com.zehong.common.utils.StringUtils.isNotBlank(result)) {
                JSONObject jsonObject = JSON.parseObject(result);
                if(jsonObject.getInteger("code") == 200) {
                    String data = jsonObject.getString("data");
                    // 重点：将JSON数组转成对象集合
                    List<AgingDeviceResultVO> list = JSON.parseArray(data, AgingDeviceResultVO.class);

                    if(list != null && list.size() > 0){
                        // 1. 将返回结果转成 Map：motherboardCode -> nbCode
                        Map<String, String> nbCodeMap = list.stream()
                                .collect(Collectors.toMap(
                                        AgingDeviceResultVO::getMotherboardCode,
                                        AgingDeviceResultVO::getNbCode,
                                        (v1, v2) -> v2  // 如果有重复key，保留后者
                                ));

                        // 2. 遍历 palletDeviceBindings，匹配设置 nbCode
                        palletDeviceBindings.stream()
                                .filter(item -> item.getMotherboardCode() != null && nbCodeMap.containsKey(item.getMotherboardCode()))
                                .forEach(item -> item.setNbCode(nbCodeMap.get(item.getMotherboardCode())));
                    }

                }  else {
                    receivedNbFailureHistory.setStatus("请求返回:" + jsonObject.getInteger("code"));
                }
            }
        } catch (Exception e) {
            receivedNbFailureHistory.setStatus("http请求失败");
        }

        // 更新托盘状态
        if(event.getTrayId()!=null){
            tTrayInfoMapper.updateReceivedNbCodeByTrayId(event.getTrayId());
        } else {
            tTrayInfoMapper.updateReceivedNbCodeByStoreyCode(event.getStoreyCode());
        }
        // 更新
        palletDeviceBindingService.batchUpdateNbCode(palletDeviceBindings);
        // 保存
        receivedNbFailureHistoryService.insertReceivedNbFailureHistory(receivedNbFailureHistory);

        log.info("接收到NB码时间结束:{}", DateUtils.getTime());
    }
}
