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

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.dji.sample.component.mqtt.model.EventsReceiver;
import com.dji.sample.component.redis.RedisConst;
import com.dji.sample.component.redis.RedisOpsUtils;
import com.dji.sample.manage.model.dto.DeviceDTO;
import com.dji.sample.manage.service.IDeviceRedisService;
import com.dji.sample.manage.service.IDeviceService;
import com.dji.sample.media.model.MediaFileCountDTO;
import com.dji.sample.media.service.IFileService;
import com.dji.sample.wayline.dao.IWaylineJobMapper;
import com.dji.sample.wayline.model.dto.TaskSceneStatisticDTO;
import com.dji.sample.wayline.model.dto.TaskStatisticDTO;
import com.dji.sample.wayline.model.dto.TaskStatusStatisticDTO;
import com.dji.sample.wayline.model.dto.WaylineJobDTO;
import com.dji.sample.wayline.model.entity.WaylineFileEntity;
import com.dji.sample.wayline.model.entity.WaylineJobEntity;
import com.dji.sample.wayline.model.enums.TaskSceneEnum;
import com.dji.sample.wayline.model.enums.WaylineJobStatusEnum;
import com.dji.sample.wayline.model.param.CreateInFlightJobParam;
import com.dji.sample.wayline.model.param.CreateJobParam;
import com.dji.sample.wayline.model.param.WaylineJobSearchParam;
import com.dji.sample.wayline.service.IWaylineFileService;
import com.dji.sample.wayline.service.IWaylineJobService;
import com.dji.sample.wayline.service.IWaylineRedisService;
import com.dji.sdk.cloudapi.device.DockModeCodeEnum;
import com.dji.sdk.cloudapi.device.DroneModeCodeEnum;
import com.dji.sdk.cloudapi.device.OsdDock;
import com.dji.sdk.cloudapi.device.OsdDockDrone;
import com.dji.sdk.cloudapi.wayline.*;
import com.dji.sdk.common.Pagination;
import com.dji.sdk.common.PaginationData;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
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.ObjectUtils;
import org.springframework.util.StringUtils;


import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.text.SimpleDateFormat;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.WeekFields;
import java.util.*;
import java.util.List;
import java.util.stream.Collectors;

import static com.dji.sample.common.util.SecurityUtils.aboveSysAdminRole;
import static com.dji.sample.common.util.SecurityUtils.getOrgId;

/**
 * @author sean
 * @version 1.1
 * @date 2022/6/1
 */
@Service
@Transactional
@Slf4j
public class WaylineJobServiceImpl extends ServiceImpl<IWaylineJobMapper, WaylineJobEntity> implements IWaylineJobService {

    @Autowired
    private IWaylineJobMapper mapper;

    @Autowired
    private IWaylineFileService waylineFileService;

    @Autowired
    private IDeviceService deviceService;

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private IFileService fileService;

    @Autowired
    private IDeviceRedisService deviceRedisService;

    @Autowired
    private IWaylineRedisService waylineRedisService;

    private Optional<WaylineJobDTO> insertWaylineJob(WaylineJobEntity jobEntity) {
        int id = mapper.insert(jobEntity);
        if (id <= 0) {
            return Optional.empty();
        }
        return Optional.ofNullable(this.entity2Dto(jobEntity));
    }

    @Override
    public Optional<WaylineJobDTO> createWaylineJob(CreateJobParam param, String workspaceId, String username, Long beginTime, Long endTime) {
        if (Objects.isNull(param)) {
            return Optional.empty();
        }
        // Immediate tasks, allocating time on the backend.
        LocalDateTime now = LocalDateTime.now();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss");
        String formattedDateTime = now.format(formatter);
        String jobId = param.getName() + "-" + formattedDateTime+ "-" + UUID.randomUUID().toString().substring(0, 4);

        WaylineJobEntity jobEntity = WaylineJobEntity.builder()
                .name(param.getName())
                .dockSn(param.getDockSn())
                .fileId(param.getFileId())
                .username(username)
                .workspaceId(workspaceId)
                .orgId(StringUtils.hasText(param.getOrgId()) ? param.getOrgId() : getOrgId())
                .jobId(jobId)
                .beginTime(beginTime)
                .endTime(endTime)
                .status(WaylineJobStatusEnum.PENDING.getVal())
                .taskType(param.getTaskType().getType())
                .waylineType(param.getWaylineType().getValue())
                .outOfControlAction(param.getOutOfControlAction().getAction())
                .rthAltitude(param.getRthAltitude())
                .mediaCount(0)
                .build();

        // 断点续飞存redis
        if (param.getBreakPointResumeFlight() != null && param.getBreakPointResumeFlight() == 1) {
            waylineRedisService.setBreakPointWaylineJobDockSn(jobId, param.getDockSn());
        }

        return insertWaylineJob(jobEntity);
    }

    @Override
    public Optional<WaylineJobDTO> createWaylineJobByParent(String workspaceId, String parentId) {
        Optional<WaylineJobDTO> parentJobOpt = this.getJobByJobId(workspaceId, parentId);
        if (parentJobOpt.isEmpty()) {
            return Optional.empty();
        }
        WaylineJobEntity jobEntity = this.dto2Entity(parentJobOpt.get());
        LocalDateTime now = LocalDateTime.now();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss");
        String formattedDateTime = now.format(formatter);
        jobEntity.setJobId(jobEntity.getName() + "-" + formattedDateTime+ "-" + UUID.randomUUID().toString().substring(0, 4));
        jobEntity.setErrorCode(null);
        jobEntity.setCompletedTime(null);
        jobEntity.setExecuteTime(null);
        jobEntity.setStatus(WaylineJobStatusEnum.PENDING.getVal());
        jobEntity.setParentId(parentId);

        return this.insertWaylineJob(jobEntity);
    }

