package com.dji.sample.manage.service.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.json.JSONUtil;
import com.dji.sample.component.redis.RedisConst;
import com.dji.sample.component.redis.RedisOpsUtils;
import com.dji.sample.component.websocket.model.BizCodeEnum;
import com.dji.sample.component.websocket.service.IWebSocketMessageService;
import com.dji.sample.manage.model.dto.DeviceDTO;
import com.dji.sample.manage.model.dto.DevicePayloadReceiver;
import com.dji.sample.manage.model.enums.DeviceFirmwareStatusEnum;
import com.dji.sample.manage.model.param.DeviceQueryParam;
import com.dji.sample.manage.service.*;
import com.dji.sample.uom.UOMDroneFlightRecordDTO;
import com.dji.sample.uom.UOMRecordService;
import com.dji.sdk.cloudapi.device.*;
import com.dji.sdk.cloudapi.device.api.AbstractDeviceService;
import com.dji.sdk.cloudapi.tsa.DeviceIconUrl;
import com.dji.sdk.cloudapi.tsa.IconUrlEnum;
import com.dji.sdk.common.SDKManager;
import com.dji.sdk.config.version.GatewayManager;
import com.dji.sdk.mqtt.MqttReply;
import com.dji.sdk.mqtt.osd.TopicOsdRequest;
import com.dji.sdk.mqtt.state.TopicStateRequest;
import com.dji.sdk.mqtt.state.TopicStateResponse;
import com.dji.sdk.mqtt.status.TopicStatusRequest;
import com.dji.sdk.mqtt.status.TopicStatusResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.BooleanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.MessageHeaders;
import org.springframework.stereotype.Service;
import org.springframework.util.*;

import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.stream.Collectors;

/**
 * @author sean
 * @version 1.7
 * @date 2023/7/4
 */
@Service
@Slf4j
public class SDKDeviceService extends AbstractDeviceService {

    @Autowired
    private IDeviceRedisService deviceRedisService;

    @Autowired
    private IDeviceService deviceService;

    @Autowired
    private IDeviceDictionaryService dictionaryService;

    @Autowired
    private IWebSocketMessageService webSocketMessageService;

    @Autowired
    private IDevicePayloadService devicePayloadService;

    @Autowired
    private UOMRecordService uomRecordService;

    @Autowired
    private IDeviceDetailService deviceDetailService;

    private static final ScheduledExecutorService recordExecutor = Executors.newScheduledThreadPool(4);
    private static final Map<String, Long> lastRecordTimeMap = new ConcurrentHashMap<>();
    private static final long MIN_RECORD_INTERVAL = 1000; // 1秒间隔
    @Override
    public TopicStatusResponse<MqttReply> updateTopoOnline(TopicStatusRequest<UpdateTopo> request, MessageHeaders headers) {
        UpdateTopoSubDevice updateTopoSubDevice = request.getData().getSubDevices().get(0);
        String deviceSn = updateTopoSubDevice.getSn();

        Optional<DeviceDTO> deviceOpt = deviceRedisService.getDeviceOnline(deviceSn);
        Optional<DeviceDTO> gatewayOpt = deviceRedisService.getDeviceOnline(request.getFrom());
        GatewayManager gatewayManager = SDKManager.registerDevice(request.getFrom(), deviceSn,
                request.getData().getDomain(), request.getData().getType(),
                request.getData().getSubType(), request.getData().getThingVersion(), updateTopoSubDevice.getThingVersion());

        if (deviceOpt.isPresent() && gatewayOpt.isPresent()) {
            deviceOnlineAgain(deviceOpt.get().getWorkspaceId(), request.getFrom(), deviceSn);
            return new TopicStatusResponse<MqttReply>().setData(MqttReply.success());
        }

        changeSubDeviceParent(deviceSn, request.getFrom());

        DeviceDTO gateway = deviceGatewayConvertToDevice(request.getFrom(), request.getData());
        Optional<DeviceDTO> gatewayEntityOpt = onlineSaveDevice(gateway, deviceSn, null);
        if (gatewayEntityOpt.isEmpty()) {
            log.error("Failed to go online, please check the status data or code logic.");
            return null;
        }
        DeviceDTO subDevice = subDeviceConvertToDevice(updateTopoSubDevice);
        Optional<DeviceDTO> subDeviceEntityOpt = onlineSaveDevice(subDevice, null, gateway.getDeviceSn());
        if (subDeviceEntityOpt.isEmpty()) {
            log.error("Failed to go online, please check the status data or code logic.");
            return null;
        }
        subDevice = subDeviceEntityOpt.get();
        gateway = gatewayEntityOpt.get();
        dockGoOnline(gateway, subDevice);
        deviceService.gatewayOnlineSubscribeTopic(gatewayManager);

        if (!StringUtils.hasText(subDevice.getWorkspaceId())) {
            return new TopicStatusResponse<MqttReply>().setData(MqttReply.success());
        }

        // Subscribe to topic related to drone devices.
        deviceService.subDeviceOnlineSubscribeTopic(gatewayManager);
        deviceService.pushDeviceOnlineTopo(gateway.getWorkspaceId(), gateway.getDeviceSn(), subDevice.getDeviceSn());

        log.debug("{} online.", subDevice.getDeviceSn());
        return new TopicStatusResponse<MqttReply>().setData(MqttReply.success());
    }

