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

import cn.hutool.core.util.CoordinateUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.dji.sample.map.dao.IFlightTaskMapper;
import com.dji.sample.map.dao.IFlightTrackMapper;
import com.dji.sample.map.model.dto.FlightTaskDTO;
import com.dji.sample.map.model.dto.FlightTrackDTO;
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.model.param.FlightTaskQueryParam;
import com.dji.sample.map.service.IFlightTrackTaskService;
import com.dji.sample.media.model.MediaFileEntity;
import com.dji.sample.media.service.IFileService;
import com.dji.sample.wayline.model.enums.WaylineTemplateTypeEnum;
import com.dji.sdk.common.Pagination;
import com.dji.sdk.common.PaginationData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * @author hqjiang
 * @version 1.0
 * @date 2024/7/23
 */
@Service
@Transactional
public class FlightTrackTaskServiceImpl implements IFlightTrackTaskService {

    @Autowired
    private IFlightTaskMapper mapper;

    @Autowired
    private IFlightTrackMapper trackMapper;

    @Autowired
    private IFileService fileService;

    @Override
    public PaginationData<FlightTaskDTO> getFlightTasksPaginationByWorkspaceId(String workspaceId, FlightTaskQueryParam param, long page, long pageSize) {
        Page<FlightTaskEntity> pageData = mapper.selectPage(
                new Page<FlightTaskEntity>(page, pageSize),
                new LambdaQueryWrapper<FlightTaskEntity>()
                        .eq(FlightTaskEntity::getWorkspaceId, workspaceId)
                        .ge(param != null && param.getBeginTime() != null, FlightTaskEntity::getCreateTime,param.getBeginTime())
                        .le(param != null && param.getEndTime() != null, FlightTaskEntity::getCreateTime,param.getEndTime())
                        .eq(param != null && StringUtils.hasText(param.getTemplateType()), FlightTaskEntity::getTemplateType,param.getTemplateType())
                        .eq(param != null && StringUtils.hasText(param.getPayload()), FlightTaskEntity::getPayload,param.getPayload())
                        .and(StringUtils.hasText(param.getSearchInfo()),
                                wrapper -> wrapper .like( FlightTaskEntity::getTaskName, param.getSearchInfo())
                                        .or().like( FlightTaskEntity::getPayload, param.getSearchInfo())
                                        .or().like( FlightTaskEntity::getWaylineName, param.getSearchInfo())
                        )
                        .orderByDesc(FlightTaskEntity::getCreateTime));
        List<FlightTaskDTO> records = pageData.getRecords()
                .stream()
                .map(this::entityToDto)
                .collect(Collectors.toList());

        return new PaginationData<FlightTaskDTO>(records, new Pagination(pageData.getCurrent(), pageData.getSize(), pageData.getTotal()));
    }

    @Override
    public FlightTaskDTO getCurrentFlightTask(String workspaceId, String droneSn) {
        FlightTaskEntity entity = mapper.selectOne(new LambdaQueryWrapper<FlightTaskEntity>()
                .eq(FlightTaskEntity::getWorkspaceId, workspaceId)
                .eq(FlightTaskEntity::getDeviceSn, droneSn)
                .le(FlightTaskEntity::getBeginTime,System.currentTimeMillis())
                .isNull(FlightTaskEntity::getEndTime)
                .orderByDesc(FlightTaskEntity::getBeginTime)
                .last("limit 1")
        );
        if(entity == null) {
            return null;
        }
        return entityToDto(entity);
    }


    private FlightTaskDTO getFlightTaskById(String taskId) {
        FlightTaskEntity entity = mapper.selectOne(new LambdaQueryWrapper<FlightTaskEntity>()
                .eq(FlightTaskEntity::getId, taskId)
        );
        if(entity == null) {
            return null;
        }
        return entityToDto(entity);
    }

    @Override
    public Integer addFlightPoint(FlightTrackDTO flightPoint) {
        FlightTrackEntity flightTrackEntity = this.flightTrackDtoToEntity(flightPoint);
        return trackMapper.insert(flightTrackEntity);
    }

