package com.fuyuanshen.web.service.device;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fuyuanshen.app.domain.AppPersonnelInfo;
import com.fuyuanshen.app.domain.AppPersonnelInfoRecords;
import com.fuyuanshen.app.domain.bo.AppPersonnelInfoBo;
import com.fuyuanshen.app.domain.dto.AppDeviceLogoUploadDto;
import com.fuyuanshen.app.domain.vo.AppPersonnelInfoVo;
import com.fuyuanshen.app.mapper.AppPersonnelInfoMapper;
import com.fuyuanshen.app.mapper.AppPersonnelInfoRecordsMapper;
import com.fuyuanshen.equipment.service.IAppBusinessFileService;
import com.fuyuanshen.equipment.service.IAppOperationVideoService;
import com.fuyuanshen.common.core.constant.GlobalConstants;
import com.fuyuanshen.common.core.domain.model.AppLoginUser;
import com.fuyuanshen.common.core.exception.BadRequestException;
import com.fuyuanshen.common.core.exception.ServiceException;
import com.fuyuanshen.common.core.utils.ImageToCArrayConverter;
import com.fuyuanshen.common.core.utils.MapstructUtils;
import com.fuyuanshen.common.core.utils.StringUtils;
import com.fuyuanshen.common.json.utils.JsonUtils;
import com.fuyuanshen.common.redis.utils.RedisUtils;
import com.fuyuanshen.common.satoken.utils.AppLoginHelper;
import com.fuyuanshen.common.satoken.utils.LoginHelper;
import com.fuyuanshen.equipment.domain.*;
import com.fuyuanshen.equipment.domain.bo.DeviceAlarmBo;
import com.fuyuanshen.equipment.domain.dto.AppDeviceSendMsgBo;
import com.fuyuanshen.equipment.domain.form.DeviceForm;
import com.fuyuanshen.equipment.domain.query.DeviceTypeQueryCriteria;
import com.fuyuanshen.equipment.enums.DeviceActiveStatusEnum;
import com.fuyuanshen.equipment.mapper.DeviceLogMapper;
import com.fuyuanshen.equipment.mapper.DeviceMapper;
import com.fuyuanshen.equipment.mapper.DeviceTypeGrantsMapper;
import com.fuyuanshen.equipment.mapper.DeviceTypeMapper;
import com.fuyuanshen.equipment.service.DeviceAssignmentsService;
import com.fuyuanshen.equipment.service.IDeviceAlarmService;
import com.fuyuanshen.global.mqtt.base.MqttXinghanJson;
import com.fuyuanshen.global.mqtt.config.MqttGateway;
import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants;
import com.fuyuanshen.global.mqtt.constants.MqttConstants;
import com.fuyuanshen.web.domain.Dto.DeviceDebugLogoUploadDto;
import com.fuyuanshen.web.domain.Dto.DeviceXinghanInstructDto;
import com.fuyuanshen.web.domain.vo.DeviceXinghanDetailVo;
import com.fuyuanshen.web.enums.AlarmTypeEnum;
import com.fuyuanshen.web.util.AliyunVoiceUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.*;

import static com.fuyuanshen.common.core.constant.GlobalConstants.GLOBAL_REDIS_KEY;
import static com.fuyuanshen.common.core.utils.ImageToCArrayConverter.convertHexToDecimal;
import static com.fuyuanshen.equipment.service.impl.DeviceServiceImpl.USER_ID_SEPARATOR;
import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.*;

@Slf4j
@Service
@RequiredArgsConstructor
public class DeviceXinghanBizService {

    private final DeviceMapper deviceMapper;
    private final AppPersonnelInfoMapper appPersonnelInfoMapper;
    private final DeviceTypeMapper deviceTypeMapper;
    private final MqttGateway mqttGateway;
    private final DeviceLogMapper deviceLogMapper;
    private final AppPersonnelInfoRecordsMapper appPersonnelInfoRecordsMapper;
    private final IDeviceAlarmService deviceAlarmService;
    private final DeviceTypeGrantsMapper deviceTypeGrantsMapper;
    private final DeviceAssignmentsService deviceAssignmentsService;
    private final IAppBusinessFileService appBusinessFileService;
    private final IAppOperationVideoService appOperationVideoService;
    @Autowired
    private ObjectMapper objectMapper;
    private final AliyunVoiceUtil voiceUtil;

