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

import cn.hutool.core.date.DateUtil;
import com.dji.sample.component.mqtt.model.EventsReceiver;
import com.dji.sample.manage.model.dto.OsdDroneDTO;
import com.dji.sample.map.model.dto.FlightTrackDTO;
import com.dji.sample.wayline.model.enums.WaylineJobStatusEnum;
import com.dji.sample.wayline.service.IWaylineRedisService;
import com.dji.sdk.cloudapi.property.DockDroneCommanderFlightHeight;
import com.dji.sdk.cloudapi.property.DockDroneCommanderModeLostAction;
import com.dji.sdk.cloudapi.property.DockDroneRthMode;
import com.dji.sdk.cloudapi.wayline.FlighttaskProgress;
import com.dji.sdk.cloudapi.wayline.FlighttaskProgressExt;
import com.dji.sdk.common.CoordinateUtil;
import com.dji.sample.common.util.UserRequest;
import com.dji.sample.component.websocket.model.BizCodeEnum;
import com.dji.sample.component.websocket.service.IWebSocketMessageService;
import com.dji.sample.configuration.CustomConfiguration;
import com.dji.sdk.common.AMap;
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.IDeviceDictionaryService;
import com.dji.sample.manage.service.IDevicePayloadService;
import com.dji.sample.manage.service.IDeviceRedisService;
import com.dji.sample.manage.service.IDeviceService;
import com.dji.sample.map.model.dto.FlightTaskDTO;
import com.dji.sample.map.model.entity.FlightTaskEntity;
import com.dji.sample.map.model.entity.FlightTrackEntity;
import com.dji.sample.map.model.enums.FlightPointTypeEnum;
import com.dji.sample.map.service.IFlightTrackTaskService;
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.config.version.GatewayManager;
import com.dji.sdk.common.SDKManager;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.MessageHeaders;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
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 IFlightTrackTaskService flightTaskService;

    @Autowired
    private IWaylineRedisService waylineRedisService;

    @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);
        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);
        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);
        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);
        deviceRedisService.setDeviceOsd(from, request.getData());

        OsdDroneDTO data = toDroneDto(request.getData());
        deviceService.pushOsdDataToWeb(device.getWorkspaceId(), BizCodeEnum.DEVICE_OSD, from, request.getData());
        //添加航点轨迹信息
        String jobId = null;
        Optional<EventsReceiver<FlighttaskProgress>> runningDataOpt = waylineRedisService.getRunningWaylineJob(request.getGateway());
        if (runningDataOpt.isPresent()) {
            log.info("======jobId getSn:" +  runningDataOpt.map(EventsReceiver::getSn).get());
            jobId = runningDataOpt.map(EventsReceiver::getOutput)
                    .map(FlighttaskProgress::getExt)
                    .map(FlighttaskProgressExt::getFlightId)
                    .orElse(null);
            log.info("======FlightId:" +  jobId);
        }
        toAddFilghtPoint(from, device, data,jobId);
    }

    @Override
    public void osdRemoteControl(TopicOsdRequest<OsdRemoteControl> request, MessageHeaders headers) {
        String from = request.getFrom();
        Optional<DeviceDTO> deviceOpt = deviceRedisService.getDeviceOnline(from);
        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.getChildDeviceSn())) {
            deviceService.getDeviceBySn(device.getChildDeviceSn()).ifPresent(device::setChildren);
        }
        deviceRedisService.setDeviceOnline(device);

        OsdRemoteControl data = request.getData();
        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);
        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);
        deviceRedisService.setDeviceOsd(from, request.getData());
        OsdDroneDTO data = toDroneDto(request.getData());
        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);
        //添加航点轨迹信息
        toAddFilghtPoint(from, device, data,null);

    }

    private void toAddFilghtPoint(String from, DeviceDTO device, OsdDroneDTO data,String jobId) {

        switch (data.getModeCode()) {
            case TAKEOFF_FINISHED:
            case MANUAL:
            case TAKEOFF_AUTO:
            case WAYLINE:
            case PANORAMIC_SHOT:
            case ACTIVE_TRACK:
            case ADS_B_AVOIDANCE:
            case RETURN_AUTO:
            case LIVE_FLIGHT_CONTROLS:
            case LANDING_THREE_PROPELLER:
            case APAS:
                FlightTaskDTO flightTask = getFlightTask(device);
                if(flightTask == null) {
                    Optional<String> lockOpt = deviceRedisService.getDroneFlightTaskLock(device.getDeviceSn());
                    if(lockOpt.isPresent()) {
                        return;
                    }
                    log.info("==============创建飞行任务====：" + data.toString());
                    deviceRedisService.setDroneFlightTaskLock(device.getDeviceSn());
                    flightTask = createFlightTask(device);
                }
                //添加飞行轨迹
                addFlightPoint(flightTask, device,data,false);
                //推送实时轨迹信息
                List<FlightTrackDTO> trackList = getRedisFlightPoint(flightTask,device,data);
                pushFlyTrackToWeb(from,device,trackList);

                break;
            case LANDING_AUTO:
            case LANDING_FORCED:
                //飞行结束
                log.info("==============飞机降落====：" + data.toString());
                Optional<FlightTaskDTO> opt = deviceRedisService.getDroneFlightTask(device.getDeviceSn());
                if(opt.isPresent()) {
                    //移除redis 任务
                    deviceRedisService.delDroneFlightTask(device.getDeviceSn());

                    flightTask = opt.get();
                    log.info("==============飞机降落 执行添加飞行轨迹====：" + flightTask.toString());
                    //添加飞行轨迹
                    FlightTaskDTO task = addFlightPoint(flightTask,device,data,true);
                    log.info("==============飞机降落 更新飞行任务====：" + task.toString());
                    //更新飞行任务
                    updateFlightTask(task);

                    //推送实时轨迹信息
                    trackList = getRedisFlightPoint(flightTask,device,data);
                    pushFlyTrackToWeb(from,device,trackList);

                    //移除redis航线
                    deviceRedisService.delDroneFlyTrack(device.getDeviceSn());
                }
                break;
            default:
                break;

        }
    }

    private void pushFlyTrackToWeb(String from, DeviceDTO device,List<FlightTrackDTO> trackList) {
        //推送实时轨迹信息
        if(!CollectionUtils.isEmpty(trackList)) {
            deviceService.pushOsdDataToWeb(device.getWorkspaceId(), BizCodeEnum.DRONE_FLY_TRACK, from, trackList);
        }
    }

    private List<FlightTrackDTO> getRedisFlightPoint(FlightTaskDTO task,DeviceDTO device,OsdDroneDTO data) {
        List<FlightTrackDTO> flightPoints = null;
        Optional<List<FlightTrackDTO>> optList = deviceRedisService.getDroneFlyTrack(device.getDeviceSn());
        if(optList.isPresent()) {
            flightPoints = optList.get();
        } else {
            flightPoints = new ArrayList<FlightTrackDTO>();
        }
        addRedisFlightPoint(task,flightPoints,device,data);
        return flightPoints;
    }

    private void addRedisFlightPoint(FlightTaskDTO task, List<FlightTrackDTO> flightPoints, DeviceDTO device,OsdDroneDTO data) {
        FlightTrackDTO point = FlightTrackDTO.builder()
                .taskId(task.getId())
                .modeCode(data.getModeCode().getCode())
                .longitude(data.getLongitude().doubleValue())
                .latitude(data.getLatitude().doubleValue())
                .altitude(data.getHeight().doubleValue())
                .elevation(data.getElevation().doubleValue())
                .type(FlightPointTypeEnum.HAND.getType())
                .createTime(LocalDateTime.ofInstant(Instant.ofEpochMilli(System.currentTimeMillis()), ZoneId.systemDefault())).build();
        flightPoints.add(point);
        deviceRedisService.setDroneFlyTrack(device.getDeviceSn(),flightPoints);
    }

    private FlightTaskDTO addFlightPoint(FlightTaskDTO task, DeviceDTO device,OsdDroneDTO data,boolean isFinished) {
        synchronized (device.getDeviceSn().intern()) {
            Optional<FlightTrackEntity> lastPoint = deviceRedisService.getLastDroneTrack(device.getDeviceSn());
            long curTime = System.currentTimeMillis();
            boolean isAdd = false;
            if (!lastPoint.isPresent()) {
                isAdd = true;
                log.info("添加飞行轨迹: 首次记录,workspaceID:" + device.getWorkspaceId() + ",sn:" + device.getDeviceSn()
                        + ",飞行器状态:" + data.getModeCode().name() + ",经度：" + data.getLongitude() + ",维度：" + data.getLatitude()
                        + "高度：" + data.getHeight());

            } else {
                long btwTime = curTime - lastPoint.get().getCreateTime();
                log.info("添加飞行轨迹: workspaceID:" + device.getWorkspaceId() + ",sn:" + device.getDeviceSn() + "当前时间：" + curTime  + "上次添加时间：" + lastPoint.get().getCreateTime()
                        + ",间隔时间（毫秒）：" + btwTime + "是否添加航点：" + (btwTime >= CustomConfiguration.frequency * 1000));
                if(btwTime >= CustomConfiguration.frequency * 1000) {
                    isAdd = true;
                    log.info("添加飞行轨迹: workspaceID:" + device.getWorkspaceId() + ",sn:" + device.getDeviceSn()
                            + ",间隔时间（毫秒）：" + btwTime + ",采集频率（秒）" + CustomConfiguration.frequency
                            + ",飞行器状态:" + data.getModeCode().name() + ",经度：" + data.getLongitude() + ",维度：" + data.getLatitude()
                            + "高度：" + data.getHeight());
                }
                if(isSamePoint(data,lastPoint.get()) || isEqualsPoint(data,lastPoint.get())) {
                    log.info("添加飞行轨迹:workspaceID:" + device.getWorkspaceId() + ",sn:" + device.getDeviceSn() +
                            "相同坐标，不做记录" + data.toString());
                    isAdd = false;
                }
            }
            if(isAdd || isFinished) {
                log.info("获取飞行轨迹任务:" + task.toString());
                Optional<AMap> homeOpt = deviceRedisService.getDroneHomeChange(device.getDeviceSn());
                //添加Home信息
                if(!lastPoint.isPresent() && homeOpt.isPresent()) {
                    FlightTrackEntity flightTrackEntity = FlightTrackEntity.builder()
                                .taskId(task.getId())
                                .modeCode(data.getModeCode().getCode())
                                .longitude(CoordinateUtil.checkValidVal(homeOpt.get().getLongitude()))
                                .latitude(CoordinateUtil.checkValidVal(homeOpt.get().getLatitude()))
                                .type(FlightPointTypeEnum.HOME.getType())
                                .createTime(System.currentTimeMillis()).build();
                        flightTaskService.addHomePoint(flightTrackEntity);
                }

                //添加轨迹信息
                FlightTrackEntity flightTrackEntity = FlightTrackEntity.builder()
                            .taskId(task.getId())
                            .modeCode(data.getModeCode().getCode())
                            .longitude(data.getLongitude().doubleValue())
                            .latitude(data.getLatitude().doubleValue())
                            .altitude(data.getHeight().doubleValue())
                            .elevation(data.getElevation().doubleValue())
                            .type(FlightPointTypeEnum.HAND.getType())
                            .createTime(System.currentTimeMillis()).build();
                flightTaskService.addFlightPoint(flightTrackEntity);
                log.info("添加轨迹完成: task:" + task.getTaskName());
                //记录添加时间
                deviceRedisService.setLastDroneTrack(device.getDeviceSn(), flightTrackEntity);
                return task;

            }
            return null;
        }
    }

    private boolean isSamePoint(OsdDroneDTO data,FlightTrackEntity lastPoint) {

        return data.getLongitude().doubleValue() == lastPoint.getLongitude()
                && data.getLatitude().doubleValue() == lastPoint.getLatitude()
                && data.getHeight().doubleValue() == lastPoint.getAltitude()
                && data.getElevation().doubleValue() == lastPoint.getElevation();
    }

    private boolean isEqualsPoint(OsdDroneDTO data,FlightTrackEntity lastPoint) {
        return  new BigDecimal(data.getLongitude().doubleValue()).compareTo(new BigDecimal(lastPoint.getLongitude().doubleValue())) == 0
                && new BigDecimal(data.getLatitude().doubleValue()).compareTo(new BigDecimal(lastPoint.getLatitude())) == 0
                && new BigDecimal(data.getHeight().doubleValue()).compareTo(new BigDecimal(lastPoint.getAltitude())) == 0
                && new BigDecimal(data.getElevation().doubleValue() ).compareTo(new BigDecimal(lastPoint.getElevation())) == 0;
    }

    private int updateFlightTask(FlightTaskDTO task) {
        if(task != null && task.getId() != null) {
            log.info("飞行结束 更新轨迹任务: " + task.toString());
            long endTime = System.currentTimeMillis();
            long beginTime = task.getBeginTime().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
            long flyTime = endTime  - beginTime;
            FlightTaskEntity entity = flightTaskService.dtoToEntity(task);
            entity.setEndTime(endTime);
            entity.setFlyTime(flyTime);
            entity.setUpdateTime(endTime);
            return flightTaskService.updateFlightTask(entity);
        }
        return 0;
    }

    private FlightTaskDTO getFlightTask(DeviceDTO device) {
        //获取任务名称
        FlightTaskDTO flightTaskDTO = null;
        Optional<FlightTaskDTO> opt = deviceRedisService.getDroneFlightTask(device.getDeviceSn());
        if(opt.isPresent()) {
            flightTaskDTO = opt.get();
        } else {
            //查询当天未完成的任务
            flightTaskDTO = flightTaskService.getCurrentFlightTask(device.getWorkspaceId(),device.getDeviceSn());
        }
        if(flightTaskDTO != null) {
            deviceRedisService.setDroneFlightTask(device.getDeviceSn(), flightTaskDTO);
        }
        return flightTaskDTO;
    }

    private FlightTaskDTO createFlightTask(DeviceDTO device) {
        Date curTime = new Date();
        String taskName = getTaskName(curTime, device.getDeviceSn());
        return flightTaskService.createFlightTask(FlightTaskEntity.builder()
                .taskName(taskName)
                .workspaceId(device.getWorkspaceId())
                .deviceSn(device.getDeviceSn())
                .deviceName(device.getDeviceName())
                .payload(device.getPayloadsList() == null ? null : device.getPayloadsList().get(0).getPayloadName())
                .beginTime(curTime.getTime())
                .createTime(curTime.getTime())
                .username(UserRequest.getCurrentUser() != null ? UserRequest.getCurrentUser().getUsername() : "system")
                .build());
    }

    //飞行器sn yyyy-MM-dd HH:mm:ss
    private String getTaskName(Date curTime,String droneSn) {
        String format = DateUtil.format(curTime, "yyyy-MM-dd HH:mm:ss");
        return droneSn + " " + format;
    }

    @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(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;
        }
        log.info("==============dockControlSourceUpdate:" + request.getData().toString());
        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(),request.getData().getModeCodeReason());
        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()));

    }

    @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;
        }
        log.info("==============rcControlSourceUpdate:" + request.getData().toString());
        Optional<DeviceDTO> deviceOpt = deviceRedisService.getDeviceOnline(request.getFrom());
        if (deviceOpt.isEmpty()) {
            return;
        }
        Optional<DeviceDTO> rcOpt = deviceRedisService.getDeviceOnline(request.getGateway());
        if (rcOpt.isEmpty()) {
            return;
        }

        AMap aMap = new AMap(CoordinateUtil.checkValidVal(request.getData().getHomeLongitude()),CoordinateUtil.checkValidVal(request.getData().getHomeLatitude()));
        //home点更新
        deviceService.updateHome(deviceOpt.get(), aMap);
        deviceService.updateFlightControl(rcOpt.get(), request.getData().getControlSource(),request.getData().getModeCodeReason());
        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()));
    }

    @Override
    public void rcLiveStatusUpdate(TopicStateRequest<RcLiveStatus> request, MessageHeaders headers) {
        log.info("==========rcLiveStatusUpdate=========request:"+ request.toString() + ",headers:" + headers.toString());
    }

    @Override
    public TopicStateResponse<MqttReply> dockDroneCurrentRthMode(TopicStateRequest<DockDroneCurrentRthMode> request, MessageHeaders headers) {
        log.info("==========dockDroneCurrentRthMode=========request:"+ request.toString() + ",headers:" + headers.toString());
        return new TopicStateResponse<MqttReply>().setData(MqttReply.success());
    }
    @Override
    public TopicStateResponse<MqttReply> dockDroneCurrentCommanderFlightMode(TopicStateRequest<DockDroneCurrentCommanderFlightMode> request, MessageHeaders headers) {
        log.info("==========dockDroneCurrentCommanderFlightMode=========request:"+ request.toString() + ",headers:" + headers.toString());
          return new TopicStateResponse<MqttReply>().setData(MqttReply.success());
    }

    @Override
    public TopicStateResponse<MqttReply> dockDroneCommanderFlightHeight(TopicStateRequest<DockDroneCommanderFlightHeight> request, MessageHeaders headers) {
        log.info("==========dockDroneCommanderFlightHeight=========request:"+ request.toString() + ",headers:" + headers.toString());
        return new TopicStateResponse<MqttReply>().setData(MqttReply.success());
    }
    @Override
    public TopicStateResponse<MqttReply> dockDroneRthMode(TopicStateRequest<DockDroneRthMode> request, MessageHeaders headers) {
        log.info("==========dockDroneRthMode=========request:"+ request.toString() + ",headers:" + headers.toString());
        return new TopicStateResponse<MqttReply>().setData(MqttReply.success());
    }

    @Override
    public void dockWpmzVersionUpdate(TopicStateRequest<DockDroneWpmzVersion> request, MessageHeaders headers) {
        log.info("==========dockWpmzVersionUpdate=========request:"+ request.toString() + ",headers:" + headers.toString());
    }
    @Override
    public void dockLiveStatusUpdate(TopicStateRequest<DockLiveStatus> request, MessageHeaders headers) {
        log.info("==========dockLiveStatusUpdate=========request:"+ request.toString() + ",headers:" + headers.toString());
    }
    @Override
    public TopicStateResponse<MqttReply> dongleInfos(TopicStateRequest<DongleInfos> request, MessageHeaders headers) {
        log.info("==========dongleInfos=========request:"+ request.toString() + ",headers:" + headers.toString());
        return new TopicStateResponse<MqttReply>().setData(MqttReply.success());
    }
    @Override
    public TopicStateResponse<MqttReply> dockDroneCommanderModeLostAction(TopicStateRequest<DockDroneCommanderModeLostAction> request, MessageHeaders headers) {
        log.info("==========dockDroneCommanderModeLostAction=========request:"+ request.toString() + ",headers:" + headers.toString());
        return new TopicStateResponse<MqttReply>().setData(MqttReply.success());
    }
    @Override
    public TopicStateResponse<MqttReply> dockDroneModeCodeReason(TopicStateRequest<DockDroneModeCodeReason> request, MessageHeaders headers) {
        log.info("==========dockDroneModeCodeReason=========request:"+ request.toString() + ",headers:" + headers.toString());
        return new TopicStateResponse<MqttReply>().setData(MqttReply.success());
    }

    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);
                            });
                });
    }


    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  OsdDroneDTO toDroneDto(Object data) {
        OsdDroneDTO dto = new OsdDroneDTO();
        if (data instanceof OsdRcDrone) {
            OsdRcDrone osdRcDrone = (OsdRcDrone) data;
            dto.setBattery(osdRcDrone.getBattery());
            dto.setModeCode(osdRcDrone.getModeCode());
            dto.setFirmwareVersion(osdRcDrone.getFirmwareVersion());
            dto.setGear(osdRcDrone.getGear());
            dto.setAttitudeHead(osdRcDrone.getAttitudeHead());
            dto.setHeight(osdRcDrone.getHeight());
            dto.setLongitude(osdRcDrone.getLongitude());
            dto.setLatitude(osdRcDrone.getLatitude());
            dto.setElevation(osdRcDrone.getElevation());
            dto.setWindSpeed(osdRcDrone.getWindSpeed());
            dto.setTotalFlightTime(osdRcDrone.getTotalFlightTime());

        } else if (data instanceof OsdDockDrone) {
            OsdDockDrone osdDockDrone = (OsdDockDrone) data;
            dto.setBattery(osdDockDrone.getBattery());
            dto.setModeCode(osdDockDrone.getModeCode());
            dto.setFirmwareVersion(osdDockDrone.getFirmwareVersion());
            dto.setGear(osdDockDrone.getGear());
            dto.setAttitudeHead(osdDockDrone.getAttitudeHead());
            dto.setHeight(osdDockDrone.getHeight());
            dto.setLongitude(osdDockDrone.getLongitude());
            dto.setLatitude(osdDockDrone.getLatitude());
            dto.setElevation(osdDockDrone.getElevation());
            dto.setWindSpeed(osdDockDrone.getWindSpeed());
            dto.setTotalFlightTime(osdDockDrone.getTotalFlightTime());
        }

        return dto;
    }

}