    @Override
    public TopicStatusResponse<MqttReply> updateTopoOffline(TopicStatusRequest<UpdateTopo> request, MessageHeaders headers) {
        GatewayManager gatewayManager = SDKManager.registerDevice(request.getFrom(), null,
                request.getData().getDomain(), request.getData().getType(),
                request.getData().getSubType(), request.getData().getThingVersion(), null);
        deviceService.gatewayOnlineSubscribeTopic(gatewayManager);
        // Only the remote controller is logged in and the aircraft is not connected.
        Optional<DeviceDTO> deviceOpt = deviceRedisService.getDeviceOnline(request.getFrom());
        if (deviceOpt.isEmpty()) {
            // When connecting for the first time
            DeviceDTO gatewayDevice = deviceGatewayConvertToDevice(request.getFrom(), request.getData());
            Optional<DeviceDTO> gatewayDeviceOpt = onlineSaveDevice(gatewayDevice, null, null);
            if (gatewayDeviceOpt.isEmpty()) {
                return null;
            }
            deviceService.pushDeviceOnlineTopo(gatewayDeviceOpt.get().getWorkspaceId(), request.getFrom(), null);
            return new TopicStatusResponse<MqttReply>().setData(MqttReply.success());
        }

        String deviceSn = deviceOpt.get().getChildDeviceSn();
        if (!StringUtils.hasText(deviceSn)) {
            return new TopicStatusResponse<MqttReply>().setData(MqttReply.success());
        }

        deviceService.subDeviceOffline(deviceSn);
        return new TopicStatusResponse<MqttReply>().setData(MqttReply.success());
    }

    @Override
    public void osdDock(TopicOsdRequest<OsdDock> request, MessageHeaders headers) {
        String from = request.getFrom();
        Optional<DeviceDTO> deviceOpt = deviceRedisService.getDeviceOnline(from);
        boolean needsRecovery = deviceOpt.isEmpty();
        if (deviceOpt.isEmpty() || !StringUtils.hasText(deviceOpt.get().getWorkspaceId())) {
            deviceOpt = deviceService.getDeviceBySn(from);
            if (deviceOpt.isEmpty()) {
                log.error("Please restart the drone.");
                return;
            }
        }

        DeviceDTO device = deviceOpt.get();
        if (!StringUtils.hasText(device.getWorkspaceId())) {
            log.error("Please bind the dock first.");
        }
        if (StringUtils.hasText(device.getChildDeviceSn())) {
            deviceService.getDeviceBySn(device.getChildDeviceSn()).ifPresent(device::setChildren);
        }

        deviceRedisService.setDeviceOnline(device);

        if (needsRecovery && StringUtils.hasText(device.getWorkspaceId())) {
            triggerDeviceAutoRecovery(from);
        }

        fillDockOsd(from, request.getData());

        deviceService.pushOsdDataToWeb(device.getWorkspaceId(), BizCodeEnum.DOCK_OSD, from, request.getData());
    }