    /**
     * 所有档位的描述表
     * key   : 指令类型，如 "ins_DetectGrade"、"ins_LightGrade" ……
     * value : Map<Integer,String>  值 -> 描述
     */
    private static final Map<String, Map<Integer, String>> GRADE_DESC = Map.of(
            "ins_DetectGrade", Map.of(1, "低档", 2, "中档", 3, "高档"),
            "ins_LightGrade",  Map.of(1, "强光", 2, "弱光"),
            "ins_SOSGrade",  Map.of(1, "爆闪模式", 2, "红蓝模式"),
            "ins_ShakeBit",  Map.of(1, "开启报警")
            // 再加 4、5、6…… 档，直接往 Map 里塞即可
    );

    /**
     * 根据指令类型和值，返回中文描述
     */
    private static String resolveGradeDesc(String type, int value) {
        return GRADE_DESC.getOrDefault(type, Map.of())
                .getOrDefault(value, "关闭");
    }

    /**
     * 设置静电预警档位
     */
    public void upDetectGradeSettings(DeviceXinghanInstructDto dto) {
        sendCommand(dto, "ins_DetectGrade","静电预警档位");
    }

    /**
     * 设置照明档位
     */
    public void upLightGradeSettings(DeviceXinghanInstructDto dto) {
        sendCommand(dto, "ins_LightGrade","照明档位");
    }

    /**
     * 设置SOS档位
     */
    public void upSOSGradeSettings(DeviceXinghanInstructDto dto) {
        if(dto.getIsBluetooth()){
            long deviceId = dto.getDeviceId();
            // 1. 使用Optional简化空值检查，使代码更简洁
            Device device = Optional.ofNullable(deviceMapper.selectById(deviceId))
                    .orElseThrow(() -> new ServiceException("设备不存在"));
            int sosGrade = Integer.parseInt(dto.getInstructValue());
            // 6. 新建报警信息
            createAlarm(device.getId(),device.getDeviceImei(),"ins_SOSGrade",sosGrade);
        }else {
            sendCommand(dto, "ins_SOSGrade","SOS档位");
        }
    }

    /**
     * 触发异步报警
     * Spring 会自动调用 AsyncConfig.getAsyncExecutor() 来执行此方法
     */
    @Async
    public void executeSosCall(String phone) {
        log.info("[SOS业务] 准备发起语音拨号 -> 目标: {}", phone);
        Map<String, String> params = Map.of("device", "670");
        String callId = voiceUtil.sendTtsSync(phone, "TTS_328730104", params);

        if (callId != null) {
            log.info("[SOS业务] 拨号指令下发成功, callId: {}", callId);
            // 这里可以记录拨打日志到数据库
        } else {
            log.error("[SOS业务] 拨号指令下发失败，请检查配置或余额");
        }
    }

    /**
     * 设置强制报警
     */
    public void upShakeBitSettings(DeviceXinghanInstructDto dto) {
        sendCommand(dto, "ins_ShakeBit","强制报警");
    }

