package com.clx.performance.service.impl;

import com.clx.order.enums.ResultEnum;
import com.clx.order.feign.OrderFeign;
import com.clx.order.vo.feign.FeignOrderVO;
import com.clx.performance.constant.RedisConstants;
import com.clx.performance.dao.OrderGoodsTruckBindDao;
import com.clx.performance.dto.zjxl.TruckCoordinateDTO;
import com.clx.performance.dto.zjxl.TruckTraceDTO;
import com.clx.performance.enums.DriverTruckEnum;
import com.clx.performance.enums.OrderGoodsTruckBindEnum;
import com.clx.performance.job.TruckTraceJob;
import com.clx.performance.model.OrderGoodsTruckBind;
import com.clx.performance.param.pc.OrderGoodsTruckListParam;
import com.clx.performance.param.pc.PageTruckListParam;
import com.clx.performance.param.pc.PageVicinityTruckListParam;
import com.clx.performance.param.pc.TruckTraceParam;
import com.clx.performance.service.TruckService;
import com.clx.performance.utils.RedisGeoUntil;
import com.clx.performance.utils.zjxl.ZjxlGpsService;
import com.clx.performance.vo.pc.OrderGoodsTruckListVO;
import com.clx.performance.vo.pc.PageTruckListVO;
import com.clx.performance.vo.pc.TruckTraceVO;
import com.clx.user.feign.UserClxFeign;
import com.clx.user.vo.feign.TruckUseStatusVO;
import com.msl.common.base.PageData;
import com.msl.common.enums.ResultCodeEnum;
import com.msl.common.exception.ServiceSystemException;
import com.msl.common.result.Result;
import com.msl.common.utils.DateUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.geo.GeoResult;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.geo.Point;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @ClassName TruckServiceImpl
 * @Description
 * @Author kavin
 * @Date 2023/9/19 16:58
 * @Version 1.0
 */
@Service
@Slf4j
public class TruckServiceImpl implements TruckService {

    @Resource
    OrderFeign orderFeign;

    @Autowired(required = false)
    UserClxFeign userClxFeign;


    @Autowired
    RedisTemplate redisTemplate;

    @Autowired
    RedisGeoUntil redisGeoUntil;

    @Autowired
    TruckTraceJob truckTraceJob;


    @Autowired
    OrderGoodsTruckBindDao orderGoodsTruckBindDao;

    @Autowired
    ZjxlGpsService zjxlGpsService;




    /**
     * @Author kavin
     * @Description geo 获取附近车辆
     * @Param []
     * @return
     **/
    private GeoResults<RedisGeoCommands.GeoLocation<String>>  vicinityTruckList(String orderNo,Integer maxShowTruckNum,double MaxDistance){
        //获取发货地经纬度
        FeignOrderVO orderInfoFeign = orderFeign.getOrderInfoFeign(orderNo);
        BigDecimal sendLatitude = orderInfoFeign.getSendLatitude();//精度
        BigDecimal sendLongitude =  orderInfoFeign.getSendLongitude();//维度
        if(Objects.isNull(sendLatitude) || Objects.isNull(sendLongitude)){
            throw new ServiceSystemException(ResultEnum.DATA_NOT_FIND,"获取订单发货地经纬度信息失败");
        }

        //获取缓存中所有车辆位置信息
        Map<String, TruckTraceDTO> map = redisTemplate.opsForHash().entries(RedisConstants.ZJXL_TRUCK_TRACE_LIST);

        if(MapUtils.isEmpty(map)){
            log.warn("缓存中车辆位置信息列表为空,缓存key:{}",RedisConstants.ZJXL_TRUCK_TRACE_LIST);
            return null;
        }

        String redisGeoKey = RedisConstants.TRUCK_LOCATION_KEY + UUID.randomUUID();

        //缓存中的车辆位置信息
        //redis 进行空间位置排序
        for(Map.Entry<String, TruckTraceDTO> entry : map.entrySet()){
            TruckTraceDTO item = entry.getValue();
            BigDecimal longitudeX = item.getLocation()[0];
            BigDecimal latitudeY = item.getLocation()[1];
            redisGeoUntil.insertRedisGeo(redisGeoKey,longitudeX.doubleValue(),latitudeY.doubleValue(),item.getTruckNo());
        }

        GeoResults<RedisGeoCommands.GeoLocation<String>> sortResult = redisGeoUntil.getRedisGeoMaxIntegerKm(
                redisGeoKey,  sendLongitude.doubleValue(),sendLatitude.doubleValue(),
                maxShowTruckNum,MaxDistance);

        //清除缓存geo数据
        redisGeoUntil.deleteAllRedisGeo(redisGeoKey);
        return  sortResult;
    }
    /**
     * @Author kavin
     * @Description 获取平台所有车辆定位信息
     * @Param []
     * @return
     **/