    @Override
    public void osdDockDrone(TopicOsdRequest<OsdDockDrone> request, MessageHeaders headers) {
        String from = request.getFrom();
        Optional<DeviceDTO> deviceOpt = deviceRedisService.getDeviceOnline(from);
        boolean needsRecovery = deviceOpt.isEmpty();
        if (deviceOpt.isEmpty()) {
            deviceOpt = deviceService.getDeviceBySn(from);
            if (deviceOpt.isEmpty()) {
                log.error("Please restart the drone.");
                return;
            }
        }

        if (!StringUtils.hasText(deviceOpt.get().getWorkspaceId())) {
            log.error("Please restart the drone.");
        }

        DeviceDTO device = deviceOpt.get();
        deviceRedisService.setDeviceOnline(device);

        if (needsRecovery && StringUtils.hasText(device.getWorkspaceId())) {
            triggerDeviceAutoRecovery(from);
        }
        deviceRedisService.setDeviceOsd(from, request.getData());
        // 存放数据
        deviceDetailService.saveDeviceOsd(from, request.getData());

        // 3. 带频率控制的记录上报
        // submitRecordWithRateLimit(device, request);
//        new Thread(() -> {
//            UOMDroneFlightRecordDTO uomDroneFlightRecordDTO = new UOMDroneFlightRecordDTO();
//            uomDroneFlightRecordDTO.setOrderID(device.getDeviceSn()+"-"+ DateUtil.format(new Date(), "yyyyMMdd")+"-"+ RandomUtil.randomString(8));
//            uomDroneFlightRecordDTO.setSn(device.getDeviceSn());
//            uomDroneFlightRecordDTO.setFlightStatus("Land");
//            uomDroneFlightRecordDTO.setManufacturerID("914403007954257495");
//            uomDroneFlightRecordDTO.setUasID("UAS-DEFAULT");
//            uomDroneFlightRecordDTO.setTimeStamp(DateUtil.format(DateUtil.date(request.getTimestamp()),"yyyyMMddHHmmss"));
//            uomDroneFlightRecordDTO.setUasModel(device.getType().name());
//            uomDroneFlightRecordDTO.setCoordinate(1);
//            uomDroneFlightRecordDTO.setLongitude((long) (NumberUtil.round(request.getData().getLongitude(),7, RoundingMode.DOWN).doubleValue() * 10000000));
//            uomDroneFlightRecordDTO.setLatitude((long) (NumberUtil.round(request.getData().getLatitude(),7, RoundingMode.DOWN).doubleValue() * 10000000));
//            uomDroneFlightRecordDTO.setHeightType(1);
//            uomDroneFlightRecordDTO.setHeight((int) (NumberUtil.round(request.getData().getElevation(),1, RoundingMode.DOWN).doubleValue() * 10));
//            uomDroneFlightRecordDTO.setAltitude((int) (request.getData().getHeight().doubleValue() * 10));
//            uomDroneFlightRecordDTO.setVS((int) (NumberUtil.round(request.getData().getVerticalSpeed(),1, RoundingMode.DOWN).doubleValue() * 10));
//            uomDroneFlightRecordDTO.setGS((int) (NumberUtil.round(request.getData().getHorizontalSpeed(),1, RoundingMode.DOWN).doubleValue() * 10));
//            uomDroneFlightRecordDTO.setCourse(-999);
//            log.info("UOM Record start: =================+++++++++++++++++");
//            log.info("UOM Record DTO: " + JSONUtil.toJsonStr(uomDroneFlightRecordDTO));
//            try {
//                String body = uomRecordService.sendRecord(uomDroneFlightRecordDTO);
//                log.info("UOM Record: " + body);
//            } catch (Exception e) {
//                e.printStackTrace();
//                log.error("UOM Record Error: " + e.getMessage());
//                log.error("UOM Record Error: " + e.getMessage());
//            }
//            log.info("UOM Record end: =================+++++++++++++++++");
//        }).start();
        deviceService.pushOsdDataToWeb(device.getWorkspaceId(), BizCodeEnum.DEVICE_OSD, from, request.getData());
    }

    @Override
    public void osdRemoteControl(TopicOsdRequest<OsdRemoteControl> request, MessageHeaders headers) {
        String from = request.getFrom();
        Optional<DeviceDTO> deviceOpt = deviceRedisService.getDeviceOnline(from);
        boolean needsRecovery = deviceOpt.isEmpty();
        if (deviceOpt.isEmpty() || !StringUtils.hasText(deviceOpt.get().getWorkspaceId())) {
            deviceOpt = deviceService.getDeviceBySn(from);
            if (deviceOpt.isEmpty()) {
                log.error("Please restart the drone.");
                return;
            }
        }
        DeviceDTO device = deviceOpt.get();
        if (StringUtils.hasText(device.getChildDeviceSn())) {
            deviceService.getDeviceBySn(device.getChildDeviceSn()).ifPresent(device::setChildren);
        }
        deviceRedisService.setDeviceOnline(device);

        if (needsRecovery && StringUtils.hasText(device.getWorkspaceId())) {
            triggerDeviceAutoRecovery(from);
        }

        OsdRemoteControl data = request.getData();
        deviceRedisService.setDeviceOsd(from, data);
        deviceService.pushOsdDataToPilot(device.getWorkspaceId(), from,
                new DeviceOsdHost()
                        .setLatitude(data.getLatitude())
                        .setLongitude(data.getLongitude())
                        .setHeight(data.getHeight()));
        deviceService.pushOsdDataToWeb(device.getWorkspaceId(), BizCodeEnum.RC_OSD, from, data);

    }

