package org.dromara.elevator.service.impl;

import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.dromara.common.core.service.DictService;
import org.dromara.common.core.utils.DateUtils;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.elevator.constant.StateConstant;
import org.dromara.elevator.constant.WorkStateEnum;
import org.dromara.elevator.constant.WorkType;
import org.dromara.elevator.domain.*;
import org.dromara.elevator.domain.bo.*;
import org.dromara.elevator.domain.vo.*;
import org.dromara.elevator.mapper.*;
import org.dromara.elevator.service.*;
import org.dromara.system.domain.bo.SysDeptBo;
import org.dromara.system.domain.vo.SysDeptVo;
import org.dromara.system.service.ISysDeptService;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.temporal.TemporalAdjusters;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import static org.dromara.elevator.constant.ElevatorConstant.*;
import static org.dromara.elevator.constant.RescueConstant.*;

/**
 * 监控大屏;Service业务层处理
 *
 * @author zy
 * @date 2024-11-04
 */
@RequiredArgsConstructor
@Service
public class ELargeScreenServiceImpl implements IELargeScreenService {

    private final ELargeScreenMapper baseMapper;
    private final IEElevatorInfoService elevatorInfoService;
    private final IEBlackBoxInfoService blackBoxInfoService;
    private final IEWorkDataService workDataService;
    private final ISysDeptService deptService;
    private final DictService dictService;
    private final EWorkDataMapper workDataMapper;
    private final EMaintenanceInfoMapper maintenanceInfoMapper;
    private final ETenementInfoMapper tenementInfoMapper;
    private final EFaultMessageMapper faultMessageMapper;
    private final EBehaviorMessageMapper behaviorMessageMapper;
    private final EBehaviorStatisticsMapper behaviorStatisticsMapper;

    /**
     * 查询监控大屏;
     *
     * @param id 主键
     * @return 监控大屏;
     */
    @Override
    public ELargeScreenVo queryById(Long id){
        return baseMapper.selectVoById(id);
    }

    /**
     * 分页查询监控大屏;列表
     *
     * @param bo        查询条件
     * @param pageQuery 分页参数
     * @return 监控大屏;分页列表
     */
    @Override
    public TableDataInfo<ELargeScreenVo> queryPageList(ELargeScreenBo bo, PageQuery pageQuery) {
        LambdaQueryWrapper<ELargeScreen> lqw = buildQueryWrapper(bo);
        Page<ELargeScreenVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
        return TableDataInfo.build(result);
    }

    /**
     * 查询符合条件的监控大屏;列表
     *
     * @param bo 查询条件
     * @return 监控大屏;列表
     */
    @Override
    public List<ELargeScreenVo> queryList(ELargeScreenBo bo) {
        LambdaQueryWrapper<ELargeScreen> lqw = buildQueryWrapper(bo);
        return baseMapper.selectVoList(lqw);
    }

    private LambdaQueryWrapper<ELargeScreen> buildQueryWrapper(ELargeScreenBo bo) {
        Map<String, Object> params = bo.getParams();
        LambdaQueryWrapper<ELargeScreen> lqw = Wrappers.lambdaQuery();
        lqw.like(StringUtils.isNotBlank(bo.getName()), ELargeScreen::getName, bo.getName());
        lqw.like(StringUtils.isNotBlank(bo.getAddress()), ELargeScreen::getAddress, bo.getAddress());
        return lqw;
    }

    /**
     * 新增监控大屏;
     *
     * @param bo 监控大屏;
     * @return 是否新增成功
     */
    @Override
    public Boolean insertByBo(ELargeScreenBo bo) {
        ELargeScreen add = MapstructUtils.convert(bo, ELargeScreen.class);
        validEntityBeforeSave(add);
        boolean flag = baseMapper.insert(add) > 0;
        if (flag) {
            bo.setId(add.getId());
        }
        return flag;
    }

    /**
     * 修改监控大屏;
     *
     * @param bo 监控大屏;
     * @return 是否修改成功
     */
    @Override
    public Boolean updateByBo(ELargeScreenBo bo) {
        ELargeScreen update = MapstructUtils.convert(bo, ELargeScreen.class);
        validEntityBeforeSave(update);
        return baseMapper.updateById(update) > 0;
    }

    /**
     * 保存前的数据校验
     */
    private void validEntityBeforeSave(ELargeScreen entity){
        //TODO 做一些数据校验,如唯一约束
    }

