package com.ningdatech.carapi.safe.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import com.alibaba.fastjson.JSON;
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.Maps;
import com.ningdatech.basic.model.DateRange;
import com.ningdatech.basic.util.CollUtils;
import com.ningdatech.basic.util.DateRangeUtil;
import com.ningdatech.basic.util.NdDateUtils;
import com.ningdatech.carapi.car.datascreen.model.dto.DataScreenParam;
import com.ningdatech.carapi.common.aop.BuildChildrenRegionEntity;
import com.ningdatech.carapi.common.aop.BuildChildrenRegionWrapper;
import com.ningdatech.carapi.common.util.CodeUtil;
import com.ningdatech.carapi.datascope.aop.LambdaDataScope;
import com.ningdatech.carapi.datascope.aop.XmlDataScope;
import com.ningdatech.carapi.safe.entity.NdVehicleAccident;
import com.ningdatech.carapi.safe.entity.NdVehicleAccidentDead;
import com.ningdatech.carapi.safe.mapper.SecurityManageDataScreenMapper;
import com.ningdatech.carapi.safe.model.dto.LongitudeLatitudeDTO;
import com.ningdatech.carapi.safe.model.po.*;
import com.ningdatech.carapi.safe.service.INdVehicleAccidentDeadService;
import com.ningdatech.carapi.safe.service.INdVehicleAccidentService;
import com.ningdatech.carapi.safe.service.SecurityManageDataScreenService;
import com.ningdatech.carapi.safe.utils.InitDataUtil;
import com.ningdatech.carapi.sys.entity.dto.RegionDTO;
import com.ningdatech.carapi.sys.helper.RegionsCacheHelper;
import lombok.RequiredArgsConstructor;
import org.springframework.aop.framework.AopContext;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author PoffyZhang
 * @since 2022-10-12
 */
@Service
@RequiredArgsConstructor
public class SecurityManageDataScreenServiceImpl implements SecurityManageDataScreenService {

    private final RegionsCacheHelper regionsCacheHelper;
    private final SecurityManageDataScreenMapper securityManageDataScreenMapper;
    private final INdVehicleAccidentService accidentService;
    private final INdVehicleAccidentDeadService accidentDeadService;

    private final String FIRST_PERIOD_TIME = "0:00-02:00";
    private final String SECOND_PERIOD_TIME = "2:00-4:00";
    private final String THIRD_PERIOD_TIME = "4:00-6:00";
    private final String FOURTH_PERIOD_TIME = "6:00-8:00";
    private final String FIFTH_PERIOD_TIME = "8:00-10:00";
    private final String SIXTH_PERIOD_TIME = "10:00-12:00";
    private final String SEVENTH_PERIOD_TIME = "12:00-14:00";
    private final String EIGHTH_PERIOD_TIME = "14:00-16:00";
    private final String NINTH_PERIOD_TIME = "16:00-18:00";
    private final String TENTH_PERIOD_TIME = "18:00-20:00";
    private final String ELEVENTH_PERIOD_TIME = "20:00-22:00";
    private final String LAST_PERIOD_TIME = "22:00-24:00";

    /**
     * 近三年保险分析对比
     *
     * @param param
     * @return
     */
    @Override
    @XmlDataScope
    @BuildChildrenRegionEntity
    public JSONObject getInsureAnalysis(DataScreenParam param, Integer type) {
        param.setType(type);
        List<OutDangerAnalysisPO> times = securityManageDataScreenMapper.getOutDangerAnalysis(param);
        return convert(times, type);
    }

    /**
     * 浙江省事故 经纬度分布
     *
     * @param param
     * @return
     */
    @Override
    public List<LongitudeLatitudeDTO> getAccidentLongitudeLatitude(DataScreenParam param) {
        LambdaQueryWrapper<NdVehicleAccident> wrapper = Wrappers.lambdaQuery(NdVehicleAccident.class);
        LambdaQueryWrapper<NdVehicleAccidentDead> wrapperDead = Wrappers.lambdaQuery(NdVehicleAccidentDead.class);
        SecurityManageDataScreenServiceImpl context = (SecurityManageDataScreenServiceImpl) AopContext.currentProxy();
        List<NdVehicleAccident> searchAccidents = context.searchAccidents(wrapper, param);
        List<NdVehicleAccidentDead> accidentDeads = context.searchAccidentDeads(wrapperDead, param);
        List<LongitudeLatitudeDTO> accidents = searchAccidents.stream().map(accident -> new LongitudeLatitudeDTO(accident.getLongitude(), accident.getLatitude())).collect(Collectors.toList());
        List<LongitudeLatitudeDTO> deads = accidentDeads.stream().map(dead -> new LongitudeLatitudeDTO(dead.getLongitude(), dead.getLatitude())).collect(Collectors.toList());
        return Stream.of(accidents, deads).flatMap(Collection::stream).distinct().collect(Collectors.toList());
    }