    public String spliceJobId(String jobName) {

        LocalDateTime now = LocalDateTime.now();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss");
        String formattedDateTime = now.format(formatter);

        return jobName.length() > 20 ? jobName.substring(0, 20) : jobName + "-" + formattedDateTime+ "-" + UUID.randomUUID().toString().substring(0, 4);
    }

    @Override
    public List<WaylineJobDTO> getJobsByConditions(String workspaceId, Collection<String> jobIds, WaylineJobStatusEnum status) {
        return mapper.selectList(
                new LambdaQueryWrapper<WaylineJobEntity>()
                        .eq(WaylineJobEntity::getWorkspaceId, workspaceId)
                        .eq(Objects.nonNull(status), WaylineJobEntity::getStatus, status.getVal())
                        .and(!CollectionUtils.isEmpty(jobIds),
                                wrapper -> jobIds.forEach(id -> wrapper.eq(WaylineJobEntity::getJobId, id).or())))
                .stream()
                .map(this::entity2Dto)
                .collect(Collectors.toList());
    }

    @Override
    public Optional<WaylineJobDTO> getJobByJobId(String workspaceId, String jobId) {
        WaylineJobEntity jobEntity = mapper.selectOne(
                new LambdaQueryWrapper<WaylineJobEntity>()
                        .eq(WaylineJobEntity::getWorkspaceId, workspaceId)
                        .eq(WaylineJobEntity::getJobId, jobId));
        return Optional.ofNullable(entity2Dto(jobEntity));
    }

    @Override
    public WaylineJobEntity getJobEntityByJobId(String jobId) {
        WaylineJobEntity jobEntity = mapper.selectOne(
                new LambdaQueryWrapper<WaylineJobEntity>()
                        .eq(WaylineJobEntity::getJobId, jobId));
        return jobEntity;
    }

    @Override
    public Boolean updateJob(WaylineJobDTO dto) {
        return mapper.update(this.dto2Entity(dto),
                new LambdaUpdateWrapper<WaylineJobEntity>()
                        .eq(WaylineJobEntity::getJobId, dto.getJobId())) > 0;
    }

    @Override
    public PaginationData<WaylineJobDTO> getJobsByWorkspaceId(String workspaceId, long page, long pageSize) {
        return getJobsByWorkspaceId(workspaceId, null, page, pageSize);
    }