    public List<TruckCoordinateDTO> getAllTruckCoordinate(){
        List<TruckCoordinateDTO> list = new ArrayList<>();
        //获取缓存中所有车辆位置信息
        Map<String, TruckTraceDTO> map = redisTemplate.opsForHash().entries(RedisConstants.ZJXL_TRUCK_TRACE_LIST);
        for(Map.Entry<String, TruckTraceDTO> entry : map.entrySet()){
            TruckTraceDTO item = entry.getValue();
            String truckNo = item.getTruckNo();
            BigDecimal longitudeX = item.getLocation()[0];
            BigDecimal latitudeY = item.getLocation()[1];
            TruckCoordinateDTO dto = TruckCoordinateDTO.builder().truckNo(truckNo).longitudeX(longitudeX).latitudeY(
                    latitudeY).build();
            list.add(dto);
        }
        return list ;
    }



    /**
     * @Author kavin
     * @Description 获取车辆列表
     * @Param [param]
     * @return
     **/
    @Override
    public PageData<PageTruckListVO> pageTruckList(PageTruckListParam param) {
        GeoResults<RedisGeoCommands.GeoLocation<String>> sortResult = this.vicinityTruckList(param.getOrderNo(),param.getNeedTruckNum()*4,Integer.MAX_VALUE);
        if(Objects.isNull(sortResult)){
            return new PageData<>();
        }
        Map<String,Integer> truckStatusMap = this.getTruckStatusMap();
        List<GeoResult<RedisGeoCommands.GeoLocation<String>>> content = sortResult.getContent();
        //查询定向单绑定的车辆列表
        List<String> bindTruckNos = orderGoodsTruckBindDao.findUnreleasedTruck();
        List<PageTruckListVO> filterList = new ArrayList<>();
        //车辆距离发货地由近及远的车辆列表信息
        for(GeoResult<RedisGeoCommands.GeoLocation<String>> item : content){
            String truckNo = item.getContent().getName();
            String truckLevel = "A";
            double maxDistance = item.getDistance().getValue();
            //进行筛选
            //默认重车过滤掉
            if(Objects.equals(DriverTruckEnum.TruckOrderStatus.ORDER_HEAVY.getCode(),truckStatusMap.get(truckNo))){
                continue;
            }
            //绑定的定向车进行过滤
            if(bindTruckNos.contains(truckNo)){
                continue;
            }
            if(StringUtils.isNotBlank(param.getTruckNo()) && !StringUtils.equals(truckNo,param.getTruckNo())){
                continue;
            }
            if(Objects.nonNull(param.getTruckUserStatus()) && !Objects.equals(param.getTruckUserStatus(),truckStatusMap.get(truckNo))){
                continue;
            }
            if(StringUtils.isNotBlank(param.getTruckLevel()) && !StringUtils.equals(truckLevel,param.getTruckLevel())){
                continue;
            }
            if(Objects.nonNull(param.getMaxDistance()) &&  maxDistance > param.getMaxDistance()){
                continue;
            }
            PageTruckListVO vo = PageTruckListVO.builder().truckNo(truckNo).truckLevel(truckLevel).maxDistance(maxDistance).build();
            vo.setTruckUserStatus(truckStatusMap.get(truckNo));
            filterList.add(vo);
        }

        //分页
        PageData<PageTruckListVO> pageData = new PageData();
        pageData.setPages(param.getPage());
        pageData.setTotal(filterList.size());
        pageData.setRecords(pageBySubList(filterList,param.getPageSize(),param.getPage()));
        return pageData;
    }





