package com.multictrl.modules.business.service.impl;

import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.json.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.google.common.collect.Maps;
import com.multictrl.common.constant.BusinessConstant;
import com.multictrl.common.constant.FlightTaskStatus;
import com.multictrl.common.exception.ErrorCode;
import com.multictrl.common.exception.RenException;
import com.multictrl.common.page.PageData;
import com.multictrl.common.service.impl.CrudServiceImpl;
import cn.hutool.core.util.StrUtil;
import com.multictrl.common.utils.*;
import com.multictrl.modules.business.dao.DockDeviceDao;
import com.multictrl.modules.business.dao.FlightTaskDao;
import com.multictrl.modules.business.dto.FlightTaskDTO;
import com.multictrl.modules.business.dto.MediaFileDTO;
import com.multictrl.modules.business.dto.RouteDTO;
import com.multictrl.modules.business.entity.*;
import com.multictrl.modules.business.influxdb.UavReport;
import com.multictrl.modules.business.service.*;
import com.multictrl.modules.job.dao.ScheduleJobDao;
import com.multictrl.modules.job.dto.ScheduleJobDTO;
import com.multictrl.modules.job.entity.ScheduleJobEntity;
import com.multictrl.modules.security.user.SecurityUser;
import com.multictrl.modules.sys.dto.SysUserDTO;
import com.multictrl.modules.sys.service.SysUserService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.*;