    @Override
    public Integer addFlightPoint(FlightTrackEntity flightPoint) {
        return trackMapper.insert(flightPoint);
    }

    @Override
    public Integer addHomePoint(FlightTrackEntity flightPoint) {
        FlightTrackEntity homePoint = trackMapper.selectOne(new LambdaQueryWrapper<FlightTrackEntity>()
                .eq(FlightTrackEntity::getTaskId, flightPoint.getTaskId())
                .eq(FlightTrackEntity::getType, FlightPointTypeEnum.HOME));
        if(homePoint == null) {
            return trackMapper.insert(flightPoint);
        }
        return 0;
    }


    /**
     * 通过taskId获取轨迹回放
     * @param taskId
     * @return
     */
    @Override
    public List<FlightTrackDTO> getFlightTrackByTaskId(String taskId) {
        List<FlightTrackEntity> filghtPoints = trackMapper.selectList(new LambdaQueryWrapper<FlightTrackEntity>()
                .eq(FlightTrackEntity::getTaskId, taskId)
                .orderByAsc(FlightTrackEntity::getCreateTime));
        if(!CollectionUtils.isEmpty(filghtPoints)) {
            //查询任务信息
            FlightTaskEntity task = mapper.selectOne(new LambdaQueryWrapper<FlightTaskEntity>() .eq(FlightTaskEntity::getId, taskId));
            //查询图片文件
            long endTime = task.getEndTime() == null ? System.currentTimeMillis():task.getEndTime();
            //飞行坐标
            List<FlightTrackEntity> normalPoints = filghtPoints.stream().filter(fp -> fp.getType() == null || FlightPointTypeEnum.HAND.getType().intValue() == fp.getType().intValue())
                    .sorted(Comparator.comparing(FlightTrackEntity::getCreateTime)).collect(Collectors.toList());
            //航线坐标
            List<FlightTrackEntity> waylinePoints = filghtPoints.stream().filter(fp ->FlightPointTypeEnum.WAYLINE.getType().intValue() == fp.getType().intValue())
                    .sorted(Comparator.comparing(FlightTrackEntity::getPointIndex)).collect(Collectors.toList());

            //照片坐标
            List<MediaFileEntity> mediaFileEntities = fileService.getFlightTrackMediaFiles(task.getWorkspaceId(),task.getDeviceSn(),task.getBeginTime(),endTime);
            //按拍摄时间去重
            List<MediaFileEntity> uniqueMediaRecords = mediaFileEntities.stream()
                    .collect(Collectors.groupingBy(
                            MediaFileEntity::getPictureTime,
                            Collectors.collectingAndThen(
                                    Collectors.toList(),
                                    list -> list.get(0)
                            )
                    ))
                    .values()
                    .stream()
                    .collect(Collectors.toList());

            //Home点
            Optional<FlightTrackEntity> homePointOpt = filghtPoints.stream().filter(fp ->FlightPointTypeEnum.HOME.getType().intValue() == fp.getType().intValue()).findFirst();
            FlightTrackEntity homePoint = null;
            if(homePointOpt.isPresent()) {
                homePoint = homePointOpt.get();
            } else {
                homePoint = FlightTrackEntity.builder().type(FlightPointTypeEnum.HOME.getType())
                        .latitude(normalPoints.get(0).getLatitude())
                        .longitude(normalPoints.get(0).getLongitude())
                        .createTime(normalPoints.get(0).getCreateTime())
                        .build();
            }
            FlightTrackEntity firstPoint = normalPoints.get(0);
            FlightTrackEntity lastPoint = normalPoints.get(normalPoints.size() - 1);

            List<FlightTrackEntity> newFilghtTrackList = null;
            //航线飞行
            if(!CollectionUtils.isEmpty(waylinePoints)) {
                newFilghtTrackList = waylinePoints;
                newFilghtTrackList.add(0,firstPoint);
                newFilghtTrackList.add(lastPoint);
            //照片
            } else if(!CollectionUtils.isEmpty(uniqueMediaRecords)) {
                List<FlightTrackEntity> mediaTrackList = uniqueMediaRecords.stream().map(m -> mediaDTOToFlightTrackDTO(task.getId(), m)).sorted(Comparator.comparing(FlightTrackEntity::getCreateTime)).collect(Collectors.toList());
                newFilghtTrackList = mediaTrackList;
                newFilghtTrackList.add(0,firstPoint);
                newFilghtTrackList.add(lastPoint);
            } else {
                newFilghtTrackList = normalPoints;
            }

            newFilghtTrackList.add(0,homePoint);

            return newFilghtTrackList.stream().map(this::flightTrackEntityToDto).collect(Collectors.toList());
        }
        return null;
    }