    @LambdaDataScope
    @BuildChildrenRegionWrapper
    public List<NdVehicleAccident> searchAccidents(LambdaQueryWrapper<NdVehicleAccident> wrapper, DataScreenParam param) {
        return accidentService.list(wrapper);
    }

    @LambdaDataScope
    @BuildChildrenRegionWrapper
    public List<NdVehicleAccidentDead> searchAccidentDeads(LambdaQueryWrapper<NdVehicleAccidentDead> wrapper, DataScreenParam param) {
        return accidentDeadService.list(wrapper);
    }

    /**
     * 本月 各时段出险
     *
     * @param param
     * @return
     */
    @Override
    @XmlDataScope
    @BuildChildrenRegionEntity
    public JSONObject getOutDangerCurrentMonth(DataScreenParam param) {
        JSONObject res = new JSONObject();
        if (param.getStartTime() == null || param.getEndTime() == null) {
            // 默认查询上月数据
            DateRange<LocalDate> monthRange = DateRangeUtil.monthsAgo(1);
            param.setStartTime(monthRange.getStart().atStartOfDay());
            param.setEndTime(monthRange.getEnd().atTime(LocalTime.MAX));
        }
        OutDangerPeriodTimePO outDangerPeriodTime = securityManageDataScreenMapper.getOutDangerPeriodTime(param);
        return convertJson(outDangerPeriodTime, res);
    }

    /**
     * 超速本月数据
     *
     * @param param
     * @return
     */
    @Override
    @XmlDataScope
    @BuildChildrenRegionEntity
    public JSONObject getOverSpeedCurrentMonth(DataScreenParam param) {
        JSONObject res = new JSONObject();
        List<OverspeedTimesPO> overspeedTimes = securityManageDataScreenMapper.getOverSpeedCurrentMonth(param);
        return convertOverspeedJson(overspeedTimes, res);
    }

    /**
     * 近三年各城市出险次数
     *
     * @param param
     * @return
     */
    @Override
    @XmlDataScope
    @BuildChildrenRegionEntity
    public JSONObject getOutDangerCity(DataScreenParam param) {
        JSONObject res = new JSONObject();
        CodeUtil.buildRegionLevel(param);
        List<OutDangerCityPO> outDangers = securityManageDataScreenMapper.getOutDangerRegions(param);
        complementaryRegion(outDangers, param.getRegionId());
        return convertCityOutDangerJson(outDangers, res);
    }

    @Override
    @XmlDataScope
    public List<OutDangerAnalysisPO> getOutDangerAnalysis(DataScreenParam param) {
        return securityManageDataScreenMapper.getOutDangerAnalysis(param);
    }

    @Override
    @XmlDataScope
    public List<OverspeedAnalysisPO> getOverspeedAnalysis(DataScreenParam param) {
        return securityManageDataScreenMapper.getOverspeedAnalysis(param);
    }

    private JSONObject convertCityOutDangerJson(List<OutDangerCityPO> outDangerCities, JSONObject res) {
        if (CollUtil.isEmpty(outDangerCities)) {
            return res;
        }
        JSONObject total = new JSONObject();
        JSONObject data = new JSONObject(true);
        Integer beforeTotal = 0;
        Integer lastTotal = 0;
        Integer currentTotal = 0;
        for (OutDangerCityPO po : outDangerCities) {
            beforeTotal += po.getBeforeOutDanger();
            lastTotal += po.getLastOutDanger();
            currentTotal += po.getCurrentOutDanger();
            JSONObject innerData = new JSONObject();
            innerData.put(String.valueOf(LocalDateTime.now().minusYears(2).getYear()), po.getBeforeOutDanger());
            innerData.put(String.valueOf(LocalDateTime.now().minusYears(1).getYear()), po.getLastOutDanger());
            innerData.put(String.valueOf(LocalDateTime.now().getYear()), po.getCurrentOutDanger());
            data.put(po.getRegionName(), innerData);
        }
        total.put(String.valueOf(LocalDateTime.now().minusYears(2).getYear()), beforeTotal);
        total.put(String.valueOf(LocalDateTime.now().minusYears(1).getYear()), lastTotal);
        total.put(String.valueOf(LocalDateTime.now().getYear()), currentTotal);
        res.put("total", total);
        res.put("data", data);
        return res;
    }

    private JSONObject convertOverspeedJson(List<OverspeedTimesPO> overspeedTimes, JSONObject res) {
        Map<String, OverspeedTimesPO> dataMap = CollUtils.listToMap(overspeedTimes, o -> o.getMonthDay());
        TreeMap<String, Object> innerData = new TreeMap();
        for (int i = 30; i >= 0; i--) {
            String dateStr = NdDateUtils.format(LocalDateTime.now().minusDays(i), DatePattern.NORM_DATE_PATTERN);
            if (dataMap.containsKey(dateStr)) {
                innerData.put(dataMap.get(dateStr).getMonthDay(), dataMap.get(dateStr).getOverspeedTimes());
            } else {
                innerData.put(dateStr, 0);
            }
        }

        res.put("total", overspeedTimes.stream().mapToInt(overspeedTime -> overspeedTime.getOverspeedTimes()).sum());
        res.put("data", innerData);
        return res;
    }