/**
 * 飞行架次
 *
 * @author Sdy
 * @since 1.0.0 2026-05-02
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class FlightTaskServiceImpl extends CrudServiceImpl<FlightTaskDao, FlightTaskEntity, FlightTaskDTO> implements FlightTaskService {
    private final DockService dockService;
    private final RouteService routeService;
    private final InfluxService influxService;
    private final MediaFileService mediaFileService;
    private final DockDeviceDao dockDeviceDao;
    private final SrsService srsService;
    private final ScheduleJobDao scheduleJobDao;
    private final SysUserService userService;

    @Override
    public QueryWrapper<FlightTaskEntity> getWrapper(Map<String, Object> params) {
        String id = (String) params.get("id");
        String key = (String) params.get("key");
        String taskType = (String) params.get("taskType");
        String routeType = (String) params.get("routeType");
        String taskStatus = (String) params.get("taskStatus");

        QueryWrapper<FlightTaskEntity> wrapper = new QueryWrapper<>();
        wrapper.eq(StrUtil.isNotBlank(id), "id", id);
        wrapper.and(StrUtil.isNotBlank(key), v ->
                v.like("dock_name", key).or().like("route_name", key));
        if (StrUtil.isNotBlank(taskType)) {
            wrapper.eq("task_type", Integer.parseInt(taskType));
        }
        if (StrUtil.isNotBlank(taskStatus)) {
            wrapper.eq("task_status", Integer.parseInt(taskStatus));
        }
        wrapper.eq(StrUtil.isNotBlank(routeType), "route_type", routeType);
        wrapper.orderByDesc("create_date");

        return wrapper;
    }

    @Override
    public PageData<FlightTaskDTO> pageList(Map<String, Object> params) {
        PageData<FlightTaskDTO> page = page(params);
        for (FlightTaskDTO dto : page.getList()) {
            if (dto.getMediaNum() == null || dto.getMediaNum() == 0) {
                Long mediaNum = mediaFileService.getDao().selectCount(new QueryWrapper<MediaFileEntity>().eq("task_id", dto.getTaskId()));
                dto.setMediaNum(mediaNum.intValue());
            }
        }
        return page;
    }

    @Override
    public void addRouteTask(String taskId, String dockSn, Long routeId) {
        FlightTaskEntity entity = new FlightTaskEntity();
        entity.setTaskId(taskId);
        entity.setTaskType(1);
        addDockInfo(entity, dockSn);
        entity.setRouteId(routeId);
        RouteDTO route = routeService.getRoute(routeId);
        entity.setRouteName(route.getRouteName());
        entity.setRouteType(route.getTemplateType());
        entity.setRouteDistance(route.getTotalDistance());
        entity.setWaypointNum(route.getRouteWaypointList().size());
        entity.setDeptId(SecurityUser.getDeptId());
        entity.setTaskStatus(FlightTaskStatus.IN_PROGRESS.getCode());
        entity.setCreateName(SecurityUser.getUser().getRealName());
        baseDao.insert(entity);
    }

    @Override
    public void addTakeoffTask(String taskId, String dockSn) {
        FlightTaskEntity entity = new FlightTaskEntity();
        entity.setTaskId(taskId);
        entity.setTaskType(2);
        addDockInfo(entity, dockSn);
        entity.setRouteName("一键起飞");
        entity.setDeptId(SecurityUser.getDeptId());
        entity.setTaskStatus(FlightTaskStatus.IN_PROGRESS.getCode());
        entity.setCreateName(SecurityUser.getUser().getRealName());
        baseDao.insert(entity);
    }

    @Override
    public void addJobRouteTask(String taskId, Long jobId) {
        FlightTaskEntity entity = new FlightTaskEntity();
        entity.setTaskId(taskId);
        entity.setTaskType(3);
        ScheduleJobEntity jobEntity = scheduleJobDao.getById(jobId);
        addDockInfo(entity, jobEntity.getDockSn());
        long routeId = Long.parseLong(jobEntity.getParams());
        entity.setRouteId(routeId);
        RouteDTO route = routeService.getRoute(routeId);
        entity.setRouteName(route.getRouteName());
        entity.setRouteType(route.getTemplateType());
        entity.setRouteDistance(route.getTotalDistance());
        entity.setWaypointNum(route.getRouteWaypointList().size());
        entity.setDeptId(jobEntity.getDeptId());
        entity.setTaskStatus(FlightTaskStatus.IN_PROGRESS.getCode());
        SysUserDTO userDTO = userService.get(jobEntity.getUpdater());
        entity.setCreateName(userDTO.getRealName());
        baseDao.insert(entity);
    }

    @Override
    public void updateTaskComplete(String dockSn) {
        String taskId = (String) CacheUtils.get(BusinessConstant.WORKING_TASK_ID + dockSn);
        if (StrUtil.isBlank(taskId)) {
            log.debug("dockSn:{} 没有正在执行的任务，无法更新架次表", dockSn);
            return;
        }
        FlightTaskEntity entity = selectById(taskId);
        if (entity == null) {
            log.error("任务不存在，taskId:{}", taskId);
            return;
        }
        entity.setTaskStatus(FlightTaskStatus.FINISHED.getCode());
        entity.setInboundDate(new Date());
        //更新飞行距离、飞行时长
        HashMap<String, Object> condition = Maps.newHashMap();
        condition.put("dockSn", dockSn);
        List<UavReport> flyList = influxService.queryData(DateUtils.format(entity.getOutboundDate(), DateUtils.DATE_TIME_PATTERN),
                DateUtils.format(entity.getInboundDate(), DateUtils.DATE_TIME_PATTERN), "uav_osd", condition, UavReport.class,
                "longitude", "latitude", "elevation", "_time");
        double total = 0;
        UavReport start = null;
        UavReport end = null;
        for (int i = 0; i < flyList.size() - 1; i++) {
            UavReport p1 = flyList.get(i);
            UavReport p2 = flyList.get(i + 1);
            total += Spatial4jUtils.distance(p1.getLatitude(), p1.getLongitude(), p2.getLatitude(), p2.getLongitude());
            //过滤出起飞和降落的记录，当飞机和机场的相对高度大于0，则认为飞机在飞行中，开始计算飞行时长
            if (flyList.get(i).getElevation() > 0) {
                end = flyList.get(i);
                if (start == null) {
                    start = flyList.get(i);
                }
            }
        }
        entity.setFlightDistance(total);
        String flightDurationText = "0秒";
        long flightDuration = 0L;
        if (start != null && end != null) {
            DateTime startDate = DateUtil.parseDateTime(start.getTimeStr());
            DateTime endDate = DateUtil.parseDateTime(end.getTimeStr());
            flightDurationText = DateUtils.formatDifference(startDate, endDate);
            flightDuration = Math.abs((endDate.getTime() - startDate.getTime()) / 1000);
        }
        entity.setFlightDurationText(flightDurationText);
        entity.setFlightDuration(flightDuration);
        //更新媒体数量 -- 媒体上传 机库是分段式的，机场空闲了，媒体文件还没上传，所以该值不准确可能为0
        Long count = mediaFileService.getDao().selectCount(new QueryWrapper<MediaFileEntity>()
                .eq("task_id", taskId).eq("dock_sn", dockSn));
        entity.setMediaNum(count.intValue());
        updateById(entity);
    }

    @Override
    public void updateTaskStart(String dockSn) {
        String taskId = (String) CacheUtils.get(BusinessConstant.WORKING_TASK_ID + dockSn);
        if (StrUtil.isBlank(taskId)) {
            log.debug("dockSn:{} 没有正在执行的任务，无法更新架次表", dockSn);
            return;
        }
        FlightTaskEntity entity = selectById(taskId);
        if (entity == null) {
            log.error("任务不存在，taskId:{}", taskId);
            return;
        }
        entity.setTaskStatus(FlightTaskStatus.IN_PROGRESS.getCode());
        entity.setOutboundDate(new Date());
        List<DockDeviceEntity> deviceEntityList = dockDeviceDao.selectList(new QueryWrapper<DockDeviceEntity>().eq("parent_sn", dockSn));
        for (DockDeviceEntity dockDevice : deviceEntityList) {
            String deviceModelKey = dockDevice.getDeviceModelKey();
            if (deviceModelKey.startsWith("0")) {
                entity.setUavSn(dockDevice.getSn());
            }
        }
        updateById(entity);
    }

    @Override
    public List<JSONObject> getFlightTrack(String taskId) {
        FlightTaskEntity entity = selectById(taskId);
        if (entity == null) {
            throw new RenException(ErrorCode.ROUTE_TASK_NOT_EXIST);
        }
        Date outboundDate = entity.getOutboundDate();
        Date inboundDate = entity.getInboundDate();
        String dockSn = entity.getDockSn();
        if (outboundDate == null || inboundDate == null || StrUtil.isBlank(dockSn)) {
            return List.of();
        }
        HashMap<String, Object> condition = Maps.newHashMap();
        condition.put("dockSn", dockSn);
        List<JSONObject> uavReportList = influxService.queryData(DateUtils.format(outboundDate, DateUtils.DATE_TIME_PATTERN),
                DateUtils.format(inboundDate, DateUtils.DATE_TIME_PATTERN), "uav_osd", condition, JSONObject.class,
                "dockSn", "_time", "obstacle_avoidance_horizon", "obstacle_avoidance_upside", "obstacle_avoidance_downside", "is_near_area_limit",
                "is_near_height_limit", "gimbal_pitch", "gimbal_roll", "gimbal_yaw", "payload_index", "zoom_factor",
                "track_id", "position_state_is_fixed", "position_state_is_quality", "position_state_is_gps_number", "position_state_is_rtk_number",
                "battery_capacity_percent", "battery_remain_flight_time", "battery_return_home_power",
                "battery_landing_power", "battery_capacity_percent_a", "battery_capacity_percent_b", "wind_direction", "wind_speed", "home_distance",
                "attitude_head", "attitude_roll", "attitude_pitch", "elevation", "height", "latitude", "longitude", "vertical_speed", "horizontal_speed");
        //去重
        Set<String> seenTimes = new LinkedHashSet<>();
        List<JSONObject> deduplicated = new ArrayList<>(uavReportList.size());
        for (JSONObject obj : uavReportList) {
            String time = obj.getStr("_time");
            if (seenTimes.add(time)) {
                deduplicated.add(obj);
            }
        }
        //补点
        List<JSONObject> filledList = new ArrayList<>();
        for (int i = 0; i < deduplicated.size(); i++) {
            JSONObject current = deduplicated.get(i);
            String currentTime = current.getStr("time");
            filledList.add(current);
            // 检查是否是最后一个点
            if (i == deduplicated.size() - 1) break;
            // 获取下一个时间点
            JSONObject next = deduplicated.get(i + 1);
            String nextTime = next.getStr("time");
            // 计算时间间隔（秒）
            long timeDiff = DateUtils.calculateTimeDifference(currentTime, nextTime);
            // 间隔 > 1 秒时，插入缺失的点
            if (timeDiff > 1) {
                for (int j = 1; j < timeDiff; j++) {
                    String missingTime = DateUtils.addSeconds(currentTime, j);
                    JSONObject missingReport = new JSONObject(current.toString());
                    missingReport.set("time", missingTime);
                    filledList.add(missingReport);
                }
            }
        }

        //去点
        for (int i = filledList.size() - 1; i > 0; i--) {
            if (i % 2 == 1) {
                filledList.remove(i);
            }
        }

        return filledList;
    }

    @Override
    public List<JSONObject> getFlightLog(String taskId) {
        FlightTaskEntity entity = selectById(taskId);
        if (entity == null) {
            throw new RenException(ErrorCode.ROUTE_TASK_NOT_EXIST);
        }
        Date outboundDate = entity.getOutboundDate();
        Date inboundDate = entity.getInboundDate();
        String dockSn = entity.getDockSn();
        if (outboundDate == null || inboundDate == null || StrUtil.isBlank(dockSn)) {
            return List.of();
        }
        HashMap<String, Object> condition = Maps.newHashMap();
        condition.put("dockSn", dockSn);

        return influxService.queryData(DateUtils.format(DateUtils.getBeforeTime(outboundDate, -5), DateUtils.DATE_TIME_PATTERN),
                DateUtils.format(inboundDate, DateUtils.DATE_TIME_PATTERN), "flight_log", condition, JSONObject.class,
                "dockSn", "_time", "method", "level", "message");
    }

    @Override
    public List<MediaFileDTO> getTaskMedia(String taskId) {
        Map<String, Object> params = new HashMap<>();
        params.put("taskId", taskId);
        List<MediaFileDTO> list = mediaFileService.list(params);
        for (MediaFileDTO dto : list) {
            String objectKey = dto.getObjectKey();
            String subFileType = dto.getSubFileType();
            if ("video".equals(subFileType)) {
                dto.setUrl(BusinessConstant.VIDEO_PATH + BusinessConstant.DOCK_MEDIA_BUCKET + "/" + objectKey);
                dto.setCover(BusinessConstant.IMAGE_PATH + BusinessConstant.DOCK_MEDIA_BUCKET + "/" +
                        objectKey.replaceAll(".mp4", BusinessConstant.VIDEO_COVER_SUFFIX));
            } else {
                dto.setUrl(BusinessConstant.IMAGE_PATH + BusinessConstant.DOCK_MEDIA_BUCKET + "/" + objectKey);
            }
        }
        return list;
    }

    @Override
    public List<SrsRecordEntity> getFlightVideo(String taskId) {
        FlightTaskEntity entity = baseDao.selectById(taskId);
        QueryWrapper<SrsRecordEntity> wrapper = new QueryWrapper<>();
        wrapper.eq("app", entity.getDockSn());
        FlightTaskDTO previousTask = this.getPreviousTask(taskId, entity.getDockSn());
        if (previousTask == null) {
            wrapper.gt("record_date", DateUtils.getBeforeHour(entity.getCreateDate(), -8));
        } else {
            wrapper.between("record_date", DateUtils.getBeforeHour(entity.getCreateDate(), -8),
                    DateUtils.getBeforeHour(previousTask.getCreateDate(), -8));
        }
        wrapper.ne("stream", "dock");
        wrapper.orderByAsc("record_date");
        List<SrsRecordEntity> list = srsService.getDao().selectList(wrapper);
        for (SrsRecordEntity srsRecordEntity : list) {
            String file = srsRecordEntity.getFile();
            srsRecordEntity.setRecordDate(DateUtils.getBeforeHour(srsRecordEntity.getRecordDate(), 8));
            srsRecordEntity.setUrl(BusinessConstant.VIDEO_PATH + file);
//            srsRecordEntity.setCover(BusinessConstant.IMAGE_PATH + getVideoCover(srsRecordEntity.getFile(), srsRecordEntity.getStreamUrl()));
            srsRecordEntity.setCover(BusinessConstant.IMAGE_PATH + file.replaceAll(".mp4", BusinessConstant.VIDEO_COVER_SUFFIX));
        }

        return list;
    }

    @Override
    public FlightTaskDTO getPreviousTask(String taskId, String dockSn) {
        FlightTaskEntity previousTask = baseDao.getPreviousTask(taskId, dockSn);
        return ConvertUtils.sourceToTarget(previousTask, FlightTaskDTO.class);
    }

    //添加dock信息
    private void addDockInfo(FlightTaskEntity entity, String dockSn) {
        entity.setDockSn(dockSn);
        DockEntity dockEntity = dockService.getDao().selectOne(new QueryWrapper<DockEntity>().eq("dock_sn", dockSn));
        if (dockEntity != null) {
            entity.setDockName(dockEntity.getDockName());
            entity.setDockType(dockEntity.getDockType());
            entity.setDockModel(dockEntity.getDockModel());
        }
    }

    //使用ffmpeg 用视频提取封面
   /* private String getVideoCover(String file, String streamUrl) {
        String fileName = file.replace("live_record" + streamUrl + "/", "");
        String videoCoverPath = "live_record" + streamUrl + "/" + FileUtil.getPrefix(fileName) + BusinessConstant.VIDEO_COVER_SUFFIX;
        FfmpegUtils.generateVideoCover("/" + file, "/" + videoCoverPath);

        return videoCoverPath;
    }*/
}