package com.ningdatech.carapi.car.position.manage;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

import com.ningdatech.carapi.homepage.entity.model.NdDataAccessGps;
import com.ningdatech.carapi.homepage.service.IDataAccessGpsService;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.ningdatech.basic.exception.BizException;
import com.ningdatech.basic.exception.code.ExceptionCode;
import com.ningdatech.basic.util.NdDateUtils;
import com.ningdatech.carapi.archives.enums.AuditStateEnum;
import com.ningdatech.carapi.car.datascreen.model.dto.DataScreenParam;
import com.ningdatech.carapi.car.position.contants.PositionContant;
import com.ningdatech.carapi.car.position.entity.VehiclePositionInfo;
import com.ningdatech.carapi.car.position.model.po.ReqRealTimeMonitorPO;
import com.ningdatech.carapi.car.position.model.po.ReqTrajectoryDataPO;
import com.ningdatech.carapi.car.position.model.vo.ResRealTimeMonitorStatisticsVO;
import com.ningdatech.carapi.car.position.model.vo.ResRealTimeMonitorVehicleGisListVO;
import com.ningdatech.carapi.car.position.model.vo.ResTrajectoryDataVO;
import com.ningdatech.carapi.car.position.service.IVehiclePositionInfoService;
import com.ningdatech.carapi.car.vehicle.model.NdVehicleBaseInfo;
import com.ningdatech.carapi.car.vehicle.model.po.VehicleCompanyPO;
import com.ningdatech.carapi.car.vehicle.service.IVehicleBaseInfoService;
import com.ningdatech.carapi.common.aop.BuildChildrenRegionWrapper;
import com.ningdatech.carapi.common.constant.DeletedEnum;
import com.ningdatech.carapi.common.constant.UserRoleTypeEnum;
import com.ningdatech.carapi.common.constant.VehicleLimitSpeedEnum;
import com.ningdatech.carapi.common.constant.VehicleTypeEnum;
import com.ningdatech.carapi.common.util.CodeUtil;
import com.ningdatech.carapi.datascope.aop.LambdaDataScope;
import com.ningdatech.carapi.homepage.entity.model.OnlineNumCnt;
import com.ningdatech.carapi.homepage.entity.po.VehicleNumPO;
import com.ningdatech.carapi.homepage.service.IHomepageService;
import com.ningdatech.carapi.sys.contants.RegionContant;
import com.ningdatech.carapi.sys.entity.Company;
import com.ningdatech.carapi.sys.entity.dto.OperatorQueryDTO;
import com.ningdatech.carapi.sys.helper.OperatorsCacheHelper;
import com.ningdatech.carapi.sys.helper.RegionsCacheHelper;
import com.ningdatech.carapi.sys.helper.VehiclesCacheHelper;
import com.ningdatech.carapi.sys.service.CompanyService;
import com.ningdatech.carapi.user.security.auth.model.UserInfoDetails;
import com.ningdatech.carapi.user.util.LoginUserUtil;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.StopWatch;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/**
 * @author liuxinxin
 * @date 2022/10/8 上午11:18
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class PositionMonitorManage {

    private final IVehiclePositionInfoService iVehiclePositionInfoService;

    private final IHomepageService homepageService;

    private final RestTemplate restTemplate;

    private final IVehicleBaseInfoService vehicleBaseInfoService;

    private final CompanyService companyService;

    private final RegionsCacheHelper regionsCacheHelper;

    private final VehiclesCacheHelper vehiclesCacheHelper;

    @Value("${gps.host}")
    private String HOST;

    private final RedisTemplate redisTemplate;
    private final OperatorsCacheHelper operatorsCacheHelper;
    private final IDataAccessGpsService dataAccessGpsService;


    public List<ResTrajectoryDataVO> trajectoryDatalist(ReqTrajectoryDataPO po) {
        String carPlate = po.getCarPlate();
        LocalDateTime startTime = po.getStartTime();
        LocalDateTime endTime = po.getEndTime();
        if (Objects.isNull(startTime)) {
            startTime = LocalDateTime.now().minusHours(2);
        }
        // 默认时间区间为2小时前到现在
        if (Objects.isNull(endTime)) {
            endTime = LocalDateTime.now();
        }

        // 判断车辆所属区域是否在登录用户的区域权限内
        NdVehicleBaseInfo car = vehiclesCacheHelper.getByCarPlate(carPlate);
        Long regionId = car.getRegionId();
        Long companyId = car.getCompanyId();
        UserInfoDetails userDetail = LoginUserUtil.loginUserDetail();
        Long userDetailRegionId = userDetail.getRegionId();
        List<Long> listChild = regionsCacheHelper.listChildRegionId(userDetailRegionId,true,true);
        // 不在登录用户的区域权限内 返回空数据
        if (!listChild.contains(regionId)){
            return Collections.emptyList();
        }

        //判断日期有没有跨天
        checkDateExtendIntoNextDay(startTime, endTime);

        //动态表名
        List<VehiclePositionInfo> vehiclePositionInfoList = iVehiclePositionInfoService
                .dynamicGpsTableHistorySearch(carPlate, startTime, endTime,NdDateUtils.format(startTime, "yyyy_MM_dd"));


        List<ResTrajectoryDataVO> resTrajectoryDataVOList = vehiclePositionInfoList.stream().map(
                r -> {
                    ResTrajectoryDataVO vo = BeanUtil.copyProperties(r, ResTrajectoryDataVO.class);
                    vo.setIsOverSpeed(checkIsOverSpeed(r,car));
                    return vo;
                }).collect(Collectors.toList());
        //连续6次超速 才算超速
        checkContinuousOverspeed(resTrajectoryDataVOList);
        return resTrajectoryDataVOList;
    }

    private final String REDIS_CAR_FILED = "car-gps:";

    private void checkContinuousOverspeed(List<ResTrajectoryDataVO> resTrajectoryDataVOList) {
        if(CollUtil.isNotEmpty(resTrajectoryDataVOList)){
            for(int i = 0;i < resTrajectoryDataVOList.size();i++){
                ResTrajectoryDataVO vo = resTrajectoryDataVOList.get(i);
                String carPlate = vo.getCarPlate();
                if(vo.getIsOverSpeed()){
                    Object num = redisTemplate.opsForValue().get(carPlate);
                    //如果之前已经有连续6个了 那就相安无事
                    if(Objects.isNull(num)){
                        redisTemplate.opsForValue().set(REDIS_CAR_FILED + carPlate,1,500,TimeUnit.SECONDS);
                    }else{
                        redisTemplate.opsForValue().increment(REDIS_CAR_FILED + carPlate);
                    }
                }else{
                    Object num = redisTemplate.opsForValue().get(REDIS_CAR_FILED + carPlate);
                    if(Objects.isNull(num)){
                        redisTemplate.opsForValue().set(REDIS_CAR_FILED + carPlate,0,500,TimeUnit.SECONDS);
                        continue;
                    }
                    //如果之前已经有连续6个了 那就相安无事
                    Integer overNum = Integer.valueOf(String.valueOf(num));
                    if(Objects.nonNull(num) &&  overNum >= 6){
                        redisTemplate.opsForValue().set(REDIS_CAR_FILED + carPlate,0,500,TimeUnit.SECONDS);
                    }else{
                        //如果不是的话 要把之前的 置为不超速
                        changeOverspeedFalse(resTrajectoryDataVOList,i,vo,overNum);
                    }
                }
            }
        }
    }

    private void changeOverspeedFalse(List<ResTrajectoryDataVO> resTrajectoryDataVOList,int index, ResTrajectoryDataVO vo, Integer overNum) {
        int flagNum = overNum;
        for(int i = index;i >= 0;i--){
            if(flagNum <= 0){
                break;
            }
            ResTrajectoryDataVO innerVo = resTrajectoryDataVOList.get(i);
            if(innerVo.getCarPlate().equals(vo.getCarPlate())){
                innerVo.setIsOverSpeed(Boolean.FALSE);
                flagNum--;
            }
        }
    }

    private Boolean checkIsOverSpeed(VehiclePositionInfo positionInfo, NdVehicleBaseInfo car) {
        //混凝土和移动泵车 是50
        Integer vehicleType = car.getVehicleType();
        VehicleTypeEnum enumByValue = VehicleTypeEnum.getEnumByValue(vehicleType);
        if(enumByValue.equals(VehicleTypeEnum.OTHER)){
            if(Objects.nonNull(positionInfo.getDashboardSpeed()) &&
                    positionInfo.getCarVelocity().compareTo(BigDecimal.valueOf(80)) >= 0){
               return Boolean.TRUE;
            }
        }else if(enumByValue.equals(VehicleTypeEnum.KRHP)){
            if(Objects.nonNull(positionInfo.getDashboardSpeed()) &&
                    positionInfo.getCarVelocity().compareTo(BigDecimal.valueOf(50)) >= 0) {
                return Boolean.TRUE;
            }
        }else if(enumByValue.equals(VehicleTypeEnum.MIXER)){
            if(Objects.nonNull(positionInfo.getDashboardSpeed()) &&
                    positionInfo.getCarVelocity().compareTo(BigDecimal.valueOf(50)) >= 0) {
                return Boolean.TRUE;
            }
        }else if(enumByValue.equals(VehicleTypeEnum.BULK_CEMENT_TRUCK)){
            if(Objects.nonNull(positionInfo.getDashboardSpeed()) &&
                    positionInfo.getCarVelocity().compareTo(BigDecimal.valueOf(80)) >= 0) {
                return Boolean.TRUE;
            }
        }else if(enumByValue.equals(VehicleTypeEnum.READY_MIXED_MORTAR_TRUC)){
            if(Objects.nonNull(positionInfo.getDashboardSpeed()) &&
                    positionInfo.getCarVelocity().compareTo(BigDecimal.valueOf(80)) >= 0) {
                return Boolean.TRUE;
            }
        }

        return Boolean.FALSE;
    }

    //判断日期有没有跨月
    private void checkDateExtendIntoNextDay(LocalDateTime startTime, LocalDateTime endTime) {
        if (!NdDateUtils.format(startTime, DatePattern.NORM_DATE_PATTERN)
                .equals(NdDateUtils.format(endTime, DatePattern.NORM_DATE_PATTERN))) {
            throw new BizException(ExceptionCode.OPERATION_EX.getCode(), "由于GPS数据量大 不支持跨日查询!");
        }
    }

    public ResRealTimeMonitorStatisticsVO realTimeMonitorStatistics(ReqRealTimeMonitorPO po) {
        // 统计时间 请求发起60秒内的数据为实时数据
        LocalDateTime statistics = LocalDateTime.now().minusSeconds(60L);

        List<String> companyIdList = po.getCompanyIdList();
        DataScreenParam param = new DataScreenParam();
        param.setCompanyIds(companyIdList);
        // 按照登录用户，如果是运营商账号登录，删选出该运营商关联的车辆信息
        UserInfoDetails userDetail = LoginUserUtil.loginUserDetail();
        Long roleId = userDetail.getRoleId();
        List<NdVehicleBaseInfo> vehicleBaseInfos = Lists.newArrayList();
        // 如果登录用户是运营商，只过滤出运营商下的车辆
        if (UserRoleTypeEnum.OPERATOR_ROLE_ID.getId().equals(roleId)) {
            String companyName = userDetail.getCompanyName();
            // 暂时控制运营商名称和运营商所在企业名称相同
            OperatorQueryDTO operatorQueryDTO = operatorsCacheHelper.getByName(companyName);
            // 获取运营商id
            Long operatorId = operatorQueryDTO.getId();
            param.setOperatorId(operatorId);
            LambdaQueryWrapper<NdVehicleBaseInfo> wrapper = Wrappers.lambdaQuery(NdVehicleBaseInfo.class);
            PositionMonitorManage context = (PositionMonitorManage) AopContext.currentProxy();
            context.buildSearchQuery(wrapper, param);
            vehicleBaseInfos = vehicleBaseInfoService.list(wrapper);
        }
        // 如果登录用户是企业信息员，过滤出使用企业和所属企业为该企业的车辆
        else if (UserRoleTypeEnum.COMPANY_INFO_OFFICER_ROLE_ID.getId().equals(roleId)){
            LambdaQueryWrapper<NdVehicleBaseInfo> wrapper = Wrappers.lambdaQuery(NdVehicleBaseInfo.class);
            wrapper.eq(NdVehicleBaseInfo::getDeleted, DeletedEnum.NO_DELETED.getCode())
                    .eq(NdVehicleBaseInfo::getAuditState, AuditStateEnum.AUDIT_SUCCESS.getCode())
                    .and(w -> w.eq(NdVehicleBaseInfo::getCompanyId,userDetail.getCompanyId()).or()
                            .eq(NdVehicleBaseInfo::getUseCompanyId,userDetail.getCompanyId()));
            vehicleBaseInfos = vehicleBaseInfoService.list(wrapper);
        }else{
            LambdaQueryWrapper<NdVehicleBaseInfo> wrapper = Wrappers.lambdaQuery(NdVehicleBaseInfo.class);
            PositionMonitorManage context = (PositionMonitorManage) AopContext.currentProxy();
            context.buildSearchQuery(wrapper, param);
            vehicleBaseInfos = vehicleBaseInfoService.list(wrapper);
        }
        ResRealTimeMonitorStatisticsVO resRealTimeMonitorStatisticsVO = new ResRealTimeMonitorStatisticsVO();
        if (CollUtil.isEmpty(vehicleBaseInfos)){
            return resRealTimeMonitorStatisticsVO;
        }
        VehicleNumPO vehicleNumPO = homepageService.vehicleNum(param);
        Integer vehiclesTotalCount = vehicleNumPO.getTotalNum();
        Integer vehiclesOnlineCount = 0;
        Integer vehiclesOfflineCount = 0;
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        Long cnt = vehicleBaseInfos.stream().filter(o -> Objects.nonNull(o) &&
                Objects.nonNull(o.getStatus()) && o.getStatus() == 1).count();
        stopWatch.stop();
        log.info("共耗时 {}s", stopWatch.getTotalTimeSeconds());
        log.info("缓存中车辆在线数为：" + cnt);
        vehiclesOnlineCount = cnt.intValue();
        vehiclesOfflineCount = vehiclesTotalCount - vehiclesOnlineCount;

        resRealTimeMonitorStatisticsVO.setVehiclesTotalCount(vehiclesTotalCount);
        resRealTimeMonitorStatisticsVO.setVehiclesOnlineCount(vehiclesOnlineCount);
        resRealTimeMonitorStatisticsVO.setVehiclesOfflineCount(vehiclesOfflineCount);
        resRealTimeMonitorStatisticsVO.setTodayVehiclesAccumulatedOnlineCount(vehiclesOnlineCount);
        return resRealTimeMonitorStatisticsVO;
    }

    @LambdaDataScope
    @BuildChildrenRegionWrapper
    public void buildSearchQuery(LambdaQueryWrapper<NdVehicleBaseInfo> wrapper, DataScreenParam param) {
        wrapper.eq(NdVehicleBaseInfo::getDeleted, DeletedEnum.NO_DELETED.getCode())
                .eq(NdVehicleBaseInfo::getAuditState, AuditStateEnum.AUDIT_SUCCESS.getCode())
                .eq(Objects.nonNull(param.getOperatorId()),NdVehicleBaseInfo::getOperatorId,param.getOperatorId())
                .in(Objects.nonNull(param.getCompanyIds()) && CollUtil.isNotEmpty(param.getCompanyIds()),NdVehicleBaseInfo::getCompanyId,param.getCompanyIds());
    }

    public List<ResRealTimeMonitorVehicleGisListVO> realTimeMonitorVehicleGisList(ReqRealTimeMonitorPO po) {
        // 只查询当天最新的数据
        LocalDateTime startTime = LocalDateTime.now().withHour(0).withMinute(0).withSecond(0);
        LocalDateTime endTime = LocalDateTime.now().withHour(23).withMinute(59).withSecond(59);
        List<NdDataAccessGps> data = dataAccessGpsService.list(Wrappers.lambdaQuery(NdDataAccessGps.class)
                .ge(NdDataAccessGps::getUpdateTime, startTime).le(NdDataAccessGps::getUpdateTime, endTime)
                .select(NdDataAccessGps::getCarLongitude, NdDataAccessGps::getCarLatitude, NdDataAccessGps::getCarPlate,
                        NdDataAccessGps::getCarVelocity, NdDataAccessGps::getCarDirection));
        if(CollUtil.isEmpty(data)){
            return Collections.emptyList();
        }
        List<String> plates = data.stream().map(NdDataAccessGps::getCarPlate).collect(Collectors.toList());
        Map<String, NdVehicleBaseInfo> map = vehicleBaseInfoService.listByCarNo(plates);
        return  data.stream().map(d -> {
            ResRealTimeMonitorVehicleGisListVO vo = BeanUtil.copyProperties(d,ResRealTimeMonitorVehicleGisListVO.class);
            if(map.containsKey(d.getCarPlate())){
                NdVehicleBaseInfo car = map.get(d.getCarPlate());
                vo.setVehicleType(car.getVehicleType());
                vo.setCompanyId(car.getCompanyId());
                vo.setCompanyName(car.getUseCompanyName());
                vo.setMoving(1);
                vo.setPlateColor(car.getPlateColor());
                vo.setSelfNo(car.getSelfNo());
//                vo.setLimitSpeed();
            }
            return vo;
        }).collect(Collectors.toList());
    }

    public List<ResRealTimeMonitorVehicleGisListVO> realTimeMonitorVehicleGisList(DataScreenParam param) {
        if (Objects.isNull(param.getRegionId())) {
            param.setRegionId(RegionContant.ZJ_REGION_ID);
        }
        List<Company> companies = companyService.list(Wrappers.lambdaQuery(Company.class).in(Company::getRegionId, regionsCacheHelper.listChildRegionId(param.getRegionId())));
        List<String> companyIds = CollUtil.isNotEmpty(companies) ? companies.stream().map(c ->
                String.valueOf(c.getId())).collect(Collectors.toList()) : Collections.emptyList();
        return realTimeMonitorVehicleGisList(new ReqRealTimeMonitorPO(companyIds));
    }
}