    @Override
    public void osdRcDrone(TopicOsdRequest<OsdRcDrone> request, MessageHeaders headers) {
        String from = request.getFrom();
        Optional<DeviceDTO> deviceOpt = deviceRedisService.getDeviceOnline(from);
        boolean needsRecovery = deviceOpt.isEmpty();
        if (deviceOpt.isEmpty()) {
            deviceOpt = deviceService.getDeviceBySn(from);
            if (deviceOpt.isEmpty()) {
                log.error("Please restart the drone.");
                return;
            }
        }
        DeviceDTO device = deviceOpt.get();
        if (!StringUtils.hasText(device.getWorkspaceId())) {
            log.error("Please bind the drone first.");
        }

        deviceRedisService.setDeviceOnline(device);

        if (needsRecovery && StringUtils.hasText(device.getWorkspaceId())) {
            triggerDeviceAutoRecovery(from);
        }

        OsdRcDrone data = request.getData();
        deviceRedisService.setDeviceOsd(from, data);
        deviceService.pushOsdDataToPilot(device.getWorkspaceId(), from,
                new DeviceOsdHost()
                        .setLatitude(data.getLatitude())
                        .setLongitude(data.getLongitude())
                        .setElevation(data.getElevation())
                        .setHeight(data.getHeight())
                        .setAttitudeHead(data.getAttitudeHead())
                        .setElevation(data.getElevation())
                        .setHorizontalSpeed(data.getHorizontalSpeed())
                        .setVerticalSpeed(data.getVerticalSpeed()));
        deviceService.pushOsdDataToWeb(device.getWorkspaceId(), BizCodeEnum.DEVICE_OSD, from, data);
    }

    @Override
    public void dockFirmwareVersionUpdate(TopicStateRequest<DockFirmwareVersion> request, MessageHeaders headers) {
        // If the reported version is empty, it will not be processed to prevent misleading page.
        if (!StringUtils.hasText(request.getData().getFirmwareVersion())) {
            return;
        }

        DeviceDTO device = DeviceDTO.builder()
                .deviceSn(request.getFrom())
                .firmwareVersion(request.getData().getFirmwareVersion())
                .firmwareStatus(BooleanUtils.isTrue(request.getData().getNeedCompatibleStatus()) ?
                        DeviceFirmwareStatusEnum.UNKNOWN : DeviceFirmwareStatusEnum.CONSISTENT_UPGRADE)
                .build();
        boolean isUpd = deviceService.updateDevice(device);
        if (!isUpd) {
            log.error("Data update of firmware version failed. SN: {}", request.getFrom());
        }
    }

    @Override
    public void rcAndDroneFirmwareVersionUpdate(TopicStateRequest<FirmwareVersion> request, MessageHeaders headers) {
        // If the reported version is empty, it will not be processed to prevent misleading page.
        if (!StringUtils.hasText(request.getData().getFirmwareVersion())) {
            return;
        }

        DeviceDTO device = DeviceDTO.builder()
                .deviceSn(request.getFrom())
                .firmwareVersion(request.getData().getFirmwareVersion())
                .build();
        boolean isUpd = deviceService.updateDevice(device);
        if (!isUpd) {
            log.error("Data update of firmware version failed. SN: {}", request.getFrom());
        }
    }

    @Override
    public void rcPayloadFirmwareVersionUpdate(TopicStateRequest<PayloadFirmwareVersion> request, MessageHeaders headers) {
        // If the reported version is empty, it will not be processed to prevent misleading page.
        if (!StringUtils.hasText(request.getData().getFirmwareVersion())) {
            return;
        }

        boolean isUpd = devicePayloadService.updateFirmwareVersion(request.getFrom(), request.getData());
        if (!isUpd) {
            log.error("Data update of payload firmware version failed. SN: {}", request.getFrom());
        }
    }