    public DeviceXinghanDetailVo getInfo(Long id) {
        Device device = deviceMapper.selectById(id);
        if (device == null) {
            throw new RuntimeException("请先将设备入库！！！");
        }

        DeviceXinghanDetailVo vo = new DeviceXinghanDetailVo();
        vo.setDeviceId(device.getId());
        vo.setDeviceName(device.getDeviceName());
        vo.setDevicePic(device.getDevicePic());
        vo.setDeviceImei(device.getDeviceImei());
        vo.setDeviceMac(device.getDeviceMac());
        vo.setDeviceStatus(device.getDeviceStatus());
        DeviceType deviceType = deviceTypeMapper.selectById(device.getDeviceType());
        if (deviceType != null) {
            vo.setCommunicationMode(Integer.valueOf(deviceType.getCommunicationMode()));
            vo.setTypeName(deviceType.getTypeName());
        }
        vo.setBluetoothName(device.getBluetoothName());


        QueryWrapper<AppPersonnelInfo> qw = new QueryWrapper<AppPersonnelInfo>()
                .eq("device_id", device.getId());
        AppPersonnelInfo appPersonnelInfo = appPersonnelInfoMapper.selectOne(qw);
        if (appPersonnelInfo != null) {
            AppPersonnelInfoVo personnelInfoVo = MapstructUtils.convert(appPersonnelInfo, AppPersonnelInfoVo.class);
            vo.setPersonnelInfo(personnelInfoVo);
        }
        //设备在线状态
        String onlineStatus = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + device.getDeviceImei()+ DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX);
        if(StringUtils.isNotBlank(onlineStatus)){
            vo.setOnlineStatus(1);
        }else{
            vo.setOnlineStatus(0);
        }
        String deviceStatus = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + device.getDeviceImei() + DeviceRedisKeyConstants.DEVICE_STATUS_KEY_PREFIX);
        // 获取设备上报的设备状态
        if(StringUtils.isNotBlank(deviceStatus)){
            try {
                MqttXinghanJson deviceJson = objectMapper.readValue(deviceStatus, MqttXinghanJson.class);
                vo.setStaLightGrade(deviceJson.getStaLightGrade());
                vo.setStaPowerPercent(deviceJson.getStaPowerPercent());
                vo.setStaSOSGrade(deviceJson.getStaSOSGrade());
                vo.setStaDetectGrade(deviceJson.getStaDetectGrade());
                vo.setStaPowerTime(deviceJson.getStaPowerTime());
            } catch (Exception e) {
                throw new IllegalArgumentException("设备状态缓存格式非法", e);
            }
        }else{
            vo.setStaPowerPercent(0);
        }

        // 获取经度纬度
        String locationKey = GlobalConstants.GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + device.getDeviceImei() + DeviceRedisKeyConstants.DEVICE_LOCATION_KEY_PREFIX;
        String locationInfo = RedisUtils.getCacheObject(locationKey);
        if(StringUtils.isNotBlank(locationInfo)){
            JSONObject jsonObject = JSONObject.parseObject(locationInfo);
            vo.setLongitude(jsonObject.get("longitude").toString());
            vo.setLatitude(jsonObject.get("latitude").toString());
            vo.setAddress((String)jsonObject.get("address"));
        }

        return vo;
    }

    /**
     * 上传设备logo
     */
    public void uploadDeviceLogo(AppDeviceLogoUploadDto bo) {
        try {
            Device device = deviceMapper.selectById(bo.getDeviceId());
            if (device == null) {
                throw new ServiceException("设备不存在");
            }
            if (isDeviceOffline(device.getDeviceImei())) {
                throw new ServiceException("设备已断开连接：" + device.getDeviceName());
            }
            MultipartFile file = bo.getFile();

            byte[] largeData = ImageToCArrayConverter.convertImageToCArray(file.getInputStream(), 160, 80, 25600);
            log.info("长度：" + largeData.length);

            log.info("原始数据大小: {} 字节", largeData.length);

            int[] ints = convertHexToDecimal(largeData);
            RedisUtils.setCacheObject(GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + device.getDeviceImei() +DEVICE_BOOT_LOGO_KEY_PREFIX, Arrays.toString(ints), Duration.ofSeconds(5 * 60L));

            Map<String, Object> payload = Map.of("ins_PicTrans",
                    Collections.singletonList(0));
            String topic = MqttConstants.GLOBAL_PUB_KEY + device.getDeviceImei();
            String json = JsonUtils.toJsonString(payload);

            try {
                mqttGateway.sendMsgToMqtt(topic, 1, json);
            } catch (Exception e) {
                log.error("上传开机画面失败, topic={}, payload={}", topic, json, e);
                throw new ServiceException("上传LOGO失败：" + e.getMessage());
            }
            log.info("发送上传开机画面到设备消息=>topic:{},payload:{}", MqttConstants.GLOBAL_PUB_KEY+device.getDeviceImei(),json);

            recordDeviceLog(device.getId(), device.getDeviceName(), "上传开机画面", "上传开机画面", AppLoginHelper.getUserId());
        } catch (Exception e){
            throw new ServiceException("发送指令失败");
        }
    }

    /**
     * 批量上传设备logo
     */
    @Transactional(rollbackFor = Exception.class)
    public void uploadDeviceLogoBatch(DeviceDebugLogoUploadDto batchDto) {
        if (CollectionUtils.isEmpty(batchDto.getDeviceIds())) {
            throw new ServiceException("设备列表为空");
        }

        // 1. 一次性把设备查出来（N -> 1）
        QueryWrapper<Device> query = new QueryWrapper<>();
        query.in("id", batchDto.getDeviceIds());
        List<Device> devices = deviceMapper.selectList(query);
        if (devices.size() != batchDto.getDeviceIds().size()) {
            throw new ServiceException("部分设备不存在");
        }

        // 2. 图片只转换一次（160*80 固定尺寸）
        byte[] largeData;
        try {
            largeData = ImageToCArrayConverter.convertImageToCArray(
                    batchDto.getFile().getInputStream(), 160, 80, 25600);
        } catch (IOException e) {
            throw new ServiceException("图片解析失败");
        }
        int[] picArray = convertHexToDecimal(largeData);

        // 3. 过滤离线设备 & 组装指令
        List<Device> onlineDevices = devices.stream()
                .filter(d -> !isDeviceOffline(d.getDeviceImei()))
                .toList();
        onlineDevices.forEach(d -> {
            String redisKey = GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + d.getDeviceImei() + DEVICE_BOOT_LOGO_KEY_PREFIX;

            // 如果 Redis 里已存在，直接跳过
            if (RedisUtils.getCacheObject(redisKey) != null) {
                return;   // 跳过本次循环
            }

            RedisUtils.setCacheObject(
                    redisKey,
                    Arrays.toString(picArray),
                    Duration.ofSeconds(5 * 60L));
            // 3.2 MQTT 下发
            Map<String, Object> payload =
                    Collections.singletonMap("ins_PicTrans", Collections.singletonList(0));
            String topic = MqttConstants.GLOBAL_PUB_KEY + d.getDeviceImei();
            String json = JsonUtils.toJsonString(payload);

            try {
                mqttGateway.sendMsgToMqtt(topic, 1, json);
            } catch (Exception e) {
                log.error("上传开机画面失败, topic={}, payload={}", topic, json, e);
                throw new ServiceException("上传LOGO失败：" + e.getMessage());
            }

            recordDeviceLog(d.getId(), d.getDeviceName(), "上传开机画面", "上传开机画面", AppLoginHelper.getUserId());

        });
    }

    /**
     * 人员登记
     * @param bo
     */
    @Transactional(rollbackFor = Exception.class)   // 1. 事务
    public boolean registerPersonInfo(AppPersonnelInfoBo bo) {
        Long deviceId = bo.getDeviceId();
        Device deviceObj = deviceMapper.selectById(deviceId);
        if (deviceObj == null) {
            throw new RuntimeException("请先将设备入库！！！");
        }
        if (deviceObj.getDeviceImei() == null) {
            return saveOrUpdatePersonnelInfo(bo, deviceId);
        }
        if (isDeviceOffline(deviceObj.getDeviceImei())) {
            throw new ServiceException("设备已断开连接：" + deviceObj.getDeviceName());
        }

        List<String> list = new ArrayList<>();
        list.add(bo.getUnitName());
        list.add(bo.getName());
        list.add(bo.getPosition());
        list.add(bo.getCode());
        String key = GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + deviceObj.getDeviceImei() + ":app_send_message_data";
        RedisUtils.deleteObject(key);                             // 先整体删掉
        RedisUtils.setCacheList(key, list);              // 再重新写入
        RedisUtils.expire(key, Duration.ofSeconds(5*60));

        Map<String, Object> payload = Map.of("ins_TexTrans",
                Collections.singletonList(0));
        String topic = MqttConstants.GLOBAL_PUB_KEY + deviceObj.getDeviceImei();
        String json = JsonUtils.toJsonString(payload);

        try {
            mqttGateway.sendMsgToMqtt(topic, 1, json);
        } catch (Exception e) {
            log.error("人员信息登记失败, topic={}, payload={}", topic, json, e);
            throw new ServiceException("人员信息登记失败：" + e.getMessage());
        }
        log.info("发送人员信息登记到设备消息=>topic:{},payload:{}", MqttConstants.GLOBAL_PUB_KEY + deviceObj.getDeviceImei(), bo);

        recordDeviceLog(deviceId, deviceObj.getDeviceName(), "人员信息登记", JSON.toJSONString(bo), AppLoginHelper.getUserId());

        return saveOrUpdatePersonnelInfo(bo, deviceId);
    }

    /**
     * 仅内部调用，已处于 @Transactional 内
     */
    private Boolean saveOrUpdatePersonnelInfo(AppPersonnelInfoBo bo, Long deviceId) {
        // 1. 先查主表有没有
        AppPersonnelInfo main = appPersonnelInfoMapper.selectOne(
                Wrappers.<AppPersonnelInfo>lambdaQuery()
                        .eq(AppPersonnelInfo::getDeviceId, deviceId));

        if (main == null) {
            /* ====== 新增场景 ====== */
            main = MapstructUtils.convert(bo, AppPersonnelInfo.class);
            main.setDeviceId(deviceId);
            // 1.1 主表插入 -> 主键回写
            appPersonnelInfoMapper.insert(main);   // 执行后 main.getId() 一定有值
        } else {
            /* ====== 更新场景 ====== */
            // 1.2 把 bo 的新值刷到主表
            appPersonnelInfoMapper.update(null,
                    Wrappers.<AppPersonnelInfo>update()
                            .eq("device_id", deviceId)
                            .set("name", bo.getName())
                            .set("position", bo.getPosition())
                            .set("unit_name", bo.getUnitName())
                            .set("code", bo.getCode())
                            .set("update_time", new Date()));
        }

        // 2. 无论新增/更新，都插入一条履历
        AppPersonnelInfoRecords record = new AppPersonnelInfoRecords();
        // 只拷业务字段，不拷时间、id、操作人
        BeanUtil.copyProperties(main, record, "id", "createTime", "updateTime", "createBy", "updateBy");
        record.setId(null);          // 让自增
        record.setPersonnelId(main.getId()); // 关键：主键已回写，不会为 null
        appPersonnelInfoRecordsMapper.insert(record);

        return true;
    }

    /**
     * 发送报警信息
     * @param bo
     * @return
     */
    public int sendAlarmMessage(AppDeviceSendMsgBo bo) {
        List<Long> deviceIds = bo.getDeviceIds();

        // 1. 简化非空检查和抛出异常
        if (deviceIds == null || deviceIds.isEmpty()) {
            throw new ServiceException("请选择设备");
        }
        if(!StringUtils.isNotEmpty(bo.getSendMsg()))
        {
            throw new ServiceException("请输入发送内容");
        }
        QueryWrapper<Device> queryWrapper = new QueryWrapper<>();
        // 使用 in 语句根据id集合查询
        queryWrapper.in("id", deviceIds);
        // 2. 将批量查询设备，减少数据库交互次数
        List<Device> devices = deviceMapper.selectList(queryWrapper);
        if (devices.size() != deviceIds.size()) {
            // 如果查询回来的设备数量不一致，说明有设备不存在，此处可以优化为更详细的提示
            throw new ServiceException("部分设备不存在");
        }

        try {
            for (Device device : devices) {
                String deviceImei = device.getDeviceImei();
                String deviceName = device.getDeviceName();

                // 3. 在循环中进行设备状态检查，快速失败
                if (isDeviceOffline(deviceImei)) {
                    // 如果设备离线，可以选择继续处理下一个设备，或者抛出异常。这里选择抛出异常。
                    throw new ServiceException(deviceName + ", 设备已断开连接");
                }

                // 4. 将Redis和MQTT操作封装在一个方法中，提高可读性
                sendSingleAlarmMessage(device, bo.getSendMsg());

                // 5. 批量更新设备状态，提高效率
                UpdateWrapper<Device> updateWrapper = new UpdateWrapper<>();
                updateWrapper.eq("id", device.getId())
                        .eq("binding_user_id", AppLoginHelper.getUserId())
                        .set("send_msg", bo.getSendMsg());
                deviceMapper.update(updateWrapper);

                // 6. 记录操作日志
                recordDeviceLog(device.getId(), deviceName, "发送紧急通知", bo.getSendMsg(), AppLoginHelper.getUserId());
            }
        } catch (ServiceException e) {
            // 捕获并重新抛出自定义异常，避免内层异常被外层泛化捕获
            log.error("发送告警信息指令失败: {}", e.getMessage(), e);
            throw e;
        } catch (Exception e) {
            log.error("发送告警信息指令发生未知错误", e);
            throw new ServiceException("发送告警信息指令失败");
        }

        return 1;
    }
    /* ---------------------------------- 私有通用方法 ---------------------------------- */

    /**
     * 封装单个设备发送告警信息的逻辑
     */
    private void sendSingleAlarmMessage(Device device, String message) {
        String deviceImei = device.getDeviceImei();
        int msgLen = message.length() / 2;
        StringBuilder msgBuilder = new StringBuilder(message);
        msgBuilder.insert(msgLen, '|');
        // 缓存告警消息到Redis
        RedisUtils.setCacheObject(GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + deviceImei + DEVICE_ALARM_MESSAGE_KEY_PREFIX, msgBuilder.toString(), Duration.ofSeconds(5 * 60L));

        // 构建并发送MQTT消息
        Map<String, Object> payload = Map.of("ins_BreakNews", Collections.singletonList(0));
        String topic = MqttConstants.GLOBAL_PUB_KEY + deviceImei;
        String json = JsonUtils.toJsonString(payload);

        try {
            mqttGateway.sendMsgToMqtt(topic, 1, json);
            log.info("发送紧急通知成功 => topic:{}, payload:{}", topic, json);
        } catch (Exception e) {
            log.error("发送紧急通知失败, topic={}, payload={}", topic, json, e);
            throw new ServiceException("发送紧急通知失败：" + e.getMessage());
        }
    }

    /**
     * 发送设备控制指令
     *
     * @param dto 设备指令数据传输对象，包含设备ID和指令值等信息
     * @param payloadKey 指令负载数据的键名
     * @param deviceAction 设备操作类型描述
     */
    private void sendCommand(DeviceXinghanInstructDto dto, String payloadKey, String deviceAction) {
        long deviceId = dto.getDeviceId();

        // 1. 使用Optional简化空值检查，使代码更简洁
        Device device = Optional.ofNullable(deviceMapper.selectById(deviceId))
                .orElseThrow(() -> new ServiceException("设备不存在"));

        String deviceImei = device.getDeviceImei();
        String deviceName = device.getDeviceName();

        // 2. 提前进行设备状态检查，逻辑更清晰
        if (isDeviceOffline(deviceImei)) {
            throw new ServiceException("设备已断开连接：" + deviceName);
        }

        // 3. 统一处理类型转换异常，避免在业务逻辑中混杂try-catch
        int value;
        try {
            value = Integer.parseInt(dto.getInstructValue());
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("指令值格式不正确，必须为整数。", e);
        }

        // 4. 使用Map.of()或Map.ofEntries()创建不可变Map，更简洁且线程安全
        Map<String, List<Integer>> payload = Map.of(payloadKey, List.of(value));

        String topic = MqttConstants.GLOBAL_PUB_KEY + deviceImei;
        String json = JsonUtils.toJsonString(payload);

        try {
            mqttGateway.sendMsgToMqtt(topic, 1, json);
            log.info("发送指令成功 => topic:{}, payload:{}", topic, json);
        } catch (Exception e) {
            log.error("发送指令失败, topic={}, payload={}", topic, json, e);
            throw new ServiceException("发送指令失败：" + e.getMessage());
        }

        // 5. 将日志记录和描述解析放在try-catch块之外，确保无论是否成功发送指令都能执行
        String content = resolveGradeDesc("ins_DetectGrade", value);
        recordDeviceLog(device.getId(),
                deviceName,
                deviceAction,
                content,
                AppLoginHelper.getUserId());

        // 6. 新建报警信息
        createAlarm(device.getId(),deviceImei,payloadKey,value);
    }

    /**
     * 批量发送设备控制指令
     *
     * @param dto 设备ID列表
     * @param payloadKey 指令负载数据的键名
     * @param deviceAction 设备操作类型描述
     */
    @Transactional(rollbackFor = Exception.class)   // 1. 事务注解
    public void sendCommandBatch(DeviceXinghanInstructDto dto, String payloadKey, String deviceAction) {
        List<String> errorMessages = Collections.synchronizedList(new ArrayList<>());
        int value;
        try {
            value = Integer.parseInt(dto.getInstructValue());
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("指令值格式不正确，必须为整数。", e);
        }
        Map<String, List<Integer>> payload = Map.of(payloadKey, List.of(value));

        // 一次性查询所有设备信息
        List<Device> devices = deviceMapper.selectList(
                new QueryWrapper<Device>().lambda().in(Device::getId, dto.getDeviceIds())
        );
        // 日志信息
        String contentText = resolveGradeDesc(payloadKey, value);

        List<DeviceLog> logs = new ArrayList<>();
        try {
            for (Device device : devices) {
                String deviceImei = device.getDeviceImei();
                String deviceName = device.getDeviceName();

                // 2. 提前进行设备状态检查，逻辑更清晰
                if (isDeviceOffline(deviceImei)) {
                    throw new ServiceException("设备已断开连接：" + deviceName);
                }

                String topic = MqttConstants.GLOBAL_PUB_KEY + deviceImei;
                String json = JsonUtils.toJsonString(payload);

                mqttGateway.sendMsgToMqtt(topic, 1, json);
                log.info("发送指令成功 => topic:{}, payload:{}", topic, json);

                // 创建设备日志实体
                DeviceLog deviceLog = new DeviceLog();
                deviceLog.setDeviceId(device.getId());
                deviceLog.setDeviceAction(deviceAction);
                deviceLog.setContent(contentText);
                deviceLog.setCreateBy(AppLoginHelper.getUserId());
                deviceLog.setDeviceName(deviceName);
                deviceLog.setCreateTime(new Date());
                logs.add(deviceLog);

                createAlarm(device.getId(), deviceImei, payloadKey, value);
            }
            deviceLogMapper.insertBatch(logs);
        } catch (ServiceException e) {
            // 捕获并重新抛出自定义异常，避免内层异常被外层泛化捕获
            log.error("批量发送指令失败: {}", e.getMessage(), e);
            throw e;
        } catch (Exception e) {
            log.error("批量发送指令发生未知错误", e);
            throw new ServiceException("批量发送指令失败");
        }
    }

    /**
     * 检查设备是否离线
     */