    /**
     * 通过taskId获取实时飞行轨迹
     * @param taskId
     * @return
     */
    @Override
    public List<FlightTrackDTO> getRTFlightTrackByTaskId(Integer taskId) {
        //飞行坐标
        List<FlightTrackEntity> filghtPoints = trackMapper.selectList(new LambdaQueryWrapper<FlightTrackEntity>()
                .eq(FlightTrackEntity::getTaskId, taskId)
                .and(wrapper ->
                        wrapper.isNull(FlightTrackEntity::getType).or()
                                .eq(FlightTrackEntity::getType,FlightPointTypeEnum.HAND.getType().intValue()).or()
                                .eq(FlightTrackEntity::getType,FlightPointTypeEnum.HOME.getType().intValue()))
                .orderByAsc(FlightTrackEntity::getCreateTime));
        if(!CollectionUtils.isEmpty(filghtPoints)) {
            //Home点
            Optional<FlightTrackEntity> homePointOpt = filghtPoints.stream().filter(fp ->FlightPointTypeEnum.HOME.getType().intValue() == fp.getType().intValue()).findFirst();
            FlightTrackEntity homePoint = null;
            if(homePointOpt.isPresent()) {
                homePoint = homePointOpt.get();
            } else {
                homePoint = FlightTrackEntity.builder().type(FlightPointTypeEnum.HOME.getType())
                        .latitude(filghtPoints.get(0).getLatitude())
                        .longitude(filghtPoints.get(0).getLongitude())
                        .createTime(filghtPoints.get(0).getCreateTime())
                        .build();
            }
            List<FlightTrackEntity> newFilghtTrackList = null;
            newFilghtTrackList = filghtPoints;
            newFilghtTrackList.add(0,homePoint);

            return newFilghtTrackList.stream().map(this::flightTrackEntityToDto).collect(Collectors.toList());
        }
        return null;
    }

    private FlightTrackEntity mediaDTOToFlightTrackDTO(Integer taskId, MediaFileEntity mediaFileEntity) {
        return FlightTrackEntity.builder().taskId(taskId)
                .longitude(mediaFileEntity.getLongitude().doubleValue())
                .latitude(mediaFileEntity.getLatitude().doubleValue())
                .altitude(mediaFileEntity.getAbsoluteAltitude().doubleValue())
                .createTime(mediaFileEntity.getPictureTime())
                .type(FlightPointTypeEnum.PICTURE.getType())
                .build();
    }


    @Override
    public Integer createFlightTask(FlightTaskDTO flightTaskDTO) {
        FlightTaskEntity flightTaskEntity = this.dtoToEntity(flightTaskDTO);
        return mapper.insert(flightTaskEntity);
    }

    @Override
    public Integer updateFlightTask(FlightTaskEntity flightTaskEntity) {
        return mapper.updateById(flightTaskEntity);
    }