    @Override
    public void dockControlSourceUpdate(TopicStateRequest<DockDroneControlSource> request, MessageHeaders headers) {
        // If the control source is empty, it will not be processed.
        if (ControlSourceEnum.UNKNOWN == request.getData().getControlSource()) {
            return;
        }
        Optional<DeviceDTO> deviceOpt = deviceRedisService.getDeviceOnline(request.getFrom());
        if (deviceOpt.isEmpty()) {
            return;
        }
        Optional<DeviceDTO> dockOpt = deviceRedisService.getDeviceOnline(request.getGateway());
        if (dockOpt.isEmpty()) {
            return;
        }

        deviceService.updateFlightControl(dockOpt.get(), request.getData().getControlSource());
        devicePayloadService.updatePayloadControl(deviceOpt.get(),
                request.getData().getPayloads().stream()
                        .map(p -> DevicePayloadReceiver.builder()
                                .controlSource(p.getControlSource())
                                .payloadIndex(p.getPayloadIndex())
                                .sn(p.getSn())
                                .deviceSn(request.getFrom())
                                .build()).collect(Collectors.toList()));
    }

    /**
     * Dock/drone live_status state property. Demo 默认抛异常；此处仅落日志并推送给前端，避免刷 ERROR。
     */
    @Override
    public void dockLiveStatusUpdate(TopicStateRequest<DockLiveStatus> request, MessageHeaders headers) {
        String from = request.getFrom();
        DockLiveStatus liveStatus = request.getData();
        Optional<DeviceDTO> deviceOpt = deviceRedisService.getDeviceOnline(from);
        if (deviceOpt.isEmpty()) {
            log.debug("Ignore dock live_status, device offline. SN: {}", from);
            return;
        }
        DeviceDTO device = deviceOpt.get();
        if (!StringUtils.hasText(device.getWorkspaceId())) {
            log.debug("Ignore dock live_status, device unbound. SN: {}", from);
            return;
        }
        log.debug("Dock live_status from {}: {}", from, liveStatus);
        deviceService.pushOsdDataToWeb(device.getWorkspaceId(), BizCodeEnum.DOCK_LIVE_STATUS, from, liveStatus);
    }

    /**
     * RC/drone live_status state property.
     */
    @Override
    public void rcLiveStatusUpdate(TopicStateRequest<RcLiveStatus> request, MessageHeaders headers) {
        String from = request.getFrom();
        RcLiveStatus liveStatus = request.getData();
        Optional<DeviceDTO> deviceOpt = deviceRedisService.getDeviceOnline(from);
        if (deviceOpt.isEmpty()) {
            log.debug("Ignore rc live_status, device offline. SN: {}", from);
            return;
        }
        DeviceDTO device = deviceOpt.get();
        if (!StringUtils.hasText(device.getWorkspaceId())) {
            log.debug("Ignore rc live_status, device unbound. SN: {}", from);
            return;
        }
        log.debug("RC live_status from {}: {}", from, liveStatus);
        deviceService.pushOsdDataToWeb(device.getWorkspaceId(), BizCodeEnum.RC_LIVE_STATUS, from, liveStatus);
    }

    /**
     * 机场 / RC / 飞行器上报的 4G Dongle 信息；暂仅落日志，避免 demo 空实现抛异常。
     */
    @Override
    public TopicStateResponse<MqttReply> dongleInfos(TopicStateRequest<DongleInfos> request, MessageHeaders headers) {
        log.debug("dongle_infos from {}: {}", request.getFrom(), request.getData());
        return new TopicStateResponse<MqttReply>().setData(MqttReply.success());
    }

    /**
     * 飞行器 WPMZ（航线任务库）版本：缓存到 Redis，并推给前端。
     */
    @Override
    public void dockWpmzVersionUpdate(TopicStateRequest<DockDroneWpmzVersion> request, MessageHeaders headers) {
        String sn = request.getFrom();
        String version = request.getData() == null ? null : request.getData().getWpmzVersion();
        if (!StringUtils.hasText(version)) {
            return;
        }
        RedisOpsUtils.setWithExpire(RedisConst.WPMZ_VERSION_PREFIX + sn, version, RedisConst.DEVICE_ALIVE_SECOND);
        Optional<DeviceDTO> deviceOpt = deviceRedisService.getDeviceOnline(sn);
        if (deviceOpt.isEmpty()) {
            deviceOpt = deviceService.getDeviceBySn(sn);
        }
        deviceOpt.filter(d -> StringUtils.hasText(d.getWorkspaceId())).ifPresent(device ->
                deviceService.pushOsdDataToWeb(device.getWorkspaceId(), BizCodeEnum.WPMZ_VERSION, sn, request.getData()));
        log.info("Updated wpmz_version. SN: {}, version: {}", sn, version);
    }