    /**
     * 校验并批量删除监控大屏;信息
     *
     * @param ids     待删除的主键集合
     * @param isValid 是否进行有效性校验
     * @return 是否删除成功
     */
    @Override
    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
        if(isValid){
            //TODO 做一些业务上的校验,判断是否需要校验
        }
        return baseMapper.deleteBatchIds(ids) > 0;
    }

    @Override
    public ElevatorQuantityStatisticsVo queryElevatorQuantityStatistics() {
        ElevatorQuantityStatisticsVo result = new ElevatorQuantityStatisticsVo();
        EElevatorInfoBo elevatorInfoBo = new EElevatorInfoBo();
//        if (!LoginHelper.isSuperAdmin()) {
//            elevatorInfoBo.setDeptId(LoginHelper.getDeptId());
//        }
        List<EElevatorInfoVo> elevatorInfoList = elevatorInfoService.queryList(elevatorInfoBo);
        if (CollectionUtils.isEmpty(elevatorInfoList)) {
            return result;
        }

        List<String> registerCodes = new ArrayList<>();
        long useTotal = elevatorInfoList.stream()
            .peek(item -> {
                if (StringUtils.isNotBlank(item.getRegisterCode())) {
                    registerCodes.add(item.getRegisterCode());
                }
            })
            .filter(item -> Objects.nonNull(item.getUseState()) && Objects.equals(IN_USE, item.getUseState()))
            .count();
        if (CollectionUtils.isNotEmpty(registerCodes)) {
            EBlackBoxInfoBo blackBoxInfoBo = new EBlackBoxInfoBo();
            blackBoxInfoBo.setRegisterCodes(registerCodes);
            Long total = blackBoxInfoService.selectCount(blackBoxInfoBo);

            result.setSmartTotal(total.intValue());
        }
        result.setPaperlessTotal(result.getSmartTotal());
        result.setTotal(elevatorInfoList.size());
        result.setUseTotal((int) useTotal);
        // 故障率: 当月 故障数 / 总运行次数
        Map<String, String> faultTypeMap = dictService.getAllDictByDictType(FAULT_TYPE_DICT_KEY);
        Date now = new Date();
        Date start = DateUtil.beginOfMonth(now);
        Date end = DateUtil.endOfMonth(now);

        Long totalBehavior = behaviorMessageMapper.selectCount(Wrappers.<EBehaviorMessage>lambdaQuery().between(EBehaviorMessage::getHappenTime, start, end));
        if (totalBehavior > 0L) {
            Long totalFault = faultMessageMapper.selectCount(Wrappers.<EFaultMessage>lambdaQuery()
                .in(EFaultMessage::getFaultCode, faultTypeMap.keySet())
                .between(EFaultMessage::getHappenTime, start, end));
            result.setFaultRatio(NumberUtil.div(totalFault, totalBehavior, 4).multiply(new BigDecimal("100").setScale(2, RoundingMode.HALF_UP)));
        }

        return result;
    }

    @Override
    public ElevatorMaintenanceStatisticsVo queryMaintenanceStatistics() {
        ElevatorMaintenanceStatisticsVo result = new ElevatorMaintenanceStatisticsVo();
        EWorkDataBo workDataBo = new EWorkDataBo();
//        if (!LoginHelper.isSuperAdmin()) {
//            workDataBo.setDeptId(LoginHelper.getDeptId());
//        }
        Date now = new Date();
        workDataBo.setMaintenanceStartTime(DateUtils.toLocalDate(DateUtil.beginOfYear(now)));
        workDataBo.setMaintenanceEndTime(DateUtils.toLocalDate(DateUtil.endOfYear(now)));
        // 年度维保信息
        List<EWorkDataVo> workDataList = workDataService.queryList(workDataBo);
        result.setYearTotal(workDataList.size());

        LocalDate monthStartTime = DateUtils.toLocalDate(DateUtil.beginOfMonth(now));
        LocalDate weekStartTime = DateUtils.toLocalDate(DateUtil.beginOfWeek(now));
        LocalDate dayTime = LocalDate.now();
        int monthTotal = 0;
        int weekTotal = 0;
        int dayTotal = 0;

        for (EWorkDataVo workData : workDataList) {
            LocalDate expectTime = workData.getExpectTime();
            // 月度
            assert monthStartTime != null;
            if (expectTime.isAfter(monthStartTime.minusDays(1))
                && expectTime.isBefore(monthStartTime.plusMonths(1))) {
                monthTotal++;
            }
            // 周
            assert weekStartTime != null;
            if (expectTime.isAfter(weekStartTime.minusDays(1))
                && expectTime.isBefore(weekStartTime.plusWeeks(1))) {
                weekTotal++;
            }
            // 日
            if (expectTime.isEqual(dayTime)) {
                dayTotal++;
            }
        }
        result.setMonthTotal(monthTotal);
        result.setWeekTotal(weekTotal);
        result.setDayTotal(dayTotal);
        return result;
    }

    @Override
    public ELargeScreenElevatorVo queryElevatorStatistics() {
        ELargeScreenElevatorVo result = new ELargeScreenElevatorVo();
        EElevatorInfoBo elevatorInfoBo = new EElevatorInfoBo();
//        if (!LoginHelper.isSuperAdmin()) {
//            elevatorInfoBo.setDeptId(LoginHelper.getDeptId());
//        }
        List<EElevatorInfoVo> elevatorInfoList = elevatorInfoService.queryList(elevatorInfoBo);
        if (CollectionUtils.isEmpty(elevatorInfoList)) {
            return result;
        }
        Map<Long, String> deptMap = getDeptInfo();

        List<EMaintenanceInfo> maintenanceInfoList = maintenanceInfoMapper.selectList();
        Map<String, Long> maintenanceInfoMap = StreamUtils.toMap(maintenanceInfoList, EMaintenanceInfo::getName, EMaintenanceInfo::getId);

        List<ETenementInfo> tenementInfoList = tenementInfoMapper.selectList();
        Map<String, Long> tenementInfoMap = StreamUtils.toMap(tenementInfoList, ETenementInfo::getName, ETenementInfo::getId);

        // 维保
        Map<String, List<EElevatorInfoVo>> maintenanceMap = elevatorInfoList.stream()
            .filter(item -> StringUtils.isNotBlank(item.getMaintenanceName()))
            .collect(Collectors.groupingBy(EElevatorInfoVo::getMaintenanceName));
        maintenanceMap.forEach((maintenanceName, eleList) -> {
            ELargeScreenElevatorVo.ELargeScreenMaintenanceInfo maintenanceInfo = new ELargeScreenElevatorVo.ELargeScreenMaintenanceInfo();
            maintenanceInfo.setMaintenanceName(maintenanceName);
            maintenanceInfo.setElevatorTotal(eleList.size());
            maintenanceInfo.setMaintenanceId(maintenanceInfoMap.get(maintenanceName));
            maintenanceInfo.setElevatorList(buildEleList(eleList));
            result.getMaintenanceInfoList().add(maintenanceInfo);
        });
        // 物业
        Map<String, List<EElevatorInfoVo>> tenementMap = elevatorInfoList.stream()
            .filter(item -> StringUtils.isNotBlank(item.getTenementName()))
            .collect(Collectors.groupingBy(EElevatorInfoVo::getTenementName));
        tenementMap.forEach((tenementName, eleList) -> {
            ELargeScreenElevatorVo.ELargeScreenTenementInfo tenementInfo = new ELargeScreenElevatorVo.ELargeScreenTenementInfo();
            tenementInfo.setTenementId(tenementInfoMap.get(tenementName));
            tenementInfo.setTenementName(tenementName);
            tenementInfo.setElevatorTotal(eleList.size());
            tenementInfo.setElevatorList(buildEleList(eleList));
            result.getTenementInfoList().add(tenementInfo);
        });
        // 部门(区域)
        Map<Long, List<EElevatorInfoVo>> eleDeptMap = elevatorInfoList.stream()
            .filter(item -> Objects.nonNull(item.getDeptId()))
            .collect(Collectors.groupingBy(EElevatorInfoVo::getDeptId));
        eleDeptMap.forEach((deptId, eleList) -> {
            ELargeScreenElevatorVo.ELargeScreenDeptInfo deptInfo = new ELargeScreenElevatorVo.ELargeScreenDeptInfo();
            deptInfo.setDeptName(deptMap.getOrDefault(deptId, ""));
            deptInfo.setDeptId(deptId);
            deptInfo.setElevatorTotal(eleList.size());
            deptInfo.setElevatorList(buildEleList(eleList));
            result.getDeptInfoList().add(deptInfo);
        });
        return result;
    }

    @Override
    public ELargeScreenMaintenanceVo queryMaintenanceTaskStatistics(String maintenanceName) {
        ELargeScreenMaintenanceVo result = new ELargeScreenMaintenanceVo();
        boolean condition = StringUtils.isNotBlank(maintenanceName);
        Integer companyTotal = maintenanceInfoMapper.selectCount(Wrappers.<EMaintenanceInfo>lambdaQuery()
            .like(condition, EMaintenanceInfo::getName, maintenanceName)).intValue();
        result.setCompanyTotal(companyTotal);
        // 工作总数量
        LambdaQueryWrapper<EWorkData> wrapper = Wrappers.<EWorkData>lambdaQuery()
            .eq(EWorkData::getWorkType, WorkType.MAINTENANCE.getType())
            .like(condition, EWorkData::getMaintenance, maintenanceName);
        Integer workTotal = this.workDataMapper.selectCount(wrapper).intValue();
        result.setWorkTotal(workTotal);
        // 完成工作数
        wrapper.in(EWorkData::getState, WorkStateEnum.complete());
        Integer completeTotal = this.workDataMapper.selectCount(wrapper).intValue();
        result.setCompleteWorkTotal(completeTotal);
        // 未完成
        LambdaQueryWrapper<EWorkData> unCompleteWrapper = Wrappers.<EWorkData>lambdaQuery()
            .eq(EWorkData::getWorkType, WorkType.MAINTENANCE.getType())
            .like(condition, EWorkData::getMaintenance, maintenanceName);
        unCompleteWrapper.in(EWorkData::getState, WorkStateEnum.unComplete());
        List<EWorkData> unCompleteWorkList = this.workDataMapper.selectList(unCompleteWrapper);
        int unCompleteElevatorTotal = unCompleteWorkList.stream().map(EWorkData::getElevatorNum).collect(Collectors.toSet()).size();
        result.setOverdueTaskTotal(unCompleteWorkList.size());
        result.setOverdueElevatorTotal(unCompleteElevatorTotal);

        // 超期
        EWorkDataBo workDataBo = new EWorkDataBo();
//        if (!LoginHelper.isSuperAdmin()) {
//            workDataBo.setDeptId(LoginHelper.getDeptId());
//        }

        LocalDate currentDate = LocalDate.now();
        LocalDate yearStartDate = currentDate.withDayOfYear(1).withYear(currentDate.getYear());
        workDataBo.setMaintenance(maintenanceName);
        workDataBo.setMaintenanceStartTime(yearStartDate);
        workDataBo.setMaintenanceEndTime(currentDate);
        // 年度维保信息
        List<EWorkDataVo> workDataList = workDataService.queryList(workDataBo);
        BigDecimal total = new BigDecimal(workDataList.size());
        Map<Integer, List<EWorkDataVo>> overdueWorkMap = workDataList.stream()
            .filter(item -> item.getState() < WorkStateEnum.COMPLETE.getState())
            .collect(Collectors.groupingBy(item -> item.getExpectTime().getMonthValue()));

        BigDecimal percent = new BigDecimal("100");
        BigDecimal tempRatio = BigDecimal.ZERO;
        for (ELargeScreenMaintenanceVo.OverdueTaskInfo info : result.getOverdueInfoList()) {
            if (overdueWorkMap.containsKey(info.getMonth())) {
                List<EWorkDataVo> overdueList = overdueWorkMap.get(info.getMonth());
                int overdueElevatorTotal = overdueList.stream().map(EWorkDataVo::getElevatorNum).collect(Collectors.toSet()).size();
                info.setOverdueElevatorTotal(overdueElevatorTotal);
                BigDecimal ratio = new BigDecimal(overdueList.size()).multiply(percent).divide(total, 2, RoundingMode.HALF_UP);
                tempRatio = ratio.add(tempRatio);
                if (tempRatio.compareTo(percent) > 0) {
                    ratio = percent.subtract(tempRatio);
                }
                info.setOverdueRatio(ratio);
            }
        }
        // 今日维保数量
        long dayTaskTotal = workDataList.stream().filter(item -> currentDate.isEqual(item.getExpectTime())).count();
        result.setDayTaskTotal((int)dayTaskTotal);

        return result;
    }

    @Override
    public List<ELargeScreenRepairTrendChartVo> queryRepairTrendChart() {
        LocalDate now = LocalDate.now();
        return IntStream.range(1, 13).mapToObj(month -> {
            ELargeScreenRepairTrendChartVo trendChartVo = new ELargeScreenRepairTrendChartVo(month);
            LocalDate start = LocalDate.of(now.getYear(), month, 1);
            LocalDate end = start.with(TemporalAdjusters.lastDayOfMonth());

            List<EWorkData> workDataList = queryRepairList(start, end);
            if (CollectionUtils.isNotEmpty(workDataList)) {
                trendChartVo.setElevatorTotal(StreamUtils.toSet(workDataList, EWorkData::getElevatorNum).size());
            }
            return trendChartVo;
        }).collect(Collectors.toList());
    }


    @Override
    public List<ELargeScreenRepairPieChartVo> queryRepairPieChart(ELargeScreenRepairBo bo) {
        List<EWorkData> workDataList = this.queryRepairList(bo.getStartTime(), bo.getEndTime());
        // 维修计数
        Map<Integer, Long> repairCountMap = workDataList.stream().collect(Collectors.groupingBy(EWorkData::getPlanType, Collectors.counting()));
        Map<String, String> repairPlanTypeMap = dictService.getAllDictByDictType(ELE_REPAIR_PLAN_TYPE_KEY);

        BigDecimal percent = new BigDecimal("100");
        AtomicReference<BigDecimal> ratioReference = new AtomicReference<>(BigDecimal.ZERO);
        return repairPlanTypeMap.keySet().stream().map(item -> {
            Integer repairPlanType = Integer.valueOf(item);
            ELargeScreenRepairPieChartVo pieChartVo = new ELargeScreenRepairPieChartVo(repairPlanType);
            if (CollectionUtils.isNotEmpty(workDataList) && repairCountMap.containsKey(repairPlanType)) {
                BigDecimal ratio = calcRatio(NumberUtil.div(new BigDecimal(repairCountMap.get(repairPlanType)), workDataList.size(), 4), percent, ratioReference);
                pieChartVo.setRatio(ratio);
            }
            return pieChartVo;
        }).collect(Collectors.toList());
    }

    @Override
    public List<ELargeScreenRepairBarChartVo> queryRepairBarChart(ELargeScreenRepairBo bo) {
        List<EWorkData> workDataList = this.queryRepairList(bo.getStartTime(), bo.getEndTime());
        Map<Integer, List<EWorkData>> repairWorkDataMap = StreamUtils.groupByKey(workDataList, EWorkData::getPlanType);
        Map<String, String> repairPlanTypeMap = dictService.getAllDictByDictType(ELE_REPAIR_PLAN_TYPE_KEY);

        return repairPlanTypeMap.keySet().stream().map(item -> {
            Integer repairPlanType = Integer.valueOf(item);
            ELargeScreenRepairBarChartVo barChartVo = new ELargeScreenRepairBarChartVo(repairPlanType);
            barChartVo.setElevatorTotal(StreamUtils.toSet(repairWorkDataMap.get(repairPlanType), EWorkData::getElevatorNum).size());
            return barChartVo;
        }).collect(Collectors.toList());
    }

    @Override
    public ELargeScreenElevatorInfoVo queryElevatorDetail(String elevatorNum) {
        EElevatorInfoVo elevatorInfo = elevatorInfoService.selectByElevatorNum(elevatorNum);

        ELargeScreenElevatorInfoVo result = new ELargeScreenElevatorInfoVo();
        if (Objects.isNull(elevatorInfo)) {
            return result;
        }
        result.setElevatorNum(elevatorInfo.getElevatorNum());
        result.setElevatorName(elevatorInfo.getElevatorName());
        result.setElevatorAddress(elevatorInfo.getElevatorAddress());
        result.setTenementName(elevatorInfo.getTenementName());
        result.setMaintenanceName(elevatorInfo.getMaintenanceName());

        LocalDate now = LocalDate.now();
        LocalDate start = now.with(TemporalAdjusters.firstDayOfMonth());
        LocalDate end = now.with(TemporalAdjusters.lastDayOfMonth());
        List<EBehaviorStatistics> behaviorStatisticsList = behaviorStatisticsMapper.selectList(Wrappers.<EBehaviorStatistics>lambdaQuery()
            .eq(EBehaviorStatistics::getElevatorNum, elevatorNum)
            .between(EBehaviorStatistics::getStatisticsTime, start, end));
        behaviorStatisticsList.forEach(item -> {
            result.setOpenCloseNum(result.getOpenCloseNum() +  item.getOpenCloseNum());
            result.setBehaviorTotal(result.getBehaviorTotal() + item.getUpNum() + item.getDownNum());
            result.setMileage(result.getMileage().add(item.getMileage()));
        });
        // 公里 -> 米
        result.setMileage(result.getMileage().multiply(new BigDecimal("1000")));

        return result;
    }

    @Override
    public List<ELargeScreenDetailFaultStatisticsVo> queryElevatorDetailFaultStatistics(String elevatorNum) {
        Date now = new Date();
        Date start = DateUtil.beginOfYear(now);
        Date end = DateUtil.endOfYear(now);

        Map<String, String> faultMap = dictService.getAllDictByDictType(FAULT_TYPE_DICT_KEY);

        Map<String, ELargeScreenDetailFaultStatisticsVo> faultStatisticsMap = faultMap.entrySet().stream()
            .map(item -> new ELargeScreenDetailFaultStatisticsVo(item.getKey(), item.getValue()))
            .collect(Collectors.toMap(ELargeScreenDetailFaultStatisticsVo::getFaultCode, Function.identity()));

        List<EFaultMessage> faultMessages = faultMessageMapper.selectList(Wrappers.<EFaultMessage>lambdaQuery()
            .eq(EFaultMessage::getElevatorNum, elevatorNum)
            .eq(EFaultMessage::getState, StateConstant.FINISHED)
            .in(EFaultMessage::getFaultCode, faultMap.keySet())
            .between(EFaultMessage::getHappenTime, start, end));
        for (EFaultMessage faultMessage : faultMessages) {
            if (faultStatisticsMap.containsKey(faultMessage.getFaultCode())) {
                ELargeScreenDetailFaultStatisticsVo statisticsVo = faultStatisticsMap.get(faultMessage.getFaultCode());
                statisticsVo.setNum(statisticsVo.getNum() + 1);
            }
        }

        return faultStatisticsMap.values().stream()
            .sorted(Comparator.comparing(ELargeScreenDetailFaultStatisticsVo::getNum).reversed())
            .collect(Collectors.toList());
    }

    @Override
    public List<ELargeScreenRescueStatisticsVo> queryRescueYearStatistics() {
        Map<Integer, ELargeScreenRescueStatisticsVo> rescueStatisticsMap = IntStream.range(1, 13)
            .mapToObj(ELargeScreenRescueStatisticsVo::new)
            .collect(Collectors.toMap(ELargeScreenRescueStatisticsVo::getMonth, Function.identity()));
        Date now = new Date();
        Date start = DateUtil.beginOfYear(now);
        Date end = DateUtil.endOfYear(now);
        List<EWorkData> workDataList = queryCompleteRescueList(start, end);
        Calendar cal = Calendar.getInstance();
        workDataList.stream().filter(item -> Objects.nonNull(item.getHappenTime())).forEach(item -> {
            cal.setTime(item.getHappenTime());
            int month = cal.get(Calendar.MONTH) + 1;
            ELargeScreenRescueStatisticsVo statisticsVo = rescueStatisticsMap.get(month);
            statisticsVo.setRescueNum(statisticsVo.getRescueNum() + 1);
        });

        return rescueStatisticsMap.values().stream()
            .sorted(Comparator.comparing(ELargeScreenRescueStatisticsVo::getMonth))
            .collect(Collectors.toList());
    }

    @Override
    public List<ELargeScreenRescueAreaStatisticsVo> queryRescueAreaStatistics(ELargeScreenRescueBo bo) {
        checkRescueBo(bo);
        List<EWorkData> workDataList = queryCompleteRescueList(bo.getStartTime(), bo.getEndTime());
        if (CollectionUtils.isEmpty(workDataList)) {
            return new ArrayList<>();
        }
        Map<Long, String> deptMap = getDeptInfo();
        Map<Long, Long> deptRescueMap = workDataList.stream().collect(Collectors.groupingBy(EWorkData::getDeptId, Collectors.counting()));
        return deptRescueMap.entrySet().stream()
            .map(item -> new ELargeScreenRescueAreaStatisticsVo(item.getKey(), deptMap.getOrDefault(item.getKey(), StrUtil.EMPTY), item.getValue().intValue()))
            .sorted(Comparator.comparing(ELargeScreenRescueAreaStatisticsVo::getRescueNum).reversed())
            .collect(Collectors.toList());
    }

    @Override
    public ELargeScreenRescueTimeStatisticsVo queryRescueTimeStatistics(ELargeScreenRescueBo bo) {
        checkRescueBo(bo);
        ELargeScreenRescueTimeStatisticsVo result = new ELargeScreenRescueTimeStatisticsVo();
        List<EWorkData> workDataList = queryCompleteRescueList(bo.getStartTime(), bo.getEndTime());
        if (CollectionUtils.isEmpty(workDataList)) {
            return result;
        }
        Map<Integer, ELargeScreenRescueTimeStatisticsVo.ReceiveTimeInfo> receiveTimeInfoMap =
            StreamUtils.toMap(result.getReceiveTimeList(), ELargeScreenRescueTimeStatisticsVo.ReceiveTimeInfo::getTimeType, Function.identity());

        Map<Integer, ELargeScreenRescueTimeStatisticsVo.RescueTimeInfo> rescueTimeInfoMap =
            StreamUtils.toMap(result.getRescueTimeList(), ELargeScreenRescueTimeStatisticsVo.RescueTimeInfo::getTimeType, Function.identity());

        workDataList.stream()
            .filter(item -> Objects.nonNull(item.getHappenTime())
                && Objects.nonNull(item.getReceiveTime()) && Objects.nonNull(item.getFinishTime()))
            .forEach(item -> {
                // 接收时间占比
                Integer receiveTimeType  = RECEIVE_TIME_GREATER_30MIN;
                int receiveTime = (int) DateUtil.between(item.getHappenTime(), item.getReceiveTime(), DateUnit.MINUTE);
                if (receiveTime <= TIME_15MIN) {
                    receiveTimeType = RECEIVE_TIME_15MIN;
                } else if (receiveTime <= TIME_30MIN) {
                    receiveTimeType = RECEIVE_TIME_30MIN;
                }
                ELargeScreenRescueTimeStatisticsVo.ReceiveTimeInfo receiveTimeInfo = receiveTimeInfoMap.get(receiveTimeType);
                receiveTimeInfo.setNum(receiveTimeInfo.getNum() + 1);

                // 救援时间占比
                Integer rescueTimeType  = RESCUE_TIME_GREATER_2HOUR;
                int rescueTime = (int) DateUtil.between(item.getReceiveTime(), item.getFinishTime(), DateUnit.MINUTE);
                if (rescueTime <= TIME_30MIN) {
                    rescueTimeType = RESCUE_TIME_30MIN;
                } else if (rescueTime <= TIME_2HOUR) {
                    rescueTimeType = RESCUE_TIME_2HOUR;
                }
                ELargeScreenRescueTimeStatisticsVo.RescueTimeInfo rescueTimeInfo = rescueTimeInfoMap.get(rescueTimeType);
                rescueTimeInfo.setNum(rescueTimeInfo.getNum() + 1);

            });
        BigDecimal percent = new BigDecimal("100");
        Integer total = workDataList.size();
        AtomicReference<BigDecimal> receiveRatioReference = new AtomicReference<>(BigDecimal.ZERO);
        AtomicReference<BigDecimal> rescueRatioReference = new AtomicReference<>(BigDecimal.ZERO);
        result.setReceiveTimeList(receiveTimeInfoMap.values().stream()
            .peek(item -> item.setRatio(calcRatio(NumberUtil.div(item.getNum(), total, 4), percent, receiveRatioReference)))
            .sorted(Comparator.comparing(ELargeScreenRescueTimeStatisticsVo.ReceiveTimeInfo::getTimeType))
            .collect(Collectors.toList()));
        result.setRescueTimeList(rescueTimeInfoMap.values().stream()
            .peek(item -> item.setRatio(calcRatio(NumberUtil.div(item.getNum(), total, 4), percent, rescueRatioReference)))
            .sorted(Comparator.comparing(ELargeScreenRescueTimeStatisticsVo.RescueTimeInfo::getTimeType))
            .collect(Collectors.toList()));

        return result;
    }

    /**
     * 占比计算
     *
     * @param currRatio      当前占比
     * @param percent        百分比
     * @param ratioReference 占比中间量
     * @return 实际占比
     */
    @NotNull
    private BigDecimal calcRatio(BigDecimal currRatio, BigDecimal percent, AtomicReference<BigDecimal> ratioReference) {
        BigDecimal ratio = currRatio.multiply(percent).setScale(2, RoundingMode.HALF_UP);
        BigDecimal tempRatio = ratioReference.get().add(ratio);
        if (tempRatio.compareTo(percent) > 0) {
            ratio = percent.subtract(ratioReference.get());
        } else {
            ratioReference.set(tempRatio);
        }
        return ratio;
    }

    @Override
    public List<ELargeScreenRescueFaultStatisticsVo> queryRescueFaultCauseStatistics(ELargeScreenRescueBo bo) {
        checkRescueBo(bo);
        List<EWorkData> workDataList = queryCompleteRescueList(bo.getStartTime(), bo.getEndTime());
        if (CollectionUtils.isEmpty(workDataList)) {
            return new ArrayList<>();
        }
        Map<String, Long> deptRescueMap = workDataList.stream()
            .filter(item -> StringUtils.isNotBlank(item.getFaultCause()))
            .collect(Collectors.groupingBy(EWorkData::getFaultCause, Collectors.counting()));

        return deptRescueMap.entrySet().stream()
            .map(item -> new ELargeScreenRescueFaultStatisticsVo(item.getKey(), item.getValue().intValue()))
            .sorted(Comparator.comparing(ELargeScreenRescueFaultStatisticsVo::getRescueNum).reversed())
            .collect(Collectors.toList());
    }

    @Override
    public List<ELargeScreenRescueRankStatisticsVo> queryRescueMaintenanceRankStatistics(ELargeScreenRescueBo bo) {
        checkRescueBo(bo);
        List<EWorkData> workDataList = queryCompleteRescueList(bo.getStartTime(), bo.getEndTime());
        if (CollectionUtils.isEmpty(workDataList)) {
            return new ArrayList<>();
        }
        Map<String, List<EWorkData>> maintenanceRescueMap = workDataList.stream()
            .filter(item -> StringUtils.isNotBlank(item.getMaintenance()))
            .collect(Collectors.groupingBy(EWorkData::getMaintenance));

        // 救援次数倒序、救援平均时间升序
        return maintenanceRescueMap.entrySet().stream().map(this::buildRescueRankStatisticsVo)
            .sorted(Comparator.comparing(ELargeScreenRescueRankStatisticsVo::getRescueTotal, Comparator.reverseOrder())
                .thenComparing(ELargeScreenRescueRankStatisticsVo::getAvgRescueTime))
            .collect(Collectors.toList());
    }

    /**
     * 构建救援排名信息
     *
     * @param item 救援数据
     * @return 救援排名信息
     */
    @NotNull
    private ELargeScreenRescueRankStatisticsVo buildRescueRankStatisticsVo(Map.Entry<String, List<EWorkData>> item) {
        ELargeScreenRescueRankStatisticsVo rankStatisticsVo = new ELargeScreenRescueRankStatisticsVo();
        rankStatisticsVo.setMaintenanceName(item.getKey());
        rankStatisticsVo.setRescueTotal(item.getValue().size());
        Set<String> eleNumSet = new HashSet<>();
        Integer totalRescueTime = item.getValue().stream()
            .filter(rescue -> Objects.nonNull(rescue.getHappenTime())
                && Objects.nonNull(rescue.getReceiveTime()) && Objects.nonNull(rescue.getFinishTime()))
            .peek(rescue -> eleNumSet.add(rescue.getElevatorNum()))
            .mapToInt(rescue -> (int) DateUtil.between(rescue.getReceiveTime(), rescue.getFinishTime(), DateUnit.MINUTE)).sum();

        rankStatisticsVo.setRescueElevatorTotal(eleNumSet.size());

        rankStatisticsVo.setAvgRescueTime(NumberUtil.div(totalRescueTime, rankStatisticsVo.getRescueTotal(), 2).intValue());
        return rankStatisticsVo;
    }

    /**
     * 查询维修数据
     *
     * @param start 开始时间
     * @param end   结束时间
     * @return 维修数据
     */
    private List<EWorkData> queryRepairList(LocalDate start, LocalDate end) {
        return workDataMapper.selectList(Wrappers.<EWorkData>lambdaQuery()
            .select(EWorkData::getElevatorNum, EWorkData::getPlanType)
            .eq(EWorkData::getWorkType, WorkType.REPAIR.getType())
            .eq(EWorkData::getRepairAudit, StateConstant.FINISHED)
            .between(EWorkData::getExpectTime, start, end));
    }

    /**
     * 构建页面电梯信息
     *
     * @param eleList 电梯列表
     * @return 电梯信息
     */
    @NotNull
    private List<ELargeScreenElevatorVo.ELargeScreenElevatorInfo> buildEleList(List<EElevatorInfoVo> eleList) {
        return eleList.stream().map(item -> {
            ELargeScreenElevatorVo.ELargeScreenElevatorInfo elevatorInfo = new ELargeScreenElevatorVo.ELargeScreenElevatorInfo();
            elevatorInfo.setElevatorName(item.getElevatorName());
            elevatorInfo.setElevatorNum(item.getElevatorNum());
            elevatorInfo.setEleId(item.getEleId());
            return elevatorInfo;
        }).collect(Collectors.toList());
    }

    /**
     * 获取部门信息
     *
     * @return
     */
    private Map<Long, String> getDeptInfo() {
        List<SysDeptVo> deptList = deptService.selectDeptList(new SysDeptBo());
        return StreamUtils.toMap(deptList, SysDeptVo::getDeptId, SysDeptVo::getDeptName);
    }


    /**
     * 查询完成救援列表
     *
     * @param start 开始时间
     * @param end   结束时间
     * @return 救援信息列表
     */
    private List<EWorkData> queryCompleteRescueList(Date start, Date end) {
        return workDataMapper.selectList(Wrappers.<EWorkData>lambdaQuery()
            .eq(EWorkData::getWorkType, WorkType.RESCUE.getType())
            .in(EWorkData::getState, WorkStateEnum.complete())
            .between(EWorkData::getHappenTime, start, end));
    }

    /**
     * 校验大屏救援查询参数
     *
     * @param bo 查询参数
     */
    private void checkRescueBo(ELargeScreenRescueBo bo) {
        if (Objects.isNull(bo.getStartTime()) || Objects.isNull(bo.getEndTime())) {
            Date now = new Date();
            bo.setStartTime(DateUtil.beginOfWeek(now));
            bo.setEndTime(DateUtil.endOfWeek(now));
        }
    }

}