    @Override
    public PaginationData<WaylineJobDTO> getJobsByWorkspaceId(String workspaceId, String dockSns, long page, long pageSize) {
        LambdaQueryWrapper<WaylineJobEntity> waylineJobQueryWrapper = new LambdaQueryWrapper<WaylineJobEntity>()
                .eq(WaylineJobEntity::getWorkspaceId, workspaceId)
                .orderByDesc(WaylineJobEntity::getId);
        if (StringUtils.hasText(dockSns)) {
            List<String> dockSnList = Arrays.asList(dockSns.split(","));
            if (!CollectionUtils.isEmpty(dockSnList)) {
                waylineJobQueryWrapper.in(WaylineJobEntity::getDockSn, dockSnList);
            }
        }
        Page<WaylineJobEntity> pageData = mapper.selectPage(new Page<WaylineJobEntity>(page, pageSize), waylineJobQueryWrapper);
        List<WaylineJobDTO> records = pageData.getRecords()
                .stream()
                .map(this::entity2Dto)
                .collect(Collectors.toList());

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

    @Override
    public PaginationData<WaylineJobDTO> getJobsByParam(WaylineJobSearchParam param, String workspaceId, long page, long pageSize) {
        LambdaQueryWrapper<WaylineJobEntity> waylineJobQueryWrapper = new LambdaQueryWrapper<>();

        if (StringUtils.hasText(param.getOrgId())) {
            waylineJobQueryWrapper.eq(WaylineJobEntity::getOrgId, param.getOrgId());
        }
        if (aboveSysAdminRole()) {

        } else {
            waylineJobQueryWrapper.eq(WaylineJobEntity::getOrgId, getOrgId());
        }

        waylineJobQueryWrapper.eq(WaylineJobEntity::getWorkspaceId, workspaceId);
        waylineJobQueryWrapper.orderByDesc(WaylineJobEntity::getId);

        if (StringUtils.hasText(param.getDockSns())) {
            List<String> dockSnList = Arrays.asList(param.getDockSns().split(","));
            if (!CollectionUtils.isEmpty(dockSnList)) {
                waylineJobQueryWrapper.in(WaylineJobEntity::getDockSn, dockSnList);
            }
        }
        // 航线名或者任务名称
        if (StringUtils.hasText(param.getTaskOrWaylineName())) {

            // 查询航线名称
            LambdaQueryWrapper<WaylineFileEntity> waylineFileEntityLambdaQueryWrapper = new LambdaQueryWrapper<>();
            waylineFileEntityLambdaQueryWrapper.like(WaylineFileEntity::getName, param.getTaskOrWaylineName());

            List<WaylineFileEntity> list = waylineFileService.list(waylineFileEntityLambdaQueryWrapper);
            List<String> waylineFileIds = list.stream().map(WaylineFileEntity::getWaylineId).distinct().collect(Collectors.toList());

            if (CollectionUtils.isEmpty(waylineFileIds)) {
                waylineJobQueryWrapper.like(WaylineJobEntity::getName, param.getTaskOrWaylineName());
            } else {
                waylineJobQueryWrapper.and(wrapper -> wrapper.like(WaylineJobEntity::getName, param.getTaskOrWaylineName())
                        .or().in(WaylineJobEntity::getFileId, waylineFileIds));
            }
        }
        // 时间段 -- getBeginTime
        if (param.getStartDate() != null) {
            waylineJobQueryWrapper.ge(WaylineJobEntity::getBeginTime, param.getStartDate());
        }
        if (param.getEndDate() != null) {
            waylineJobQueryWrapper.le(WaylineJobEntity::getBeginTime, param.getEndDate());
        }

        // jobid
        if (StringUtils.hasText(param.getJobIds())) {
            List<String> jobIdList = Arrays.asList(param.getJobIds().split(","));
            if (!CollectionUtils.isEmpty(jobIdList)) {
                waylineJobQueryWrapper.in(WaylineJobEntity::getJobId, jobIdList);
            }
        }

        Page<WaylineJobEntity> pageData = mapper.selectPage(new Page<>(page, pageSize), waylineJobQueryWrapper);
        List<WaylineJobDTO> records = pageData.getRecords()
                .stream()
                .map(this::entity2Dto)
                .collect(Collectors.toList());

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


    @Override
    public PaginationData<WaylineJobDTO> getTodayJobsByWorkspaceId(String workspaceId, String dockSns, long page, long pageSize) {
        LambdaQueryWrapper<WaylineJobEntity> waylineJobQueryWrapper = new LambdaQueryWrapper<>();
        if (aboveSysAdminRole()) {

        } else {
            waylineJobQueryWrapper.eq(WaylineJobEntity::getOrgId, getOrgId());
        }
        waylineJobQueryWrapper.eq(WaylineJobEntity::getWorkspaceId, workspaceId)
                .orderByDesc(WaylineJobEntity::getId);
        if (StringUtils.hasText(dockSns)) {
            List<String> dockSnList = Arrays.asList(dockSns.split(","));
            if (!CollectionUtils.isEmpty(dockSnList)) {
                waylineJobQueryWrapper.in(WaylineJobEntity::getDockSn, dockSnList);
            }
        }

        LocalDate today = LocalDate.now();
        LocalDateTime startOfDay = today.atStartOfDay();
        long startTimestamp = startOfDay.toEpochSecond(ZoneOffset.UTC);
        startTimestamp = startTimestamp*1000;
        waylineJobQueryWrapper.ge(WaylineJobEntity::getBeginTime, startTimestamp);
        // 今天结束时间：23:59:59
        LocalDateTime endOfDay = today.atTime(23, 59, 59);
        long endTimestamp = endOfDay.toEpochSecond(ZoneOffset.UTC);
        endTimestamp = endTimestamp*1000;
        waylineJobQueryWrapper.le(WaylineJobEntity::getBeginTime, endTimestamp);

        Page<WaylineJobEntity> pageData = mapper.selectPage(new Page<>(page, pageSize), waylineJobQueryWrapper);
        List<WaylineJobDTO> records = pageData.getRecords()
                .stream()
                .map(this::entity2Dto)
                .collect(Collectors.toList());

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


    private WaylineJobEntity dto2Entity(WaylineJobDTO dto) {
        WaylineJobEntity.WaylineJobEntityBuilder builder = WaylineJobEntity.builder();
        if (dto == null) {
            return builder.build();
        }
        if (Objects.nonNull(dto.getBeginTime())) {
            builder.beginTime(dto.getBeginTime().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
        }
        if (Objects.nonNull(dto.getEndTime())) {
            builder.endTime(dto.getEndTime().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
        }
        if (Objects.nonNull(dto.getExecuteTime())) {
            builder.executeTime(dto.getExecuteTime().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
        }
        if (Objects.nonNull(dto.getCompletedTime())) {
            builder.completedTime(dto.getCompletedTime().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
        }
        return builder.status(dto.getStatus())
                .mediaCount(dto.getMediaCount())
                .name(dto.getJobName())
                .errorCode(dto.getCode())
                .jobId(dto.getJobId())
                .fileId(dto.getFileId())
                .dockSn(dto.getDockSn())
                .workspaceId(dto.getWorkspaceId())
                .orgId(dto.getOrgId())
                .taskType(Optional.ofNullable(dto.getTaskType()).map(TaskTypeEnum::getType).orElse(null))
                .taskScene(Optional.ofNullable(dto.getTaskScene()).map(TaskSceneEnum::getScene).orElse(null))
                .waylineType(Optional.ofNullable(dto.getWaylineType()).map(WaylineTypeEnum::getValue).orElse(null))
                .username(dto.getUsername())
                .rthAltitude(dto.getRthAltitude())
                .outOfControlAction(Optional.ofNullable(dto.getOutOfControlAction())
                        .map(OutOfControlActionEnum::getAction).orElse(null))
                .parentId(dto.getParentId())
                .build();
    }

    public WaylineJobStatusEnum getWaylineState(String dockSn) {
        Optional<DeviceDTO> dockOpt = deviceRedisService.getDeviceOnline(dockSn);
        if (dockOpt.isEmpty() || !StringUtils.hasText(dockOpt.get().getChildDeviceSn())) {
            return WaylineJobStatusEnum.UNKNOWN;
        }
        Optional<OsdDock> dockOsdOpt = deviceRedisService.getDeviceOsd(dockSn, OsdDock.class);
        Optional<OsdDockDrone> deviceOsdOpt = deviceRedisService.getDeviceOsd(dockOpt.get().getChildDeviceSn(), OsdDockDrone.class);
        if (dockOsdOpt.isEmpty() || deviceOsdOpt.isEmpty() || DockModeCodeEnum.WORKING != dockOsdOpt.get().getModeCode()) {
            return WaylineJobStatusEnum.UNKNOWN;
        }

        OsdDockDrone osdDevice = deviceOsdOpt.get();
        if (DroneModeCodeEnum.WAYLINE == osdDevice.getModeCode()
                || DroneModeCodeEnum.MANUAL == osdDevice.getModeCode()
                || DroneModeCodeEnum.TAKEOFF_AUTO == osdDevice.getModeCode()) {
            if (StringUtils.hasText(waylineRedisService.getPausedWaylineJobId(dockSn))) {
                return WaylineJobStatusEnum.PAUSED;
            }
            if (waylineRedisService.getRunningWaylineJob(dockSn).isPresent()) {
                return WaylineJobStatusEnum.IN_PROGRESS;
            }
        }
        return WaylineJobStatusEnum.UNKNOWN;
    }

    private WaylineJobDTO entity2Dto(WaylineJobEntity entity) {
        if (entity == null) {
            return null;
        }

        WaylineJobDTO.WaylineJobDTOBuilder builder = WaylineJobDTO.builder()
                .jobId(entity.getJobId())
                .jobName(entity.getName())
                .fileId(entity.getFileId())
                .fileName(waylineFileService.getWaylineByWaylineId(entity.getWorkspaceId(), entity.getFileId())
                        .orElse(new GetWaylineListResponse()).getName())
                .dockSn(entity.getDockSn())
                .dockName(deviceService.getDeviceBySn(entity.getDockSn())
                        .orElse(DeviceDTO.builder().build()).getNickname())
                .username(entity.getUsername())
                .workspaceId(entity.getWorkspaceId())
                .orgId(entity.getOrgId())
                .status(WaylineJobStatusEnum.IN_PROGRESS.getVal() == entity.getStatus() &&
                        entity.getJobId().equals(waylineRedisService.getPausedWaylineJobId(entity.getDockSn())) ?
                                WaylineJobStatusEnum.PAUSED.getVal() : entity.getStatus())
                .code(entity.getErrorCode())
                .beginTime(Objects.nonNull(entity.getBeginTime()) ?
                        LocalDateTime.ofInstant(Instant.ofEpochMilli(entity.getBeginTime()), ZoneId.systemDefault()) : null)
                .endTime(Objects.nonNull(entity.getEndTime()) ?
                        LocalDateTime.ofInstant(Instant.ofEpochMilli(entity.getEndTime()), ZoneId.systemDefault()) : null)
                .executeTime(Objects.nonNull(entity.getExecuteTime()) ?
                        LocalDateTime.ofInstant(Instant.ofEpochMilli(entity.getExecuteTime()), ZoneId.systemDefault()) : null)
                .completedTime(WaylineJobStatusEnum.find(entity.getStatus()).getEnd() ?
                        LocalDateTime.ofInstant(Instant.ofEpochMilli(entity.getUpdateTime()), ZoneId.systemDefault()) : null)
                .taskType(Objects.nonNull(entity.getTaskType()) ? TaskTypeEnum.find(entity.getTaskType()) : null)
                .taskScene(Objects.nonNull(entity.getTaskScene()) ? TaskSceneEnum.find(entity.getTaskScene()) : null)
                .waylineType(WaylineTypeEnum.find(entity.getWaylineType()))
                .rthAltitude(entity.getRthAltitude())
                .outOfControlAction(OutOfControlActionEnum.find(entity.getOutOfControlAction()))
                .mediaCount(entity.getMediaCount());

        if (Objects.nonNull(entity.getEndTime())) {
            builder.endTime(LocalDateTime.ofInstant(Instant.ofEpochMilli(entity.getEndTime()), ZoneId.systemDefault()));
        }
        if (WaylineJobStatusEnum.IN_PROGRESS.getVal() == entity.getStatus()) {
            builder.progress(waylineRedisService.getRunningWaylineJob(entity.getDockSn())
                    .map(EventsReceiver::getOutput)
                    .map(FlighttaskProgress::getProgress)
                    .map(FlighttaskProgressData::getPercent)
                    .orElse(null));
        }

        if (entity.getMediaCount() == 0) {
            return builder.build();
        }

        // sync the number of media files
        String key = RedisConst.MEDIA_HIGHEST_PRIORITY_PREFIX + entity.getDockSn();
        String countKey = RedisConst.MEDIA_FILE_PREFIX + entity.getDockSn();
        Object mediaFileCount = RedisOpsUtils.hashGet(countKey, entity.getJobId());
        if (Objects.nonNull(mediaFileCount)) {
            builder.uploadedCount(((MediaFileCountDTO) mediaFileCount).getUploadedCount())
                    .uploading(RedisOpsUtils.checkExist(key) && entity.getJobId().equals(((MediaFileCountDTO)RedisOpsUtils.get(key)).getJobId()));
            return builder.build();
        }

        int uploadedSize = fileService.getFilesByWorkspaceAndJobId(entity.getWorkspaceId(), entity.getJobId()).size();
        // All media for this job have been uploaded.
        if (uploadedSize >= entity.getMediaCount()) {
            return builder.uploadedCount(uploadedSize).build();
        }
        RedisOpsUtils.hashSet(countKey, entity.getJobId(),
                MediaFileCountDTO.builder()
                        .jobId(entity.getJobId())
                        .mediaCount(entity.getMediaCount())
                        .uploadedCount(uploadedSize).build());
        return builder.build();
    }

    /**
     * 统计任务数据
     * @param workspaceId
     * @param timeType 0 周 1 月 2 年
     */
    @Override
    public List<TaskStatisticDTO> getTaskStatistics(String workspaceId, Integer timeType) {

        if (timeType == null) {
            timeType = 0;
        }
        // 当前时间
        ZoneId zoneId = ZoneId.systemDefault();
        LocalDate today = LocalDate.now();
        DateTimeFormatter dayFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");

        long startSecond = 0L;
        long endSecond = 0L;

        // ===== 12日 =====
        if (timeType == 0) {
            // 6天前 0点
            startSecond = today.minusDays(6).atStartOfDay(zoneId).toEpochSecond();
            // 5天后 23:59:59
            endSecond = today.plusDays(5).atTime(LocalTime.MAX.withNano(0)).atZone(zoneId).toEpochSecond(); // 秒级时间戳;
        }
        // ===== 12周 =====
        if (timeType == 1) {
            // 使用系统默认地区的周定义（通常是周一为第一天）
            WeekFields weekFields = WeekFields.of(Locale.getDefault());
            // 获取本周的周一
            LocalDate currentWeekStart = today.with(weekFields.dayOfWeek(), 1);
            // 最小日期：6周前的周一
            LocalDate minDate = currentWeekStart.minusWeeks(6);
            // 最大日期：5周后的周日
            LocalDate maxDate = currentWeekStart.plusWeeks(5).with(weekFields.dayOfWeek(), 7);

            // 转为时间戳（秒）
            startSecond = minDate.atStartOfDay().toEpochSecond(ZoneOffset.UTC);
            endSecond = maxDate.atTime(23, 59, 59).toEpochSecond(ZoneOffset.UTC);
        }
        // ===== 12月 =====
        if (timeType == 2) {
            // 获取当前年份
            int year = Year.now().getValue();

            // 当年最小日期时间：2025-01-01 00:00:00
            LocalDateTime startOfYear = LocalDateTime.of(year, 1, 1, 0, 0, 0);
            startSecond = startOfYear.toEpochSecond(ZoneOffset.UTC);
            // 当年最大日期时间：2025-12-31 23:59:59
            LocalDateTime endOfYear = LocalDateTime.of(year, 12, 31, 23, 59, 59);
            endSecond = endOfYear.toEpochSecond(ZoneOffset.UTC);
        }

        //  查询任务
        LambdaQueryWrapper<WaylineJobEntity> waylineJobQueryWrapper = new LambdaQueryWrapper<>();
        startSecond = startSecond*1000;
        endSecond = endSecond*1000;
        waylineJobQueryWrapper.between(WaylineJobEntity::getBeginTime, startSecond, endSecond);
        waylineJobQueryWrapper.eq(WaylineJobEntity::getWorkspaceId, workspaceId);
        if (aboveSysAdminRole()) {

        } else {
            waylineJobQueryWrapper.eq(WaylineJobEntity::getOrgId, getOrgId());
        }

        List<WaylineJobEntity> waylineJobList = this.mapper.selectList(waylineJobQueryWrapper);

        // 对数据进行分析
        if (timeType == 0) {


            Map<String, List<WaylineJobEntity>> dailyGrouped = waylineJobList.stream()
                    .collect(Collectors.groupingBy(
                            item -> {
                                Instant instant = Instant.ofEpochMilli(item.getBeginTime());
                                return instant.atZone(zoneId).toLocalDate().format(dayFormatter);
                            },
                            TreeMap::new, // 指定使用 TreeMap 自动按 LocalDate 排序
                            Collectors.toList()
                    ));

            List<String> dateList = new ArrayList<>();
            // 前6天（不含今天）→ 今天往前推6天 到 今天
            for (int i = -6; i <= 0; i++) {
                dateList.add(today.plusDays(i).format(dayFormatter));
            }
            // 后5天（不含今天）
            for (int i = 1; i <= 5; i++) {
                dateList.add(today.plusDays(i).format(dayFormatter));
            }

            // 遍历 dateList
            List<TaskStatisticDTO> newStatisticData = new ArrayList<>();
            for (String dayStr : dateList) {
                List<WaylineJobEntity> curDayTaskList = dailyGrouped.getOrDefault(dayStr, new ArrayList<>());
                TaskStatisticDTO taskStatisticDTO = partitionTaskData(curDayTaskList);
                taskStatisticDTO.setDate(dayStr);

                newStatisticData.add(taskStatisticDTO);
            }
            return newStatisticData;
        }
        if (timeType == 1) {
            // 按“周”分组
            // 输出每周的分组
            // String[] weekLabels = {"前六周", "前五周", "前四周", "前三周", "前两周", "前一周", "本周", "后一周", "后两周", "后三周", "后四周", "后五周"};
            String[] weekLabels = {"6 weeks ago", "5 weeks ago", "4 weeks ago", "3 weeks ago", "2 weeks ago", "1 weeks ago", "this week", "1 week later", "2 week later", "3 week later", "4 week later", "5 week later"};

            // 获取当前日期和周的起始日期（周一）
            WeekFields weekFields = WeekFields.of(Locale.getDefault());  // 获取当前地区的周规则
            LocalDate startOfWeek = today.with(weekFields.dayOfWeek(), 1); // 获取当前周的周一

            List<List<WaylineJobEntity>> allWeeks = new ArrayList<>(12);
            for (int i = 0; i < 12; i++) {
                allWeeks.add(new ArrayList<>());
            }

            // 按周填充数据
            for (WaylineJobEntity waylineJobEntity : waylineJobList) {
                LocalDate curDate = Instant.ofEpochMilli(waylineJobEntity.getBeginTime()).atZone(zoneId).toLocalDate();

                // 获取每个日期所在的周的起始日期（周一）
                LocalDate startOfCurrentWeek = curDate.with(weekFields.dayOfWeek(), 1);
                // 计算这个周属于第几组，负数为前面几周，正数为后面几周
                int weekIndex = (int) startOfWeek.until(startOfCurrentWeek, java.time.temporal.ChronoUnit.WEEKS);
                // 确保index在-6到+5之间（即12个周）
                if (weekIndex >= -6 && weekIndex <= 5) {
                    // 将日期添加到相应的周组
                    allWeeks.get(weekIndex + 6).add(waylineJobEntity);
                }
            }
            List<TaskStatisticDTO> newStatisticData = new ArrayList<>();
            for (int i = 0; i <= weekLabels.length - 1; i++) {
                TaskStatisticDTO taskStatisticDTO = partitionTaskData(allWeeks.get(i));
                taskStatisticDTO.setDate(weekLabels[i]);

                newStatisticData.add(taskStatisticDTO);
            }

            return newStatisticData;
        }
        if (timeType == 2) {
            // 获取当前年份（可以指定为某一年）
            int targetYear = Year.now().getValue(); // 如果要固定写死，比如 2025 就直接 int targetYear = 2025;
            // 日期格式化器：yyyy-MM
            DateTimeFormatter ymFormatter = DateTimeFormatter.ofPattern("yyyy-MM");
            // 初始化 Map（按当前年 1~12 月）
            Map<String, List<WaylineJobEntity>> monthMap = new LinkedHashMap<>();
            for (int month = 1; month <= 12; month++) {
                LocalDate firstDayOfMonth = LocalDate.of(targetYear, month, 1);
                String key = firstDayOfMonth.format(ymFormatter);
                monthMap.put(key, new ArrayList<>());
            }

            // 数据填充（只放入当年的日期）
            for (WaylineJobEntity waylineJobEntity : waylineJobList) {
                LocalDate curDate = Instant.ofEpochMilli(waylineJobEntity.getBeginTime()).atZone(zoneId).toLocalDate();
                if (monthMap.containsKey(curDate.format(ymFormatter))) {
                    monthMap.get(curDate.format(ymFormatter)).add(waylineJobEntity);
                }
            }

            // 遍历数据
            List<TaskStatisticDTO> newStatisticData = new ArrayList<>();
            for (Map.Entry<String, List<WaylineJobEntity>> entry : monthMap.entrySet()) {
                TaskStatisticDTO taskStatisticDTO = partitionTaskData(entry.getValue());
                taskStatisticDTO.setDate(entry.getKey());

                newStatisticData.add(taskStatisticDTO);
            }
            return newStatisticData;
        }

        return new ArrayList<>();
    }

    // 区分数据
    private TaskStatisticDTO partitionTaskData(List<WaylineJobEntity> taskList) {

        TaskStatisticDTO taskStatisticDTO = new TaskStatisticDTO();
        // 初始化
        taskStatisticDTO.setCount(taskList.size());

        // 任务状态 已完成 未完成
        int finishCount = 0;
        int unfinishCount = 0;
        Map<Integer, List<WaylineJobEntity>> taskMapByTaskStatus = taskList.stream().filter(x -> !ObjectUtils.isEmpty(x.getStatus())).collect(Collectors.groupingBy(WaylineJobEntity::getStatus));
        List<TaskStatusStatisticDTO> statusList = new ArrayList<>();
        // 遍历任务状态
        for (WaylineJobStatusEnum waylineJobStatusEnum : WaylineJobStatusEnum.values()) {

            TaskStatusStatisticDTO tempStatusDTO = new TaskStatusStatisticDTO();
            tempStatusDTO.setTaskStatus(waylineJobStatusEnum.getVal());
            tempStatusDTO.setTaskStatusName(waylineJobStatusEnum.name());

            List<WaylineJobEntity> curStatusTaskList = taskMapByTaskStatus.getOrDefault(waylineJobStatusEnum.getVal(), new ArrayList<>());
            tempStatusDTO.setCount(curStatusTaskList.size());

            boolean statusEnd = waylineJobStatusEnum.getEnd();
            if (statusEnd) {
                finishCount += curStatusTaskList.size();
            } else {
                unfinishCount += curStatusTaskList.size();
            }

            statusList.add(tempStatusDTO);
        }
        taskStatisticDTO.setFinishCount(finishCount);
        taskStatisticDTO.setUnfinishCount(unfinishCount);
        taskStatisticDTO.setStatusList(statusList);


        Map<Integer, List<WaylineJobEntity>> taskMapByTaskScene = taskList.stream().filter(x -> !ObjectUtils.isEmpty(x.getTaskScene())).collect(Collectors.groupingBy(WaylineJobEntity::getTaskScene));
        List<TaskSceneStatisticDTO> sceneList = new ArrayList<>();
        // 任务起飞类型
        for (TaskSceneEnum taskSceneEnum : TaskSceneEnum.values()) {
            TaskSceneStatisticDTO sceneDTO = new TaskSceneStatisticDTO();
            sceneDTO.setTaskScene(taskSceneEnum.getScene());
            sceneDTO.setTaskSceneName(taskSceneEnum.getSceneName());

            List<WaylineJobEntity> curSceneTaskList = taskMapByTaskScene.getOrDefault(taskSceneEnum.getScene(), new ArrayList<>());

            sceneDTO.setCount(curSceneTaskList.size());

            sceneList.add(sceneDTO);
        }
        taskStatisticDTO.setSceneList(sceneList);

        return taskStatisticDTO;
    }


    @SneakyThrows
    @Override
    public void exportTaskList(HttpServletResponse response, String workspaceId)  {

        // 模拟数据（根据传入参数过滤）
        LambdaQueryWrapper<WaylineJobEntity> waylineJobQueryWrapper = new LambdaQueryWrapper<>();
        waylineJobQueryWrapper.eq(WaylineJobEntity::getWorkspaceId, workspaceId);
        if (aboveSysAdminRole()) {

        } else {
            waylineJobQueryWrapper.eq(WaylineJobEntity::getOrgId, getOrgId());
        }
        List<WaylineJobEntity> list = this.list();

        // 创建 Excel
        XSSFWorkbook workbook = new XSSFWorkbook();
        Sheet sheet = workbook.createSheet("task");

        Row header = sheet.createRow(0);
        header.createCell(0).setCellValue("task_name");
        header.createCell(1).setCellValue("dock_sn");
        header.createCell(2).setCellValue("username");
        header.createCell(3).setCellValue("create_time");
        header.createCell(4).setCellValue("begin_time");
        header.createCell(5).setCellValue("completed_time");
        // header.createCell(6).setCellValue("task_scene");

        // 使用format方法将时间戳转换为字符串
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        int rowIndex = 1;
        for (WaylineJobEntity waylineJobEntity : list) {
            Row row = sheet.createRow(rowIndex);
            if (StringUtils.hasText(waylineJobEntity.getName())) {
                row.createCell(0).setCellValue(waylineJobEntity.getName());
            }
            if (StringUtils.hasText(waylineJobEntity.getDockSn())) {
                row.createCell(1).setCellValue(waylineJobEntity.getDockSn());
            }
            if (StringUtils.hasText(waylineJobEntity.getUsername())) {
                row.createCell(2).setCellValue(waylineJobEntity.getUsername());
            }
            if (waylineJobEntity.getCreateTime() != null) {
                row.createCell(3).setCellValue(dateFormat.format(new Date(waylineJobEntity.getCreateTime())));
            }
            if (waylineJobEntity.getBeginTime() != null) {
                row.createCell(4).setCellValue(dateFormat.format(new Date(waylineJobEntity.getBeginTime())));
            }
            if (waylineJobEntity.getCompletedTime() != null) {
                row.createCell(5).setCellValue(dateFormat.format(new Date(waylineJobEntity.getCompletedTime())));
            }
            // row.createCell(6).setCellValue(waylineJobEntity.getTaskScene());
            rowIndex++;
        }

        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setHeader("Content-Disposition", "attachment; filename=users.xlsx");

        ServletOutputStream out = response.getOutputStream();
        workbook.write(out);
        out.flush();
        out.close();
        workbook.close();

    }

    @Override
    public Optional<WaylineJobDTO> createInFlightWaylineJob(CreateInFlightJobParam param, String workspaceId, String username, Long beginTime, Long endTime) {
        if (Objects.isNull(param)) {
            return Optional.empty();
        }
        // Immediate tasks, allocating time on the backend.
        String jobId = spliceJobId(param.getName());

        WaylineJobEntity jobEntity = WaylineJobEntity.builder()
                .name(param.getName())
                .dockSn(param.getDockSn())
                .fileId(param.getFileId())
                .waylineType(param.getWaylineType().getValue())
                .username(username)
                .workspaceId(workspaceId)
                .orgId(StringUtils.hasText(param.getOrgId()) ? param.getOrgId() : getOrgId())
                .jobId(jobId)
                .beginTime(beginTime)
                .endTime(endTime)
                .status(WaylineJobStatusEnum.PENDING.getVal())
                .taskType(param.getTaskType().getType())
                .outOfControlAction(param.getOutOfControlAction().getAction())
                .rthAltitude(param.getRthAltitude())
                .mediaCount(0)
                .build();

        return insertInFlightWaylineJob(jobEntity);
    }

    private Optional<WaylineJobDTO> insertInFlightWaylineJob(WaylineJobEntity jobEntity) {
        int id = mapper.insert(jobEntity);
        if (id <= 0) {
            return Optional.empty();
        }
        return Optional.ofNullable(this.inFlightentity2Dto(jobEntity));
    }
    private WaylineJobDTO inFlightentity2Dto(WaylineJobEntity entity) {
        if (entity == null) {
            return null;
        }

        WaylineJobDTO.WaylineJobDTOBuilder builder = WaylineJobDTO.builder()
                .jobId(entity.getJobId())
                .jobName(entity.getName())
                .fileId(entity.getFileId())
                .fileName(waylineFileService.getWaylineByWaylineId(entity.getWorkspaceId(), entity.getFileId())
                        .orElse(new GetWaylineListResponse()).getName())
                .dockSn(entity.getDockSn())
                .dockName(deviceService.getDeviceBySn(entity.getDockSn())
                        .orElse(DeviceDTO.builder().build()).getNickname())
                .username(entity.getUsername())
                .workspaceId(entity.getWorkspaceId())
                .orgId(entity.getOrgId())
                .status(WaylineJobStatusEnum.IN_PROGRESS.getVal() == entity.getStatus() &&
                        entity.getJobId().equals(waylineRedisService.getPausedWaylineJobId(entity.getDockSn())) ?
                        WaylineJobStatusEnum.PAUSED.getVal() : entity.getStatus())
                .code(entity.getErrorCode())
                .beginTime(Objects.nonNull(entity.getBeginTime()) ?
                        LocalDateTime.ofInstant(Instant.ofEpochMilli(entity.getBeginTime()), ZoneId.systemDefault()) : null)
                .endTime(Objects.nonNull(entity.getEndTime()) ?
                        LocalDateTime.ofInstant(Instant.ofEpochMilli(entity.getEndTime()), ZoneId.systemDefault()) : null)
                .executeTime(Objects.nonNull(entity.getExecuteTime()) ?
                        LocalDateTime.ofInstant(Instant.ofEpochMilli(entity.getExecuteTime()), ZoneId.systemDefault()) : null)
                .completedTime(WaylineJobStatusEnum.find(entity.getStatus()).getEnd() ?
                        LocalDateTime.ofInstant(Instant.ofEpochMilli(entity.getUpdateTime()), ZoneId.systemDefault()) : null)
                .taskType(Objects.nonNull(entity.getTaskType()) ? TaskTypeEnum.find(entity.getTaskType()) : null)
                .taskScene(Objects.nonNull(entity.getTaskScene()) ? TaskSceneEnum.find(entity.getTaskScene()) : null)
                .waylineType(WaylineTypeEnum.find(entity.getWaylineType()))
                .rthAltitude(entity.getRthAltitude())
                .outOfControlAction(OutOfControlActionEnum.find(entity.getOutOfControlAction()))
                .mediaCount(entity.getMediaCount());

        if (Objects.nonNull(entity.getEndTime())) {
            builder.endTime(LocalDateTime.ofInstant(Instant.ofEpochMilli(entity.getEndTime()), ZoneId.systemDefault()));
        }
        if (WaylineJobStatusEnum.IN_PROGRESS.getVal() == entity.getStatus()) {
            builder.progress(waylineRedisService.getRunningWaylineJob(entity.getDockSn())
                    .map(EventsReceiver::getOutput)
                    .map(FlighttaskProgress::getProgress)
                    .map(FlighttaskProgressData::getPercent)
                    .orElse(null));
        }

        if (entity.getMediaCount() == 0) {
            return builder.build();
        }

        // sync the number of media files
        String key = RedisConst.MEDIA_HIGHEST_PRIORITY_PREFIX + entity.getDockSn();
        String countKey = RedisConst.MEDIA_FILE_PREFIX + entity.getDockSn();
        Object mediaFileCount = RedisOpsUtils.hashGet(countKey, entity.getJobId());
        if (Objects.nonNull(mediaFileCount)) {
            builder.uploadedCount(((MediaFileCountDTO) mediaFileCount).getUploadedCount())
                    .uploading(RedisOpsUtils.checkExist(key) && entity.getJobId().equals(((MediaFileCountDTO)RedisOpsUtils.get(key)).getJobId()));
            return builder.build();
        }

        int uploadedSize = fileService.getFilesByWorkspaceAndJobId(entity.getWorkspaceId(), entity.getJobId()).size();
        // All media for this job have been uploaded.
        if (uploadedSize >= entity.getMediaCount()) {
            return builder.uploadedCount(uploadedSize).build();
        }
        RedisOpsUtils.hashSet(countKey, entity.getJobId(),
                MediaFileCountDTO.builder()
                        .jobId(entity.getJobId())
                        .mediaCount(entity.getMediaCount())
                        .uploadedCount(uploadedSize).build());
        return builder.build();
    }

    @Override
    public List<TaskStatusStatisticDTO> getTaskStatusCount(String workspaceId) {

        List<WaylineJobEntity> waylineJobList = getWaylineJobList(workspaceId);

        Map<Integer, List<WaylineJobEntity>> resultMap = waylineJobList.stream().collect(Collectors.groupingBy(WaylineJobEntity::getStatus));

        List<TaskStatusStatisticDTO> countList = new ArrayList<>();
        for (WaylineJobStatusEnum waylineJobStatusEnum : WaylineJobStatusEnum.values()) {

            TaskStatusStatisticDTO tempStatusDTO = new TaskStatusStatisticDTO();
            tempStatusDTO.setTaskStatus(waylineJobStatusEnum.getVal());
            tempStatusDTO.setTaskStatusName(waylineJobStatusEnum.name());

            List<WaylineJobEntity> tempList = resultMap.getOrDefault(waylineJobStatusEnum.getVal(), new ArrayList<>());

            tempStatusDTO.setCount(tempList.size());
            countList.add(tempStatusDTO);
        }

        return countList;
    }

    List<WaylineJobEntity> getWaylineJobList(String workspaceId) {

        LambdaQueryWrapper<WaylineJobEntity> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(WaylineJobEntity::getWorkspaceId, workspaceId);

        // 获取 org
        if (aboveSysAdminRole()) {

        } else {
            queryWrapper.eq(WaylineJobEntity::getOrgId, getOrgId());
        }
        List<WaylineJobEntity> list = this.list(queryWrapper);

        return list;
    }
}