    /**
     * 飞行器进入当前状态的原因（mode_code_reason）：缓存并推送，供前端展示返航/降落等原因。
     */
    @Override
    public TopicStateResponse<MqttReply> dockDroneModeCodeReason(
            TopicStateRequest<DockDroneModeCodeReason> request, MessageHeaders headers) {
        String sn = request.getFrom();
        DockDroneModeCodeReason data = request.getData();
        if (data != null && data.getModeCodeReason() != null) {
            RedisOpsUtils.setWithExpire(
                    RedisConst.MODE_CODE_REASON_PREFIX + sn,
                    data.getModeCodeReason().getReason(),
                    RedisConst.DEVICE_ALIVE_SECOND);
            Optional<DeviceDTO> deviceOpt = deviceRedisService.getDeviceOnline(sn);
            if (deviceOpt.isEmpty()) {
                deviceOpt = deviceService.getDeviceBySn(sn);
            }
            deviceOpt.filter(d -> StringUtils.hasText(d.getWorkspaceId())).ifPresent(device ->
                    deviceService.pushOsdDataToWeb(device.getWorkspaceId(), BizCodeEnum.MODE_CODE_REASON, sn, data));
            log.debug("Updated mode_code_reason. SN: {}, reason: {}", sn, data.getModeCodeReason());
        }
        return new TopicStateResponse<MqttReply>().setData(MqttReply.success());
    }

    @Override
    public void rcControlSourceUpdate(TopicStateRequest<RcDroneControlSource> request, MessageHeaders headers) {
        // If the control source is empty, it will not be processed.
        if (ControlSourceEnum.UNKNOWN == request.getData().getControlSource()) {
            return;
        }
        Optional<DeviceDTO> deviceOpt = deviceRedisService.getDeviceOnline(request.getFrom());
        if (deviceOpt.isEmpty()) {
            return;
        }
        Optional<DeviceDTO> dockOpt = deviceRedisService.getDeviceOnline(request.getGateway());
        if (dockOpt.isEmpty()) {
            return;
        }

        deviceService.updateFlightControl(dockOpt.get(), request.getData().getControlSource());
        devicePayloadService.updatePayloadControl(deviceOpt.get(),
                request.getData().getPayloads().stream()
                        .map(p -> DevicePayloadReceiver.builder()
                                .controlSource(p.getControlSource())
                                .payloadIndex(p.getPayloadIndex())
                                .sn(p.getSn())
                                .deviceSn(request.getFrom())
                                .build()).collect(Collectors.toList()));
    }

    private void dockGoOnline(DeviceDTO gateway, DeviceDTO subDevice) {
        if (DeviceDomainEnum.DOCK != gateway.getDomain()) {
            return;
        }
        if (!StringUtils.hasText(gateway.getWorkspaceId())) {
            log.error("The dock is not bound, please bind it first and then go online.");
            return;
        }
        if (!Objects.requireNonNullElse(subDevice.getBoundStatus(), false)) {
            // Directly bind the drone of the dock to the same workspace as the dock.
            deviceService.bindDevice(DeviceDTO.builder().deviceSn(subDevice.getDeviceSn()).workspaceId(gateway.getWorkspaceId()).build());
            subDevice.setWorkspaceId(gateway.getWorkspaceId());
        }
        deviceRedisService.setDeviceOnline(subDevice);
    }

    private void changeSubDeviceParent(String deviceSn, String gatewaySn) {
        List<DeviceDTO> gatewaysList = deviceService.getDevicesByParams(
                DeviceQueryParam.builder()
                        .childSn(deviceSn)
                        .build());
        gatewaysList.stream()
                .filter(gateway -> !gateway.getDeviceSn().equals(gatewaySn))
                .forEach(gateway -> {
                    gateway.setChildDeviceSn("");
                    deviceService.updateDevice(gateway);
                    deviceRedisService.getDeviceOnline(gateway.getDeviceSn())
                            .ifPresent(device -> {
                                device.setChildDeviceSn(null);
                                deviceRedisService.setDeviceOnline(device);
                            });
                });
    }