    private JSONObject convertJson(OutDangerPeriodTimePO po, JSONObject res) {
        if (Objects.isNull(po)) {
            return res;
        }
        res.put(FIRST_PERIOD_TIME, po.getFirstPeriodTime());
        res.put(SECOND_PERIOD_TIME, po.getSecondPeriodTime());
        res.put(THIRD_PERIOD_TIME, po.getThirdPeriodTime());
        res.put(FOURTH_PERIOD_TIME, po.getFourthPeriodTime());
        res.put(FIFTH_PERIOD_TIME, po.getFifthPeriodTime());
        res.put(SIXTH_PERIOD_TIME, po.getSixthPeriodTime());
        res.put(SEVENTH_PERIOD_TIME, po.getSeventhPeriodTime());
        res.put(EIGHTH_PERIOD_TIME, po.getEighthPeriodTime());
        res.put(NINTH_PERIOD_TIME, po.getNinthPeriodTime());
        res.put(TENTH_PERIOD_TIME, po.getTenthPeriodTime());
        res.put(ELEVENTH_PERIOD_TIME, po.getEleventhPeriodTime());
        res.put(LAST_PERIOD_TIME, po.getLastPeriodTime());
        return res;
    }

    private JSONObject convert(List<OutDangerAnalysisPO> outDangerTimes, Integer type) {
        Map<String, Map<String, Object>> yearMap = InitDataUtil.initThreeYears();
        for (OutDangerAnalysisPO outDangerTime : outDangerTimes) {
            if (yearMap.containsKey(outDangerTime.getYear())) {
                Map<String, Object> monthMap = yearMap.get(outDangerTime.getYear());
                if (monthMap.containsKey(outDangerTime.getMonth())) {
                    switch (type) {
                        case 1:
                            monthMap.put(outDangerTime.getMonth(), outDangerTime.getOutDangerTimes());
                            break;
                        case 2:
                            monthMap.put(outDangerTime.getMonth(), outDangerTime.getInjuries());
                            break;
                        case 3:
                            monthMap.put(outDangerTime.getMonth(), outDangerTime.getDeathToll());
                            break;
                        default:
                            monthMap.put(outDangerTime.getMonth(), outDangerTime.getOutDangerTimes());
                    }
                }
            }
        }
        Map<String, Object> total = countTotal(yearMap);
        yearMap.put("total", total);
        return JSONObject.parseObject(JSON.toJSONString(yearMap));
    }

    private Map<String, Object> countTotal(Map<String, Map<String, Object>> yearMap) {
        Map<String, Object> total = Maps.newHashMap();
        Map<String, Object> currentMap = yearMap.get(String.valueOf(LocalDateTime.now().getYear()));
        int currentSum = currentMap.values().stream().mapToInt(o -> Integer.parseInt(String.valueOf(o))).sum();
        total.put(String.valueOf(LocalDateTime.now().getYear()), currentSum);
        Map<String, Object> lastMap = yearMap.get(String.valueOf(LocalDateTime.now().minusYears(1).getYear()));
        int lastSum = lastMap.values().stream().mapToInt(o -> Integer.parseInt(String.valueOf(o))).sum();
        total.put(String.valueOf(LocalDateTime.now().minusYears(1).getYear()), lastSum);
        Map<String, Object> beforeMap = yearMap.get(String.valueOf(LocalDateTime.now().minusYears(2).getYear()));
        int beforeSum = beforeMap.values().stream().mapToInt(o -> Integer.parseInt(String.valueOf(o))).sum();
        total.put(String.valueOf(LocalDateTime.now().minusYears(2).getYear()), beforeSum);
        return total;
    }

    public void complementaryRegion(List<OutDangerCityPO> list, Long regionId) {
        Map<Long, OutDangerCityPO> dataMap = CollUtils.listToMap(list, OutDangerCityPO::getRegionId);
        List<RegionDTO> regions = regionsCacheHelper.listChildOnly(regionId, true);
        List<OutDangerCityPO> tmpList = new ArrayList<>();
        regions.forEach(w -> {
            OutDangerCityPO odc = dataMap.get(w.getId());
            if (odc == null) {
                odc = new OutDangerCityPO();
                odc.setRegionId(w.getId());
                odc.setRegionName(w.getRegionName());
                odc.setRegionName(w.getRegionName());
                odc.setRegionCode(w.getRegionCode());
                odc.setTotal(0);
                odc.setLastOutDanger(0);
                odc.setCurrentOutDanger(0);
                odc.setBeforeOutDanger(0);
            }
            tmpList.add(odc);
        });
        list.clear();
        list.addAll(tmpList);
    }
}