//    private boolean isDeviceOffline(String imei) {
//        // 原方法名语义相反，这里取反，使含义更清晰
//        return getDeviceStatus(imei);
//    }

    /**
     * 新建报警 Bo
     */
    private void createAlarm(Long deviceId, String deviceImei,
                            String payloadKey, int value) {
        // 这里直接放你原来的 createAlarmBo 全部逻辑
        if (!"ins_SOSGrade".equals(payloadKey) || value == 0) {
            return;
        }
        AlarmTypeEnum type = AlarmTypeEnum.SOS;
        String redisKey = buildAlarmRedisKey(deviceImei, type);
        Long alarmId = RedisUtils.getCacheObject(redisKey);
        // 已存在未结束报警 -> 什么都不做（同一条报警）
        if (alarmId != null) {
            // key 还在 -> 同一条报警，只续期
            RedisUtils.setCacheObject(redisKey, alarmId, Duration.ofMinutes(10));
            return;
        }
        // 不存在 -> 新建
        DeviceAlarmBo bo = new DeviceAlarmBo();
        bo.setDeviceId(deviceId);
        bo.setDeviceImei(deviceImei);
        bo.setDeviceAction(0); // 强制报警
        bo.setStartTime(new Date());
        bo.setTreatmentState(1);  // 未处理
        bo.setContent("强制报警：" + type.getDesc());
        String location = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + deviceImei + DEVICE_LOCATION_KEY_PREFIX);
        if (StrUtil.isNotBlank(location)) {
            bo.setLocation(JSONObject.parseObject(location).getString("address"));
        }
        deviceAlarmService.insertByBo(bo);
        RedisUtils.setCacheObject(redisKey, bo.getId(), Duration.ofMinutes(10));
    }

    /**
     * key 构建
     */
    private String buildAlarmRedisKey(String deviceImei, AlarmTypeEnum type) {
        return StrUtil.format("{}{}{}{}:alarm_id",
                GLOBAL_REDIS_KEY, DEVICE_KEY_PREFIX, deviceImei, type.getSuffix());
    }

    /**
     * 记录设备操作日志
     * @param deviceId 设备ID
     * @param content 日志内容
     * @param operator 操作人
     */
    private void recordDeviceLog(Long deviceId,String deviceName, String deviceAction, String content, Long operator) {
        try {
            // 创建设备日志实体
            com.fuyuanshen.equipment.domain.DeviceLog deviceLog = new com.fuyuanshen.equipment.domain.DeviceLog();
            deviceLog.setDeviceId(deviceId);
            deviceLog.setDeviceAction(deviceAction);
            deviceLog.setContent(content);
            deviceLog.setCreateBy(operator);
            deviceLog.setDeviceName(deviceName);
            deviceLog.setCreateTime(new Date());

            // 插入日志记录
            deviceLogMapper.insert(deviceLog);
        } catch (Exception e) {
            log.error("记录设备操作日志失败: {}", e.getMessage(), e);
        }
    }

    private boolean isDeviceOffline(String deviceImei) {
        String deviceOnlineStatusRedisKey = GlobalConstants.GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX+ deviceImei + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX ;
        return RedisUtils.getCacheObject(deviceOnlineStatusRedisKey)==null;
    }

    public List<DeviceType> queryDeviceTypes() {
        DeviceTypeQueryCriteria criteria = new DeviceTypeQueryCriteria();

        return deviceTypeMapper.findAll(criteria);
    }

    /**
     * 校验唯一性约束
     */
    private void validateDeviceUnique(DeviceForm form) {
        if (form.getDeviceMac() != null && form.getBluetoothName() == null) {
            throw new BadRequestException("请填写蓝牙名称！！！");
        }

        // 使用 QueryWrapper 替代 lambdaQuery()
        Long macCount = deviceMapper.selectCount(new LambdaQueryWrapper<Device>()
                .eq(Device::getDeviceMac, form.getDeviceMac()));
        if (macCount > 0) {
            throw new BadRequestException("设备MAC已存在！！！");
        }

        Long imeiCount = deviceMapper.selectCount(new LambdaQueryWrapper<Device>()
                .eq(Device::getDeviceImei, form.getDeviceImei()));
        if (imeiCount > 0) {
            throw new BadRequestException("设备IMEI已存在！！！");
        }
    }

    // @Log("新增设备")
    public void addDevice(DeviceForm deviceForm) {
        validateDeviceUnique(deviceForm);

        DeviceTypeQueryCriteria queryCriteria = new DeviceTypeQueryCriteria();
        queryCriteria.setDeviceTypeId(deviceForm.getDeviceType());
        queryCriteria.setCustomerId(LoginHelper.getUserId());
        DeviceTypeGrants typeGrants = deviceTypeGrantsMapper.selectById(queryCriteria.getDeviceTypeId());
        if (typeGrants == null) {
            throw new ServiceException("设备类型不存在!!!");
        }
        DeviceType deviceTypes = deviceTypeMapper.selectById(typeGrants.getDeviceTypeId());
        if (deviceTypes == null) {
            throw new ServiceException("设备类型不存在!!!");
        }

        // 转换对象并插入数据库
        Device device = new Device();

        BeanUtil.copyProperties(deviceForm, device, true);
        device.setDeviceNo(createDeviceNo());
        AppLoginUser loginUser = AppLoginHelper.getLoginUser();
        device.setCurrentOwnerId(loginUser.getUserId());
        device.setOriginalOwnerId(loginUser.getUserId());
        device.setCreateByName(loginUser.getNickname());
        device.setTypeName(deviceTypes.getTypeName());
        device.setDeviceType(deviceTypes.getId());
        device.setDevicePic(deviceTypes.getDevicePic());
        if (device.getDeviceImei() != null) {
            device.setPubTopic("A/" + device.getDeviceImei());
            device.setSubTopic("B/" + device.getDeviceImei());
        }
        // 0 未绑定
        device.setBindingStatus(0);
        deviceMapper.insert(device);

        Long deviceId = device.getDeviceId();

        // 查询设备类型的文件列表
        // 4. 核心优化：同步设备类型的文件列表 (一行代码)
        appBusinessFileService.cloneFiles(deviceTypes.getId(), device.getId());
        //同步设备类型的视频列表
        appOperationVideoService.cloneFiles(deviceTypes.getId(), device.getId());

        // 新增设备类型记录
        DeviceAssignments assignments = new DeviceAssignments();
        assignments.setDeviceId(device.getId());
        assignments.setAssignedAt(LocalDateTime.now());
        // 分配者
        assignments.setAssignerId(loginUser.getUserId());
        assignments.setAssignerName(loginUser.getUsername());
        // 接收者
        assignments.setAssigneeId(loginUser.getUserId());
        assignments.setActive(DeviceActiveStatusEnum.ACTIVE.getCode());
        String lever = USER_ID_SEPARATOR + loginUser.getUserId();
        assignments.setLever(lever);
        deviceAssignmentsService.save(assignments);
    }

    private String createDeviceNo() {
        String uuidStr = UUID.fastUUID().toString(); // 获取带 - 的标准格式字符串
        return uuidStr.replaceAll("-", "");
    }

    public Map<String, Object> GetDeviceByName(DeviceForm deviceForm){
        List<Map<String, Object>> list= deviceMapper.GetDeviceByName(deviceForm);
        Map<String, Object> device=null;
        if(list!=null && list.size()>0){
            device=list.get(0);
        }
        return device;
    }

    public int getEquipCountByType(DeviceForm form){
        var res=deviceMapper.getEquipCountByType(form);
        return res;
    }

    public List<Map<String,Object>> getEquipAllByType(DeviceForm deviceForm){
        List<Map<String, Object>> list= deviceMapper.getEquipAllByType(deviceForm);
        return list;
    }

}