    private void triggerDeviceAutoRecovery(String deviceSn) {
        log.warn("[Auto-Recovery] OSD received from offline device {}, triggering online recovery", deviceSn);
        try {
            deviceService.recoverDeviceOnline(deviceSn);
        } catch (Exception e) {
            log.error("[Auto-Recovery] Failed to recover device {}: {}", deviceSn, e.getMessage());
        }
    }

    public void deviceOnlineAgain(String workspaceId, String gatewaySn, String deviceSn) {
        DeviceDTO device = DeviceDTO.builder().loginTime(LocalDateTime.now()).deviceSn(deviceSn).build();
        DeviceDTO gateway = DeviceDTO.builder()
                .loginTime(LocalDateTime.now())
                .deviceSn(gatewaySn)
                .childDeviceSn(deviceSn).build();
        deviceService.updateDevice(gateway);
        deviceService.updateDevice(device);
        gateway = deviceRedisService.getDeviceOnline(gatewaySn).map(g -> {
            g.setChildDeviceSn(deviceSn);
            return g;
        }).get();
        device = deviceRedisService.getDeviceOnline(deviceSn).map(d -> {
            d.setParentSn(gatewaySn);
            return d;
        }).get();
        deviceRedisService.setDeviceOnline(gateway);
        deviceRedisService.setDeviceOnline(device);
        if (StringUtils.hasText(workspaceId)) {
            deviceService.subDeviceOnlineSubscribeTopic(SDKManager.getDeviceSDK(gatewaySn));
        }

        log.warn("{} is already online.", deviceSn);
    }

    /**
     * Convert the received gateway device object into a database entity object.
     * @param gateway
     * @return
     */
    private DeviceDTO deviceGatewayConvertToDevice(String gatewaySn, UpdateTopo gateway) {
        if (null == gateway) {
            throw new IllegalArgumentException();
        }
        return DeviceDTO.builder()
                .deviceSn(gatewaySn)
                .subType(gateway.getSubType())
                .type(gateway.getType())
                .thingVersion(gateway.getThingVersion())
                .domain(gateway.getDomain())
                .controlSource(gateway.getSubDevices().isEmpty() ? null :
                        ControlSourceEnum.find(gateway.getSubDevices().get(0).getIndex().getControlSource()))
                .build();
    }

    /**
     * Convert the received drone device object into a database entity object.
     * @param device
     * @return
     */
    private DeviceDTO subDeviceConvertToDevice(UpdateTopoSubDevice device) {
        if (null == device) {
            throw new IllegalArgumentException();
        }
        return DeviceDTO.builder()
                .deviceSn(device.getSn())
                .type(device.getType())
                .subType(device.getSubType())
                .thingVersion(device.getThingVersion())
                .domain(device.getDomain())
                .build();
    }

    private Optional<DeviceDTO> onlineSaveDevice(DeviceDTO device, String childSn, String parentSn) {

        device.setChildDeviceSn(childSn);
        device.setLoginTime(LocalDateTime.now());

        Optional<DeviceDTO> deviceOpt = deviceService.getDeviceBySn(device.getDeviceSn());

        if (deviceOpt.isEmpty()) {
            device.setIconUrl(new DeviceIconUrl());
            // Set the icon of the gateway device displayed in the pilot's map, required in the TSA module.
            device.getIconUrl().setNormalIconUrl(IconUrlEnum.NORMAL_PERSON.getUrl());
            // Set the icon of the gateway device displayed in the pilot's map when it is selected, required in the TSA module.
            device.getIconUrl().setSelectIconUrl(IconUrlEnum.SELECT_PERSON.getUrl());
            device.setBoundStatus(false);

            // Query the model information of this gateway device.
            dictionaryService.getOneDictionaryInfoByTypeSubType(
                    device.getDomain().getDomain(), device.getType().getType(), device.getSubType().getSubType())
                    .ifPresent(entity -> {
                        device.setDeviceName(entity.getDeviceName());
                        device.setNickname(entity.getDeviceName());
                        device.setDeviceDesc(entity.getDeviceDesc());
                    });
        }
        boolean success = deviceService.saveOrUpdateDevice(device);
        if (!success) {
            return Optional.empty();
        }

        deviceOpt = deviceService.getDeviceBySn(device.getDeviceSn());
        DeviceDTO redisDevice = deviceOpt.get();
        redisDevice.setStatus(true);
        redisDevice.setParentSn(parentSn);

        deviceRedisService.setDeviceOnline(redisDevice);
        return deviceOpt;
    }