    /**
     * @Author kavin
     * @Description 查询附近10公里内的车辆
     * @Param [param]
     * @return
     **/
    @Override
    public PageData<PageTruckListVO> getVicinityTruckList(PageVicinityTruckListParam param) {
        GeoResults<RedisGeoCommands.GeoLocation<String>> sortResult = this.vicinityTruckList(param.getOrderNo(),100000,10);
        if(Objects.isNull(sortResult)){
            return new PageData<>();
        }
        Map<String,Integer> truckStatusMap = this.getTruckStatusMap();
        List<PageTruckListVO> list = new ArrayList<>();
        List<GeoResult<RedisGeoCommands.GeoLocation<String>>> content = sortResult.getContent();


        for(GeoResult<RedisGeoCommands.GeoLocation<String>> item : content){
            String truckNo = item.getContent().getName();
            //默认重车过滤掉
            if (Objects.equals(DriverTruckEnum.TruckOrderStatus.ORDER_HEAVY.getCode(), truckStatusMap.get(truckNo))) {
                continue;
            }
            String truckLevel = "A";
            double maxDistance = item.getDistance().getValue();
            PageTruckListVO vo = PageTruckListVO.builder().truckNo(truckNo).truckLevel(truckLevel).maxDistance(maxDistance).truckUserStatus(truckStatusMap.get(truckNo)).build();
            list.add(vo);
        }
        //分页
        PageData<PageTruckListVO> pageData = new PageData();
        pageData.setPages(param.getPage());
        pageData.setTotal(list.size());
        pageData.setRecords(pageBySubList(list,param.getPageSize(),param.getPage()));
        return pageData;
    }

    @Override
    public void addTruckGPSInfo(TruckTraceDTO truckTraceDTO) {
        redisTemplate.opsForHash().put(RedisConstants.ZJXL_TRUCK_TRACE_LIST,truckTraceDTO.getTruckNo(),truckTraceDTO);

    }

    /**
     * @Author kavin
     * @Description 获取平台用车状态信息
     * @Param []
     * @return
     **/
    private Map<String,Integer> getTruckStatusMap(){
        //查询平台所有车辆的出车状态信息
        Result<List<TruckUseStatusVO>> result = userClxFeign.getPlatformTruckStatus();

        if(!Objects.equals(result.getCode(), ResultCodeEnum.SUCCESS.getCode())){
            throw new ServiceSystemException(ResultEnum.DATA_NOT_FIND,"查询车辆出车状态信息失败");
        }

        Map<String,Integer> truckStatusMap = new HashMap<>();
        result.getData().stream().forEach(item->{
            truckStatusMap.put(item.getTruckNo(),item.getTruckOrderStatus());
        });
        return truckStatusMap;
    }



    @Override
    public void test() {

        //redisGeoUntil.deleteAllRedisGeo("station");
        Point redisGeo = redisGeoUntil.getRedisGeo("station", "游仙站点");
        System.out.println(redisGeo);

        redisGeoUntil.updateRedisGeo("station", 117.481011, 40.996791, "kavin33站点");
        redisGeoUntil.updateRedisGeo("station", 116.514203, 39.905409, "游仙站点");
        redisGeoUntil.updateRedisGeo("station", 116.514203, 39.905409, "石桥铺站点");
        redisGeoUntil.updateRedisGeo("station", 116.562108, 39.787602, "花荄站点");
        redisGeoUntil.updateRedisGeo("station", 116.334255, 40.027400, "三台县站点");
        GeoResults<RedisGeoCommands.GeoLocation<String>> sortResult = redisGeoUntil.getRedisGeoMaxIntegerKm(
                "station", 116.334212, 39.992813, 5,Integer.MAX_VALUE);

        List<GeoResult<RedisGeoCommands.GeoLocation<String>>> content = sortResult.getContent();

        for(GeoResult<RedisGeoCommands.GeoLocation<String>> item : content){
            String truckNo = item.getContent().getName();
            double maxDistance = item.getDistance().getValue();
            System.out.println(item);
/*            GeoResult [content: RedisGeoCommands.GeoLocation(name=三台县站点, point=Point [x=116.334253, y=40.027400]), distance: 3.847 KILOMETERS, ]
            GeoResult [content: RedisGeoCommands.GeoLocation(name=园艺山站点, point=Point [x=116.481050, y=39.996793]), distance: 12.52 KILOMETERS, ]
            GeoResult [content: RedisGeoCommands.GeoLocation(name=游仙站点, point=Point [x=116.514202, y=39.905409]), distance: 18.1673 KILOMETERS, ]
            GeoResult [content: RedisGeoCommands.GeoLocation(name=石桥铺站点, point=Point [x=116.514202, y=39.905409]), distance: 18.1673 KILOMETERS, ]
            GeoResult [content: RedisGeoCommands.GeoLocation(name=花荄站点, point=Point [x=116.562106, y=39.787603]), distance: 29.987 KILOMETERS, ]*/
        }
    }


