package com.zehong.system.task;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
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.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.PowerOffCommandEvent;
import com.zehong.system.task.event.ReceivedNbCodeEvent;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
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.Date;
import java.util.List;
import java.util.Map;
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;

    @Autowired
    private ISysConfigService configService;

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

    @Resource
    private IPalletDeviceBindingService palletDeviceBindingService;

    @Resource
    private IReceivedNbFailureHistoryService receivedNbFailureHistoryService;

    // 使用常量定义索引，避免魔法数字
    private static final int AGING_STAGE_INDEX = 3;
    private static final int DEFAULT_HOURS = 72;
    /**
     * 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);
        }
    }

    /**
     * 接收到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());
    }
}