    private void fillDockOsd(String dockSn, OsdDock dock) {
        Optional<OsdDock> oldDockOpt = deviceRedisService.getDeviceOsd(dockSn, OsdDock.class);
        if (Objects.nonNull(dock.getJobNumber())) {
            return;
        }
        if (oldDockOpt.isEmpty()) {
            deviceRedisService.setDeviceOsd(dockSn, dock);
            return;
        }
        OsdDock oldDock = oldDockOpt.get();
        if (Objects.nonNull(dock.getModeCode())) {
            dock.setDrcState(oldDock.getDrcState());
            deviceRedisService.setDeviceOsd(dockSn, dock);
            return;
        }
        if (Objects.nonNull(dock.getDrcState()) ) {
            oldDock.setDrcState(dock.getDrcState());
            deviceRedisService.setDeviceOsd(dockSn, oldDock);
        }
    }
    private void submitRecordWithRateLimit(DeviceDTO device, TopicOsdRequest<OsdDockDrone> request) {

        String sn = device.getDeviceSn();
        long currentTime = System.currentTimeMillis();

        // 检查时间间隔
        if (lastRecordTimeMap.containsKey(sn)) {
            long lastTime = lastRecordTimeMap.get(sn);
            if (currentTime - lastTime < MIN_RECORD_INTERVAL) {
                log.debug("Skip record for SN {} due to rate limit", sn);
                return;
            }
        }

        // 更新时间戳并提交任务
        lastRecordTimeMap.put(sn, currentTime);
        recordExecutor.submit(() -> processFlightRecord(device, request));
    }

    private void processFlightRecord(DeviceDTO device, TopicOsdRequest<OsdDockDrone> request) {
        // 1. 构建DTO（提取为独立方法）
        UOMDroneFlightRecordDTO recordDto = buildFlightRecordDto(device, request);

        // 2. 记录日志（优化日志格式）
        log.info("UOM Record start ================= [SN: {}]", device.getDeviceSn());
        log.debug("UOM Record DTO: {}", JSONUtil.toJsonStr(recordDto));

        // 3. 发送记录（带异常处理）
        try {
            String response = uomRecordService.sendRecord(recordDto);
            log.info("UOM Record success, response: {}", response);
        } catch (Exception e) {
            log.error("UOM Record failed for SN {}: {}", device.getDeviceSn(), e.getMessage());
            log.debug("Stack trace: ", e);
        } finally {
            log.info("UOM Record end =================== [SN: {}]", device.getDeviceSn());
        }
    }
    private UOMDroneFlightRecordDTO buildFlightRecordDto(DeviceDTO device, TopicOsdRequest<OsdDockDrone> request) {
        UOMDroneFlightRecordDTO dto = new UOMDroneFlightRecordDTO();
        // 公共字段设置
        dto.setOrderID(String.format("%s-%s-%s",
                device.getDeviceSn(),
                DateUtil.format(new Date(), "yyyyMMdd"),
                RandomUtil.randomString(8)));

        dto.setSn(device.getDeviceSn());
        dto.setFlightStatus("Land");
        dto.setManufacturerID("914403007954257495");
        dto.setUasID("UAS-DEFAULT");
        dto.setTimeStamp(DateUtil.format(DateUtil.date(request.getTimestamp()), "yyyyMMddHHmmss"));
        dto.setUasModel(device.getType().name());

        // 坐标相关计算
        OsdDockDrone data = request.getData();
        dto.setCoordinate(1);
        dto.setLongitude(convertCoordinate(data.getLongitude().doubleValue()));
        dto.setLatitude(convertCoordinate(data.getLatitude().doubleValue()));
        dto.setHeightType(1);
        dto.setHeight(convertToInt(data.getElevation().doubleValue(), 1));
        dto.setAltitude(convertToInt(data.getHeight().doubleValue(), 1));
        dto.setVS(convertToInt(data.getVerticalSpeed().doubleValue(), 1));
        dto.setGS(convertToInt(data.getHorizontalSpeed().doubleValue(), 1));
        dto.setCourse(-999);

        return dto;
    }
    // 辅助方法
    private long convertCoordinate(Double value) {
        return (long) (NumberUtil.round(value, 7, RoundingMode.DOWN).doubleValue() * 10000000);
    }

    private int convertToInt(Double value, int decimalPlaces) {
        return (int) (NumberUtil.round(value, decimalPlaces, RoundingMode.DOWN).doubleValue() * 10);
    }
}