    @Override
    public FlightTaskDTO createFlightTask(FlightTaskEntity flightTaskEntity) {
        FlightTaskEntity entity = mapper.selectOne(new LambdaQueryWrapper<FlightTaskEntity>()
                //.eq(FlightTaskEntity::getTaskName, flightTaskEntity.getTaskName())
                .eq(FlightTaskEntity::getDeviceSn, flightTaskEntity.getDeviceSn())
                .isNull(FlightTaskEntity::getEndTime)
                .eq(FlightTaskEntity::getWorkspaceId,flightTaskEntity.getWorkspaceId()));
        if(entity  == null) {
            int result = mapper.insert(flightTaskEntity);
            if (result > 0) {
                return entityToDto(flightTaskEntity);
            }
        }
        return entityToDto(entity);
    }

    @Override
    public FlightTaskEntity dtoToEntity(FlightTaskDTO flightTask) {
        FlightTaskEntity.FlightTaskEntityBuilder builder = FlightTaskEntity.builder();

        if (flightTask != null) {
            builder.workspaceId(flightTask.getWorkspaceId())
                    .id(flightTask.getId())
                    .taskName(flightTask.getTaskName())
                    .deviceSn(flightTask.getDeviceSn())
                    .deviceName(flightTask.getDeviceName())
                    .payload(flightTask.getPayload())
                    .waylineName(flightTask.getWaylineName())
                    .templateType(flightTask.getTemplateType())
                    .distance(flightTask.getDistance())
                    .flyTime(flightTask.getFlyTime())
                    .beginTime(flightTask.getBeginTime() != null ?
                            flightTask.getBeginTime().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() : null)
                    .endTime(flightTask.getEndTime() != null ?
                            flightTask.getEndTime().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() : null)
                    .createTime(flightTask.getCreateTime() != null ?
                            flightTask.getCreateTime().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() : null)
                    .updateTime(flightTask.getUpdateTime() != null ?
                            flightTask.getUpdateTime().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() : null)
                    .username(flightTask.getUsername());
        }
        return builder.build();
    }

    private FlightTrackEntity flightTrackDtoToEntity(FlightTrackDTO flightPoint) {
        FlightTrackEntity.FlightTrackEntityBuilder builder = FlightTrackEntity.builder();

        if (flightPoint != null) {
            builder.taskId(flightPoint.getTaskId())
                    .longitude(flightPoint.getLongitude())
                    .latitude(flightPoint.getLatitude())
                    .altitude(flightPoint.getAltitude())
                    .modeCode(flightPoint.getModeCode())
                    .elevation(flightPoint.getElevation())
                    .createTime(flightPoint.getCreateTime() != null ?
                            flightPoint.getCreateTime().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() : null);

        }
        return builder.build();
    }

    @Override
    public FlightTrackDTO flightTrackEntityToDto(FlightTrackEntity flightTrackEntity) {
        FlightTrackDTO.FlightTrackDTOBuilder builder = FlightTrackDTO.builder();
        if (flightTrackEntity != null) {
            builder.taskId(flightTrackEntity.getTaskId())
                    .longitude(flightTrackEntity.getLongitude())
                    .latitude(flightTrackEntity.getLatitude())
                    .altitude(flightTrackEntity.getAltitude())
                    .modeCode(flightTrackEntity.getModeCode())
                    .elevation(flightTrackEntity.getElevation())
                    .type(flightTrackEntity.getType())
                    .createTime(LocalDateTime.ofInstant(Instant.ofEpochMilli(flightTrackEntity.getCreateTime()), ZoneId.systemDefault()))
                    ;
        }
        return builder.build();
    }

    @Override
    public List<FlightTaskEntity> getFlightTrack(String deviceSn, String worksapceId) {
        List<FlightTaskEntity> result = new ArrayList<>();
        LambdaQueryWrapper<FlightTaskEntity> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(FlightTaskEntity ::getDeviceSn,deviceSn)
                .eq(FlightTaskEntity ::getWorkspaceId,worksapceId);
        result = mapper.selectList(wrapper);
        return result;
    }

