package cn.iocoder.yudao.module.iot.service.device;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.*;
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
import cn.iocoder.yudao.module.iot.core.biz.dto.IotSubDeviceRegisterFullReqDTO;
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
import cn.iocoder.yudao.module.iot.core.enums.device.IotDeviceStateEnum;
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity;
import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO;
import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterRespDTO;
import cn.iocoder.yudao.module.iot.core.topic.auth.IotSubDeviceRegisterReqDTO;
import cn.iocoder.yudao.module.iot.core.topic.auth.IotSubDeviceRegisterRespDTO;
import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoAddReqDTO;
import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoChangeReqDTO;
import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoDeleteReqDTO;
import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoGetRespDTO;
import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;
import cn.iocoder.yudao.module.iot.core.util.IotProductAuthUtils;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceGroupDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
import cn.iocoder.yudao.module.iot.dal.mysql.device.IotDeviceMapper;
import cn.iocoder.yudao.module.iot.dal.redis.RedisKeyConstants;
import cn.iocoder.yudao.module.iot.enums.product.IotProductDeviceTypeEnum;
import cn.iocoder.yudao.module.iot.service.device.message.IotDeviceMessageService;
import cn.iocoder.yudao.module.iot.service.product.IotProductService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Nullable;
import javax.annotation.Resource;
import javax.validation.ConstraintViolationException;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*;
import static java.util.Collections.singletonList;

/**
 * IoT 设备 Service 实现类
 *
 * @author 芋道源码
 */
@Service
@Validated
@Slf4j
public class IotDeviceServiceImpl implements IotDeviceService {

    @Resource
    private IotDeviceMapper deviceMapper;

    @Resource
    @Lazy  // 延迟加载，解决循环依赖
    private IotProductService productService;
    @Resource
    @Lazy // 延迟加载，解决循环依赖
    private IotDeviceGroupService deviceGroupService;
    @Resource
    @Lazy // 延迟加载，解决循环依赖
    private IotDeviceMessageService deviceMessageService;

    private IotDeviceServiceImpl getSelf() {
        return SpringUtil.getBean(getClass());
    }

    @Override
    public Long createDevice(IotDeviceSaveReqVO createReqVO) {
        return createDevice0(createReqVO).getId();
    }

    private IotDeviceDO createDevice0(IotDeviceSaveReqVO createReqVO) {
        // 1.1 校验产品是否存在
        IotProductDO product = productService.getProduct(createReqVO.getProductId());
        if (product == null) {
            throw exception(PRODUCT_NOT_EXISTS);
        }
        // 1.2 统一校验
        validateCreateDeviceParam(product.getProductKey(), createReqVO.getDeviceName(),
                createReqVO.getGatewayId(), product);
        // 1.3 校验分组存在
        deviceGroupService.validateDeviceGroupExists(createReqVO.getGroupIds());
        // 1.4 校验设备序列号全局唯一
        validateSerialNumberUnique(createReqVO.getSerialNumber(), null);

        // 2. 插入到数据库
        IotDeviceDO device = BeanUtils.toBean(createReqVO, IotDeviceDO.class);
        initDevice(device, product);
        deviceMapper.insert(device);
        return device;
    }

    private void validateCreateDeviceParam(String productKey, String deviceName,
                                           Long gatewayId, IotProductDO product) {
        // 校验设备名称在同一产品下是否唯一
        TenantUtils.executeIgnore(() -> {
            if (deviceMapper.selectByProductKeyAndDeviceName(productKey, deviceName) != null) {
                throw exception(DEVICE_NAME_EXISTS);
            }
        });
        // 校验父设备是否为合法网关
        if (IotProductDeviceTypeEnum.isGatewaySub(product.getDeviceType())
                && gatewayId != null) {
            validateGatewayDeviceExists(gatewayId);
        }
    }

    /**
     * 校验设备序列号全局唯一性
     *
     * @param serialNumber 设备序列号
     * @param excludeId 排除的设备编号（用于更新时排除自身）
     */
    private void validateSerialNumberUnique(String serialNumber, Long excludeId) {
        if (StrUtil.isBlank(serialNumber)) {
            return;
        }
        IotDeviceDO existDevice = deviceMapper.selectBySerialNumber(serialNumber);
        if (existDevice != null && ObjUtil.notEqual(existDevice.getId(), excludeId)) {
            throw exception(DEVICE_SERIAL_NUMBER_EXISTS);
        }
    }

    private void initDevice(IotDeviceDO device, IotProductDO product) {
        device.setProductId(product.getId()).setProductKey(product.getProductKey())
                .setDeviceType(product.getDeviceType())
                .setDeviceSecret(generateDeviceSecret()) // 生成密钥
                .setState(IotDeviceStateEnum.INACTIVE.getState()); // 默认未激活
    }

    private String generateDeviceSecret() {
        return IdUtil.fastSimpleUUID();
    }