    /**
     * @Author kavin
     * @Description 手动执行获取车辆定位信息
     * @Param []
     * @return
     **/
    public void handExecTruckTrace(){
        log.info("手动执行获取平台车辆定位信息");
        truckTraceJob.getPlatformTruckTrace();
    }

    /**
     * @Author kavin
     * @Description 对给的list 进行分页
     * @Param [list, pagesize, currentPage]
     * @return
     **/
    public static List<PageTruckListVO> pageBySubList(List<PageTruckListVO> list, int pageSize, int currentPage) {
        if(CollectionUtils.isEmpty(list)){
            return list;
        }
        return list.stream().skip((currentPage-1)*pageSize).limit(pageSize).collect(Collectors.toList());
    }

    @Override
    public List<OrderGoodsTruckListVO> getOrderGoodsTruckList(OrderGoodsTruckListParam param) {
        List<OrderGoodsTruckBind> truckList = orderGoodsTruckBindDao.getTrucksBindByOrderGoodsNo(
                param.getOrderGoodsNo(),param.getTruckNo());

        if(CollectionUtils.isEmpty(truckList)){
            return new ArrayList<>();
        }

        Map<String,Integer> truckStatusMap = this.getTruckStatusMap();

        List<OrderGoodsTruckListVO> result = new ArrayList<>();
        truckList.forEach(item->{
            Integer orderedStatus = Objects.equals(OrderGoodsTruckBindEnum.Status.GET.getCode(),item.getStatus())
                    || Objects.equals(OrderGoodsTruckBindEnum.Status.SUCCESS.getCode(),item.getStatus())?1:0;
            OrderGoodsTruckListVO vo = OrderGoodsTruckListVO.builder().truckNo(item.getTruckNo()).truckLevel("A").
                    truckUseStatus(truckStatusMap.get(item.getTruckNo())).orderedStatus(orderedStatus).build();
            result.add(vo);
        });
        String orderNo = truckList.get(0).getOrderNo();
        GeoResults<RedisGeoCommands.GeoLocation<String>> sortResult = this.vicinityTruckList(orderNo,Integer.MAX_VALUE,Integer.MAX_VALUE);
        if(Objects.isNull(sortResult)){
            return result;
        }

        Map<String,Double> truckDistanceMap= new HashMap<>();

        List<GeoResult<RedisGeoCommands.GeoLocation<String>>> content = sortResult.getContent();
        //车辆距离发货地由近及远的车辆列表信息
        for(GeoResult<RedisGeoCommands.GeoLocation<String>> item : content){
            String truckNo = item.getContent().getName();
            double maxDistance = item.getDistance().getValue();
            truckDistanceMap.put(truckNo,maxDistance);
        }
        result.stream().forEach(item->{ item.setMaxDistance(truckDistanceMap.get(item.getTruckNo()));});
        return result;
    }

    @Override
    public List<TruckTraceVO> getTruckTrace(TruckTraceParam param) {
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime endTime = DateUtils.parseDateTime(param.getEndTime()).get();
        if(endTime.isAfter(now)){
            throw new ServiceSystemException(ResultEnum.PARAM_ERROR,"结束时间不能晚于当前时间");
        }
        LocalDateTime beginTime = DateUtils.parseDateTime(param.getBeginTime()).get();
        if(beginTime.isAfter(endTime)){
            throw new ServiceSystemException(ResultEnum.PARAM_ERROR,"开始时间不能早于结束时间");
        }

        long seconds  = Duration.between(beginTime, endTime).getSeconds();
        if(seconds > 259200){
            throw new ServiceSystemException(ResultEnum.PARAM_ERROR,"开始时间和结束时间不能相差超于3天");
        }
        List<TruckTraceDTO> truckTrace = zjxlGpsService.getTruckTrace(param.getTruckNo(), param.getBeginTime(),
                param.getEndTime());
        List<TruckTraceVO> result = new ArrayList<>();
        if(CollectionUtils.isNotEmpty(truckTrace)){
            for (TruckTraceDTO item : truckTrace) {
                TruckTraceVO  vo = TruckTraceVO.builder().truckNo(
                        item.getTruckNo()).longitude(item.getLocation()[0]).latitude(item.getLocation()[1])
                        .angle(item.getAngle()).speed(item.getSpeed()).gpsTime(item.getGpsTime())
                        .height(item.getHeight()).mileage(item.getMileage()).createTime(item.getCreateTime())
                        .modifiedTime(item.getCreateTime()).build();
                result.add(vo);
            }
        }
        return  result;
    }
}