    @Override
    public double getFlightKilometersByTaskId(Integer taskId) {
        double result = 0;
        List<FlightTrackEntity> trackResult = new ArrayList<>();
        LambdaQueryWrapper<FlightTrackEntity> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(FlightTrackEntity::getTaskId,taskId)
                .orderByDesc(FlightTrackEntity::getCreateTime);
        trackResult = trackMapper.selectList(wrapper);
        List<CoordinateUtil.Coordinate> coordinates = new ArrayList<>();
        for (int i = 0; i < trackResult.size(); i++) {
            FlightTrackEntity flightTrackStart = trackResult.get(i);
            coordinates.add(new CoordinateUtil.Coordinate(flightTrackStart.getLongitude(), flightTrackStart.getLatitude()));
        }
        if(coordinates.size() > 0) {
            result = calculateTotalDistance(coordinates);
        }
        FlightTaskEntity flightTaskEntity = new FlightTaskEntity();
        flightTaskEntity.setId(taskId);
        flightTaskEntity.setFlightKilometers(result);
        mapper.updateById(flightTaskEntity);
        return result;
    }

    private FlightTaskDTO entityToDto(FlightTaskEntity flightTaskEntity) {
        FlightTaskDTO.FlightTaskDTOBuilder builder = FlightTaskDTO.builder();
        if (flightTaskEntity != null) {
            builder.id(flightTaskEntity.getId())
                    .workspaceId(flightTaskEntity.getWorkspaceId())
                    .taskName(flightTaskEntity.getTaskName())
                    .deviceSn(flightTaskEntity.getDeviceSn())
                    .deviceName(flightTaskEntity.getDeviceName())
                    .payload(flightTaskEntity.getPayload())
                    .waylineName(flightTaskEntity.getWaylineName())
                    .distance(flightTaskEntity.getDistance())
                    .flyTime(flightTaskEntity.getFlyTime())
                    .beginTime(flightTaskEntity.getBeginTime() != null ? LocalDateTime.ofInstant(Instant.ofEpochMilli(flightTaskEntity.getBeginTime()), ZoneId.systemDefault()) : null)
                    .endTime(flightTaskEntity.getEndTime() != null ? LocalDateTime.ofInstant(Instant.ofEpochMilli(flightTaskEntity.getEndTime()), ZoneId.systemDefault()) : null)
                    .templateType( StringUtils.hasText(flightTaskEntity.getTemplateType()) ? WaylineTemplateTypeEnum.find(flightTaskEntity.getTemplateType()).get().getType() : "")
                    .createTime(LocalDateTime.ofInstant(Instant.ofEpochMilli(flightTaskEntity.getCreateTime()), ZoneId.systemDefault()))
                    .updateTime(flightTaskEntity.getUpdateTime() != null ? LocalDateTime.ofInstant(Instant.ofEpochMilli(flightTaskEntity.getUpdateTime()), ZoneId.systemDefault()) : null)
                    .username(flightTaskEntity.getUsername());
        }
        return builder.build();
    }



    private double calculateTotalDistance(List<CoordinateUtil.Coordinate> coordinates) {
        double totalDistance = 0.0;

        for (int i = 0; i < coordinates.size() - 1; i++) {
            CoordinateUtil.Coordinate start = coordinates.get(i);
            CoordinateUtil.Coordinate end = coordinates.get(i + 1);
            double distance = calculateDistance(start, end);
            totalDistance += distance;
        }

        return totalDistance;
    }

    private  double calculateDistance(CoordinateUtil.Coordinate start, CoordinateUtil.Coordinate end) {
        final int R = 6371; // 地球平均半径，单位为公里
        double lat1 = Math.toRadians(start.getLat());
        double lat2 = Math.toRadians(end.getLat());
        double deltaLat = Math.toRadians(end.getLat() - start.getLat());
        double deltaLon = Math.toRadians(end.getLng() - start.getLng());
        double a = Math.sin(deltaLat / 2) * Math.sin(deltaLat / 2)
                + Math.cos(lat1) * Math.cos(lat2) * Math.sin(deltaLon / 2) * Math.sin(deltaLon / 2);
        double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
        return R * c;
    }

}