    @Override
    public void updateDevice(IotDeviceSaveReqVO updateReqVO) {
        updateReqVO.setDeviceName(null).setProductId(null); // 不允许更新
        // 1.1 校验存在
        IotDeviceDO device = validateDeviceExists(updateReqVO.getId());
        // 1.2 校验父设备是否为合法网关
        if (IotProductDeviceTypeEnum.isGatewaySub(device.getDeviceType())
                && updateReqVO.getGatewayId() != null) {
            validateGatewayDeviceExists(updateReqVO.getGatewayId());
        }
        // 1.3 校验分组存在
        deviceGroupService.validateDeviceGroupExists(updateReqVO.getGroupIds());
        // 1.4 校验设备序列号全局唯一
        validateSerialNumberUnique(updateReqVO.getSerialNumber(), updateReqVO.getId());

        // 2. 更新到数据库
        IotDeviceDO updateObj = BeanUtils.toBean(updateReqVO, IotDeviceDO.class);
        deviceMapper.updateById(updateObj);

        // 3. 清空对应缓存
        deleteDeviceCache(device);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateDeviceGroup(IotDeviceUpdateGroupReqVO updateReqVO) {
        // 1.1 校验设备存在
        List<IotDeviceDO> devices = deviceMapper.selectByIds(updateReqVO.getIds());
        if (CollUtil.isEmpty(devices)) {
            return;
        }
        // 1.2 校验分组存在
        deviceGroupService.validateDeviceGroupExists(updateReqVO.getGroupIds());

        // 3. 更新设备分组
        deviceMapper.updateBatch(convertList(devices, device -> new IotDeviceDO()
                .setId(device.getId()).setGroupIds(updateReqVO.getGroupIds())));

        // 4. 清空对应缓存
        deleteDeviceCache(devices);
    }

    @Override
    public void deleteDevice(Long id) {
        // 1.1 校验存在
        IotDeviceDO device = validateDeviceExists(id);
        // 1.2 如果是网关设备，检查是否有子设备绑定
        if (IotProductDeviceTypeEnum.isGateway(device.getDeviceType())
                && deviceMapper.selectCountByGatewayId(id) > 0) {
            throw exception(DEVICE_GATEWAY_HAS_SUB);
        }

        // 2. 删除设备
        deviceMapper.deleteById(id);

        // 3. 清空对应缓存
        deleteDeviceCache(device);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteDeviceList(Collection<Long> ids) {
        // 1.1 校验存在
        if (CollUtil.isEmpty(ids)) {
            return;
        }
        List<IotDeviceDO> devices = deviceMapper.selectByIds(ids);
        if (CollUtil.isEmpty(devices)) {
            return;
        }
        // 1.2 如果是网关设备，检查是否有子设备绑定
        for (IotDeviceDO device : devices) {
            if (IotProductDeviceTypeEnum.isGateway(device.getDeviceType())
                    && deviceMapper.selectCountByGatewayId(device.getId()) > 0) {
                throw exception(DEVICE_GATEWAY_HAS_SUB);
            }
        }

        // 2. 删除设备
        deviceMapper.deleteByIds(ids);

        // 3. 清空对应缓存
        deleteDeviceCache(devices);
    }

    @Override
    public IotDeviceDO validateDeviceExists(Long id) {
        IotDeviceDO device = deviceMapper.selectById(id);
        if (device == null) {
            throw exception(DEVICE_NOT_EXISTS);
        }
        return device;
    }

    @Override
    public IotDeviceDO validateDeviceExistsFromCache(Long id) {
        IotDeviceDO device = getSelf().getDeviceFromCache(id);
        if (device == null) {
            throw exception(DEVICE_NOT_EXISTS);
        }
        return device;
    }

    /**
     * 校验网关设备是否存在
     *
     * @param id 设备 ID
     */
    private void validateGatewayDeviceExists(Long id) {
        IotDeviceDO device = deviceMapper.selectById(id);
        if (device == null) {
            throw exception(DEVICE_GATEWAY_NOT_EXISTS);
        }
        if (!IotProductDeviceTypeEnum.isGateway(device.getDeviceType())) {
            throw exception(DEVICE_NOT_GATEWAY);
        }
    }

    @Override
    public IotDeviceDO getDevice(Long id) {
        return deviceMapper.selectById(id);
    }

    @Override
    @Cacheable(value = RedisKeyConstants.DEVICE, key = "#id", unless = "#result == null")
    @TenantIgnore // 忽略租户信息
    public IotDeviceDO getDeviceFromCache(Long id) {
        return deviceMapper.selectById(id);
    }

    @Override
    @Cacheable(value = RedisKeyConstants.DEVICE, key = "#productKey + '_' + #deviceName", unless = "#result == null")
    @TenantIgnore // 忽略租户信息，跨租户 productKey + deviceName 是唯一的
    public IotDeviceDO getDeviceFromCache(String productKey, String deviceName) {
        return deviceMapper.selectByProductKeyAndDeviceName(productKey, deviceName);
    }

    @Override
    public PageResult<IotDeviceDO> getDevicePage(IotDevicePageReqVO pageReqVO) {
        return deviceMapper.selectPage(pageReqVO);
    }

    @Override
    public List<IotDeviceDO> getDeviceListByCondition(@Nullable Integer deviceType, @Nullable Long productId) {
        return deviceMapper.selectListByCondition(deviceType, productId);
    }

    @Override
    public List<IotDeviceDO> getDeviceListByState(Integer state) {
        return deviceMapper.selectListByState(state);
    }

    @Override
    public List<IotDeviceDO> getDeviceListByProductId(Long productId) {
        return deviceMapper.selectListByProductId(productId);
    }

    @Override
    public void updateDeviceState(IotDeviceDO device, Integer state) {
        // 1. 更新状态和时间
        IotDeviceDO updateObj = new IotDeviceDO().setId(device.getId()).setState(state);
        if (device.getOnlineTime() == null
                && Objects.equals(state, IotDeviceStateEnum.ONLINE.getState())) {
            updateObj.setActiveTime(LocalDateTime.now());
        }
        if (Objects.equals(state, IotDeviceStateEnum.ONLINE.getState())) {
            updateObj.setOnlineTime(LocalDateTime.now());
        } else if (Objects.equals(state, IotDeviceStateEnum.OFFLINE.getState())) {
            updateObj.setOfflineTime(LocalDateTime.now());
        }
        deviceMapper.updateById(updateObj);

        // 2. 清空对应缓存
        deleteDeviceCache(device);

        // 3. 网关设备下线时，联动所有子设备下线
        if (Objects.equals(state, IotDeviceStateEnum.OFFLINE.getState())
                && IotProductDeviceTypeEnum.isGateway(device.getDeviceType())) {
            handleGatewayOffline(device);
        }
    }

    /**
     * 处理网关下线，联动所有子设备下线
     *
     * @param gatewayDevice 网关设备
     */
    private void handleGatewayOffline(IotDeviceDO gatewayDevice) {
        List<IotDeviceDO> subDevices = deviceMapper.selectListByGatewayId(gatewayDevice.getId());
        if (CollUtil.isEmpty(subDevices)) {
            return;
        }
        for (IotDeviceDO subDevice : subDevices) {
            if (Objects.equals(subDevice.getState(), IotDeviceStateEnum.ONLINE.getState())) {
                try {
                    updateDeviceState(subDevice, IotDeviceStateEnum.OFFLINE.getState());
                    log.info("[handleGatewayOffline][网关({}/{}) 下线，子设备({}/{}) 联动下线]",
                            gatewayDevice.getProductKey(), gatewayDevice.getDeviceName(),
                            subDevice.getProductKey(), subDevice.getDeviceName());
                } catch (Exception ex) {
                    log.error("[handleGatewayOffline][子设备({}/{}) 下线失败]",
                            subDevice.getProductKey(), subDevice.getDeviceName(), ex);
                }
            }
        }
    }

    @Override
    public void updateDeviceState(Long id, Integer state) {
        // 校验存在
        IotDeviceDO device = validateDeviceExists(id);
        // 执行更新
        updateDeviceState(device, state);
    }

    @Override
    public Long getDeviceCountByProductId(Long productId) {
        return deviceMapper.selectCountByProductId(productId);
    }

    @Override
    public Long getDeviceCountByGroupId(Long groupId) {
        return deviceMapper.selectCountByGroupId(groupId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class) // 添加事务，异常则回滚所有导入
    public IotDeviceImportRespVO importDevice(List<IotDeviceImportExcelVO> importDevices, boolean updateSupport) {
        // 1. 参数校验
        if (CollUtil.isEmpty(importDevices)) {
            throw exception(DEVICE_IMPORT_LIST_IS_EMPTY);
        }

        // 2. 遍历，逐个创建 or 更新
        IotDeviceImportRespVO respVO = IotDeviceImportRespVO.builder().createDeviceNames(new ArrayList<>())
                .updateDeviceNames(new ArrayList<>()).failureDeviceNames(new LinkedHashMap<>()).build();
        importDevices.forEach(importDevice -> {
            try {
                // 2.1.1 校验字段是否符合要求
                try {
                    ValidationUtils.validate(importDevice);
                } catch (ConstraintViolationException ex) {
                    respVO.getFailureDeviceNames().put(importDevice.getDeviceName(), ex.getMessage());
                    return;
                }
                // 2.1.2 校验产品是否存在
                IotProductDO product = productService.validateProductExists(importDevice.getProductKey());
                // 2.1.3 校验父设备是否存在
                Long gatewayId = null;
                if (StrUtil.isNotEmpty(importDevice.getParentDeviceName())) {
                    IotDeviceDO gatewayDevice = deviceMapper.selectByDeviceName(importDevice.getParentDeviceName());
                    if (gatewayDevice == null) {
                        throw exception(DEVICE_GATEWAY_NOT_EXISTS);
                    }
                    if (!IotProductDeviceTypeEnum.isGateway(gatewayDevice.getDeviceType())) {
                        throw exception(DEVICE_NOT_GATEWAY);
                    }
                    gatewayId = gatewayDevice.getId();
                }
                // 2.1.4 校验设备分组是否存在
                Set<Long> groupIds = new HashSet<>();
                if (StrUtil.isNotEmpty(importDevice.getGroupNames())) {
                    String[] groupNames = importDevice.getGroupNames().split(",");
                    for (String groupName : groupNames) {
                        IotDeviceGroupDO group = deviceGroupService.getDeviceGroupByName(groupName);
                        if (group == null) {
                            throw exception(DEVICE_GROUP_NOT_EXISTS);
                        }
                        groupIds.add(group.getId());
                    }
                }

                // 2.2.1 判断如果不存在，在进行插入
                IotDeviceDO existDevice = deviceMapper.selectByDeviceName(importDevice.getDeviceName());
                if (existDevice == null) {
                    createDevice(new IotDeviceSaveReqVO()
                            .setDeviceName(importDevice.getDeviceName())
                            .setProductId(product.getId()).setGatewayId(gatewayId).setGroupIds(groupIds));
                    respVO.getCreateDeviceNames().add(importDevice.getDeviceName());
                    return;
                }
                // 2.2.2 如果存在，判断是否允许更新
                if (!updateSupport) {
                    throw exception(DEVICE_KEY_EXISTS);
                }
                updateDevice(new IotDeviceSaveReqVO().setId(existDevice.getId())
                        .setGatewayId(gatewayId).setGroupIds(groupIds));
                respVO.getUpdateDeviceNames().add(importDevice.getDeviceName());
            } catch (ServiceException ex) {
                respVO.getFailureDeviceNames().put(importDevice.getDeviceName(), ex.getMessage());
            }
        });
        return respVO;
    }

    @Override
    public IotDeviceAuthInfoRespVO getDeviceAuthInfo(Long id) {
        IotDeviceDO device = validateDeviceExists(id);
        // 使用 IotDeviceAuthUtils 生成认证信息
        IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(
                device.getProductKey(), device.getDeviceName(), device.getDeviceSecret());
        return BeanUtils.toBean(authInfo, IotDeviceAuthInfoRespVO.class);
    }

    private void deleteDeviceCache(IotDeviceDO device) {
        // 保证 Spring AOP 触发
        getSelf().deleteDeviceCache0(device);
    }

    private void deleteDeviceCache(List<IotDeviceDO> devices) {
        devices.forEach(this::deleteDeviceCache);
    }

    @SuppressWarnings("unused")
    @Caching(evict = {
        @CacheEvict(value = RedisKeyConstants.DEVICE, key = "#device.id"),
        @CacheEvict(value = RedisKeyConstants.DEVICE, key = "#device.productKey + '_' + #device.deviceName")
    })
    public void deleteDeviceCache0(IotDeviceDO device) {
    }

    @Override
    public Long getDeviceCount(LocalDateTime createTime) {
        return deviceMapper.selectCountByCreateTime(createTime);
    }

    @Override
    public Map<Long, Integer> getDeviceCountMapByProductId() {
        return deviceMapper.selectDeviceCountMapByProductId();
    }

    @Override
    public Map<Integer, Long> getDeviceCountMapByState() {
        return deviceMapper.selectDeviceCountGroupByState();
    }

    @Override
    public List<IotDeviceDO> getDeviceListByProductKeyAndNames(String productKey, List<String> deviceNames) {
        if (StrUtil.isBlank(productKey) || CollUtil.isEmpty(deviceNames)) {
            return Collections.emptyList();
        }
        return deviceMapper.selectByProductKeyAndDeviceNames(productKey, deviceNames);
    }

    @Override
    public boolean authDevice(IotDeviceAuthReqDTO authReqDTO) {
        // 1. 校验设备是否存在
        IotDeviceIdentity deviceInfo = IotDeviceAuthUtils.parseUsername(authReqDTO.getUsername());
        if (deviceInfo == null) {
            log.error("[authDevice][认证失败，username({}) 格式不正确]", authReqDTO.getUsername());
            return false;
        }
        String deviceName = deviceInfo.getDeviceName();
        String productKey = deviceInfo.getProductKey();
        IotDeviceDO device = getSelf().getDeviceFromCache(productKey, deviceName);
        if (device == null) {
            log.warn("[authDevice][设备({}/{}) 不存在]", productKey, deviceName);
            return false;
        }

        // 2. 校验密码
        IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(productKey, deviceName, device.getDeviceSecret());
        if (ObjUtil.notEqual(authInfo.getPassword(), authReqDTO.getPassword())) {
            log.error("[authDevice][设备({}/{}) 密码不正确]", productKey, deviceName);
            return false;
        }
        return true;
    }

    @Override
    public List<IotDeviceDO> validateDeviceListExists(Collection<Long> ids) {
        List<IotDeviceDO> devices = getDeviceList(ids);
        if (devices.size() != ids.size()) {
            throw exception(DEVICE_NOT_EXISTS);
        }
        return devices;
    }

    @Override
    public List<IotDeviceDO> getDeviceList(Collection<Long> ids) {
        if (CollUtil.isEmpty(ids)) {
            return Collections.emptyList();
        }
        return deviceMapper.selectByIds(ids);
    }

    @Override
    public void updateDeviceFirmware(Long deviceId, Long firmwareId) {
        // 1. 校验设备是否存在
        IotDeviceDO device = validateDeviceExists(deviceId);

        // 2. 更新设备固件版本
        IotDeviceDO updateObj = new IotDeviceDO().setId(deviceId).setFirmwareId(firmwareId);
        deviceMapper.updateById(updateObj);

        // 3. 清空对应缓存
        deleteDeviceCache(device);
    }

    @Override
    public void updateDeviceLocation(IotDeviceDO device, BigDecimal longitude, BigDecimal latitude) {
        // 1. 更新定位信息
        deviceMapper.updateById(new IotDeviceDO().setId(device.getId())
                .setLongitude(longitude).setLatitude(latitude));

        // 2. 清空对应缓存
        deleteDeviceCache(device);
    }

    @Override
    public List<IotDeviceDO> getDeviceListByHasLocation() {
        return deviceMapper.selectListByHasLocation();
    }

    // ========== 网关-拓扑管理（后台操作） ==========

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void bindDeviceGateway(Collection<Long> subIds, Long gatewayId) {
        if (CollUtil.isEmpty(subIds)) {
            return;
        }
        // 1.1 校验网关设备存在且类型正确
        validateGatewayDeviceExists(gatewayId);
        // 1.2 校验每个设备是否可绑定
        List<IotDeviceDO> devices = deviceMapper.selectByIds(subIds);
        for (IotDeviceDO device : devices) {
            checkSubDeviceCanBind(device, gatewayId);
        }

        // 2. 批量更新数据库
        List<IotDeviceDO> updateList = convertList(devices, device ->
                new IotDeviceDO().setId(device.getId()).setGatewayId(gatewayId));
        deviceMapper.updateBatch(updateList);

        // 3. 清空对应缓存
        deleteDeviceCache(devices);

        // 4. 下发网关设备拓扑变更通知（增加）
        sendTopoChangeNotify(gatewayId, IotDeviceTopoChangeReqDTO.STATUS_CREATE, devices);
    }

    private void checkSubDeviceCanBind(IotDeviceDO device, Long gatewayId) {
        if (!IotProductDeviceTypeEnum.isGatewaySub(device.getDeviceType())) {
            throw exception(DEVICE_NOT_GATEWAY_SUB, device.getProductKey(), device.getDeviceName());
        }
        // 已绑定到其他网关，拒绝绑定（需先解绑）
        if (device.getGatewayId() != null && ObjUtil.notEqual(device.getGatewayId(), gatewayId)) {
            throw exception(DEVICE_GATEWAY_BINDTO_EXISTS, device.getProductKey(), device.getDeviceName());
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void unbindDeviceGateway(Collection<Long> subIds, Long gatewayId) {
        // 1. 校验设备存在
        if (CollUtil.isEmpty(subIds)) {
            return;
        }
        List<IotDeviceDO> devices = deviceMapper.selectByIds(subIds);
        devices.removeIf(device -> ObjUtil.notEqual(device.getGatewayId(), gatewayId));
        if (CollUtil.isEmpty(devices)) {
            return;
        }

        // 2. 批量更新数据库（将 gatewayId 设置为 null）
        deviceMapper.updateGatewayIdBatch(convertList(devices, IotDeviceDO::getId), null);

        // 3. 清空对应缓存
        deleteDeviceCache(devices);

        // 4. 下发网关设备拓扑变更通知（删除）
        sendTopoChangeNotify(gatewayId, IotDeviceTopoChangeReqDTO.STATUS_DELETE, devices);
    }

    @Override
    public PageResult<IotDeviceDO> getUnboundSubDevicePage(IotDevicePageReqVO pageReqVO) {
        return deviceMapper.selectUnboundSubDevicePage(pageReqVO);
    }

    @Override
    public List<IotDeviceDO> getDeviceListByGatewayId(Long gatewayId) {
        return deviceMapper.selectListByGatewayId(gatewayId);
    }

    // ========== 网关-拓扑管理（设备上报） ==========

    @Override
    public List<IotDeviceIdentity> handleTopoAddMessage(IotDeviceMessage message, IotDeviceDO gatewayDevice) {
        // 1.1 校验网关设备类型
        if (!IotProductDeviceTypeEnum.isGateway(gatewayDevice.getDeviceType())) {
            throw exception(DEVICE_NOT_GATEWAY);
        }
        // 1.2 解析参数
        IotDeviceTopoAddReqDTO params = JsonUtils.convertObject(message.getParams(), IotDeviceTopoAddReqDTO.class);
        if (params == null || CollUtil.isEmpty(params.getSubDevices())) {
            throw exception(DEVICE_TOPO_PARAMS_INVALID);
        }

        // 2. 遍历处理每个子设备
        List<IotDeviceIdentity> addedSubDevices = new ArrayList<>();
        for (IotDeviceAuthReqDTO subDeviceAuth : params.getSubDevices()) {
            try {
                IotDeviceDO subDevice = addDeviceTopo(gatewayDevice, subDeviceAuth);
                addedSubDevices.add(new IotDeviceIdentity(subDevice.getProductKey(), subDevice.getDeviceName()));
            } catch (Exception ex) {
                log.warn("[handleTopoAddMessage][网关({}/{}) 添加子设备失败，message={}]",
                        gatewayDevice.getProductKey(), gatewayDevice.getDeviceName(), message, ex);
            }
        }

        // 3. 返回响应数据（包含成功添加的子设备列表）
        return addedSubDevices;
    }

    private IotDeviceDO addDeviceTopo(IotDeviceDO gatewayDevice, IotDeviceAuthReqDTO subDeviceAuth) {
        // 1.1 解析子设备信息
        IotDeviceIdentity subDeviceInfo = IotDeviceAuthUtils.parseUsername(subDeviceAuth.getUsername());
        if (subDeviceInfo == null) {
            throw exception(DEVICE_TOPO_SUB_DEVICE_USERNAME_INVALID);
        }
        // 1.2 校验子设备认证信息
        if (!authDevice(subDeviceAuth)) {
            throw exception(DEVICE_TOPO_SUB_DEVICE_AUTH_FAILED);
        }
        // 1.3 获取子设备
        IotDeviceDO subDevice = getSelf().getDeviceFromCache(subDeviceInfo.getProductKey(), subDeviceInfo.getDeviceName());
        if (subDevice == null) {
            throw exception(DEVICE_NOT_EXISTS);
        }
        // 1.4 校验子设备类型
        checkSubDeviceCanBind(subDevice, gatewayDevice.getId());

        // 2. 更新数据库
        deviceMapper.updateById(new IotDeviceDO().setId(subDevice.getId()).setGatewayId(gatewayDevice.getId()));
        log.info("[addDeviceTopo][网关({}/{}) 绑定子设备({}/{})]",
                gatewayDevice.getProductKey(), gatewayDevice.getDeviceName(),
                subDevice.getProductKey(), subDevice.getDeviceName());

        // 3. 清空对应缓存
        deleteDeviceCache(subDevice);
        return subDevice;
    }

    @Override
    public List<IotDeviceIdentity> handleTopoDeleteMessage(IotDeviceMessage message, IotDeviceDO gatewayDevice) {
        // 1.1 校验网关设备类型
        if (!IotProductDeviceTypeEnum.isGateway(gatewayDevice.getDeviceType())) {
            throw exception(DEVICE_NOT_GATEWAY);
        }
        // 1.2 解析参数
        IotDeviceTopoDeleteReqDTO params = JsonUtils.convertObject(message.getParams(), IotDeviceTopoDeleteReqDTO.class);
        if (params == null || CollUtil.isEmpty(params.getSubDevices())) {
            throw exception(DEVICE_TOPO_PARAMS_INVALID);
        }

        // 2. 遍历处理每个子设备
        List<IotDeviceIdentity> deletedSubDevices = new ArrayList<>();
        for (IotDeviceIdentity subDeviceIdentity : params.getSubDevices()) {
            try {
                deleteDeviceTopo(gatewayDevice, subDeviceIdentity);
                deletedSubDevices.add(subDeviceIdentity);
            } catch (Exception ex) {
                log.warn("[handleTopoDeleteMessage][网关({}/{}) 删除子设备失败，productKey={}, deviceName={}]",
                        gatewayDevice.getProductKey(), gatewayDevice.getDeviceName(),
                        subDeviceIdentity.getProductKey(), subDeviceIdentity.getDeviceName(), ex);
            }
        }

        // 3. 返回响应数据（包含成功删除的子设备列表）
        return deletedSubDevices;
    }

    private void deleteDeviceTopo(IotDeviceDO gatewayDevice, IotDeviceIdentity subDeviceIdentity) {
        // 1.1 获取子设备
        IotDeviceDO subDevice = getSelf().getDeviceFromCache(subDeviceIdentity.getProductKey(), subDeviceIdentity.getDeviceName());
        if (subDevice == null) {
            throw exception(DEVICE_NOT_EXISTS);
        }
        // 1.2 校验子设备是否绑定到该网关
        if (ObjUtil.notEqual(subDevice.getGatewayId(), gatewayDevice.getId())) {
            throw exception(DEVICE_TOPO_SUB_NOT_BINDTO_GATEWAY,
                    subDeviceIdentity.getProductKey(), subDeviceIdentity.getDeviceName());
        }

        // 2. 更新数据库（将 gatewayId 设置为 null）
        deviceMapper.updateGatewayIdBatch(singletonList(subDevice.getId()), null);
        log.info("[deleteDeviceTopo][网关({}/{}) 解绑子设备({}/{})]",
                gatewayDevice.getProductKey(), gatewayDevice.getDeviceName(),
                subDevice.getProductKey(), subDevice.getDeviceName());

        // 3. 清空对应缓存
        deleteDeviceCache(subDevice);

        // 4. 子设备下线
        if (Objects.equals(subDevice.getState(), IotDeviceStateEnum.ONLINE.getState())) {
            updateDeviceState(subDevice, IotDeviceStateEnum.OFFLINE.getState());
        }
    }

    @Override
    public IotDeviceTopoGetRespDTO handleTopoGetMessage(IotDeviceDO gatewayDevice) {
        // 1. 校验网关设备类型
        if (!IotProductDeviceTypeEnum.isGateway(gatewayDevice.getDeviceType())) {
            throw exception(DEVICE_NOT_GATEWAY);
        }

        // 2. 获取子设备列表并转换
        List<IotDeviceDO> subDevices = deviceMapper.selectListByGatewayId(gatewayDevice.getId());
        List<IotDeviceIdentity> subDeviceIdentities = convertList(subDevices, subDevice ->
                new IotDeviceIdentity(subDevice.getProductKey(), subDevice.getDeviceName()));
        return new IotDeviceTopoGetRespDTO().setSubDevices(subDeviceIdentities);
    }

    /**
     * 发送拓扑变更通知给网关设备
     *
     * @param gatewayId  网关设备编号
     * @param status     变更状态（0-创建, 1-删除）
     * @param subDevices 子设备列表
     * @see <a href="https://help.aliyun.com/zh/marketplace/notify-gateway-topology-changes">阿里云 - 通知网关拓扑关系变化</a>
     */
    private void sendTopoChangeNotify(Long gatewayId, Integer status, List<IotDeviceDO> subDevices) {
        if (CollUtil.isEmpty(subDevices)) {
            return;
        }
        // 1. 获取网关设备
        IotDeviceDO gatewayDevice = deviceMapper.selectById(gatewayId);
        if (gatewayDevice == null) {
            log.warn("[sendTopoChangeNotify][网关设备({}) 不存在，无法发送拓扑变更通知]", gatewayId);
            return;
        }

        try {
            // 2.1 构建拓扑变更通知消息
            List<IotDeviceIdentity> subList = convertList(subDevices, subDevice ->
                    new IotDeviceIdentity(subDevice.getProductKey(), subDevice.getDeviceName()));
            IotDeviceTopoChangeReqDTO params = new IotDeviceTopoChangeReqDTO(status, subList);
            IotDeviceMessage notifyMessage = IotDeviceMessage.requestOf(
                    IotDeviceMessageMethodEnum.TOPO_CHANGE.getMethod(), params);

            // 2.2 发送消息
            deviceMessageService.sendDeviceMessage(notifyMessage, gatewayDevice);
            log.info("[sendTopoChangeNotify][网关({}/{}) 发送拓扑变更通知成功，status={}, subDevices={}]",
                    gatewayDevice.getProductKey(), gatewayDevice.getDeviceName(),
                    status, subList);
        } catch (Exception ex) {
            log.error("[sendTopoChangeNotify][网关({}/{}) 发送拓扑变更通知失败，status={}]",
                    gatewayDevice.getProductKey(), gatewayDevice.getDeviceName(), status, ex);
        }
    }

    // ========== 设备动态注册 ==========

    @Override
    public IotDeviceRegisterRespDTO registerDevice(IotDeviceRegisterReqDTO reqDTO) {
        // 1.1 校验产品
        IotProductDO product = TenantUtils.executeIgnore(() ->
                productService.getProductByProductKey(reqDTO.getProductKey()));
        if (product == null) {
            throw exception(PRODUCT_NOT_EXISTS);
        }
        // 1.2 校验产品是否开启动态注册
        if (BooleanUtil.isFalse(product.getRegisterEnabled())) {
            throw exception(DEVICE_REGISTER_DISABLED);
        }
        // 1.3 【重要！！！】验证签名
        if (!IotProductAuthUtils.verifySign(reqDTO.getProductKey(), reqDTO.getDeviceName(),
                product.getProductSecret(), reqDTO.getSign())) {
            throw exception(DEVICE_REGISTER_SECRET_INVALID);
        }
        return TenantUtils.execute(product.getTenantId(), () -> {
            // 1.4 校验设备是否已存在（已存在则不允许重复注册）
            IotDeviceDO device = getSelf().getDeviceFromCache(reqDTO.getProductKey(), reqDTO.getDeviceName());
            if (device != null) {
                throw exception(DEVICE_REGISTER_ALREADY_EXISTS);
            }

            // 2.1 自动创建设备
            IotDeviceSaveReqVO createReqVO = new IotDeviceSaveReqVO()
                    .setDeviceName(reqDTO.getDeviceName())
                    .setProductId(product.getId());
            device = createDevice0(createReqVO);
            log.info("[registerDevice][产品({}) 自动创建设备({})]",
                    reqDTO.getProductKey(), reqDTO.getDeviceName());
            // 2.2 返回设备密钥
            return new IotDeviceRegisterRespDTO(device.getProductKey(), device.getDeviceName(), device.getDeviceSecret());
        });
    }

    @Override
    public List<IotSubDeviceRegisterRespDTO> registerSubDevices(IotSubDeviceRegisterFullReqDTO reqDTO) {
        // 1. 校验网关设备
        IotDeviceDO gatewayDevice = getSelf().getDeviceFromCache(reqDTO.getGatewayProductKey(), reqDTO.getGatewayDeviceName());

        // 2. 遍历注册每个子设备
        return TenantUtils.execute(gatewayDevice.getTenantId(), () ->
                registerSubDevices0(gatewayDevice, reqDTO.getSubDevices()));
    }

    @Override
    public List<IotSubDeviceRegisterRespDTO> handleSubDeviceRegisterMessage(IotDeviceMessage message, IotDeviceDO gatewayDevice) {
        // 1. 解析参数
        if (!(message.getParams() instanceof List)) {
            throw exception(DEVICE_SUB_REGISTER_PARAMS_INVALID);
        }
        List<IotSubDeviceRegisterReqDTO> subDevices = JsonUtils.convertList(message.getParams(), IotSubDeviceRegisterReqDTO.class);

        // 2. 遍历注册每个子设备
        return registerSubDevices0(gatewayDevice, subDevices);
    }

    private List<IotSubDeviceRegisterRespDTO> registerSubDevices0(IotDeviceDO gatewayDevice,
                                                                   List<IotSubDeviceRegisterReqDTO> subDevices) {
        // 1.1 校验网关设备
        if (gatewayDevice == null) {
            throw exception(DEVICE_NOT_EXISTS);
        }
        if (!IotProductDeviceTypeEnum.isGateway(gatewayDevice.getDeviceType())) {
            throw exception(DEVICE_NOT_GATEWAY);
        }
        // 1.2 注册设备不能为空
        if (CollUtil.isEmpty(subDevices)) {
            throw exception(DEVICE_SUB_REGISTER_PARAMS_INVALID);
        }

        // 2. 遍历注册每个子设备
        List<IotSubDeviceRegisterRespDTO> results = new ArrayList<>(subDevices.size());
        for (IotSubDeviceRegisterReqDTO subDevice : subDevices) {
            try {
                IotDeviceDO device = registerSubDevice0(gatewayDevice, subDevice);
                results.add(new IotSubDeviceRegisterRespDTO(
                        subDevice.getProductKey(), subDevice.getDeviceName(), device.getDeviceSecret()));
            } catch (Exception ex) {
                log.error("[registerSubDevices0][子设备({}/{}) 注册失败]",
                        subDevice.getProductKey(), subDevice.getDeviceName(), ex);
            }
        }
        return results;
    }

    private IotDeviceDO registerSubDevice0(IotDeviceDO gatewayDevice, IotSubDeviceRegisterReqDTO params) {
        // 1.1 校验产品
        IotProductDO product = productService.getProductByProductKey(params.getProductKey());
        if (product == null) {
            throw exception(PRODUCT_NOT_EXISTS);
        }
        // 1.2 校验产品是否为网关子设备类型
        if (!IotProductDeviceTypeEnum.isGatewaySub(product.getDeviceType())) {
            throw exception(DEVICE_SUB_REGISTER_PRODUCT_NOT_GATEWAY_SUB, params.getProductKey());
        }
        // 1.3 校验设备是否已存在（子设备动态注册：设备必须已预注册）
        IotDeviceDO existDevice = getSelf().getDeviceFromCache(params.getProductKey(), params.getDeviceName());
        if (existDevice == null) {
            throw exception(DEVICE_NOT_EXISTS);
        }
        // 1.4 校验是否绑定到其他网关
        if (existDevice.getGatewayId() != null && ObjUtil.notEqual(existDevice.getGatewayId(), gatewayDevice.getId())) {
            throw exception(DEVICE_GATEWAY_BINDTO_EXISTS,
                    existDevice.getProductKey(), existDevice.getDeviceName());
        }

        // 2. 绑定到网关（如果尚未绑定）
        if (existDevice.getGatewayId() == null) {
            // 2.1 更新数据库
            deviceMapper.updateById(new IotDeviceDO().setId(existDevice.getId()).setGatewayId(gatewayDevice.getId()));
            // 2.2 清空对应缓存
            deleteDeviceCache(existDevice);
            log.info("[registerSubDevice][网关({}/{}) 绑定子设备({}/{})]",
                    gatewayDevice.getProductKey(), gatewayDevice.getDeviceName(),
                    existDevice.getProductKey(), existDevice.getDeviceName());
        }
        return existDevice;
    }

}
