package com.tofly.scada.handle;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.tofly.em.IndexTypeInfoEnum;
import com.tofly.em.StatisticsTypeEnum;
import com.tofly.scada.entity.Allocation;
import com.tofly.scada.entity.ScadaLjFlow;
import com.tofly.scada.entity.StatisticsScada;
import com.tofly.scada.entity.dto.ScadaQueryDto;
import com.tofly.scada.entity.vo.ScadaIndexVo;
import com.tofly.scada.mapper.ScadaMapper;
import com.tofly.scada.service.AllocationService;
import com.tofly.scada.service.ScadaLjFlowService;
import com.tofly.scada.service.ScadaReportService;
import com.tofly.scada.service.StatisticsScadaService;
import com.tofly.scada.util.SnowFlake;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * scada数据处理
 *
 * @author JF
 */
@Component
@Slf4j
@AllArgsConstructor
public class ScadaHandler {

    private final ScadaMapper scadaMapper;

    private final AllocationService allocationService;

    private final StatisticsScadaService statisticsScadaService;

    private final ScadaLjFlowService scadaLjFlowService;

    private final ScadaReportService scadaReportService;

    public void syncTwentyFourHourData(Date startDate, Date endDate) {
        List<Date> dates = findDates(startDate, endDate, 0);
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                4,
                8,
                120,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(24),
                (r)->{
                    Thread t = new Thread(r);
                    t.setName("thread-" + ((int)(Math.random()*1000)));
                    return t;},
                new ThreadPoolExecutor.CallerRunsPolicy());
        dates.forEach(date -> {
                    //整个日历调整时间
                    Calendar calendar = Calendar.getInstance();
                    calendar.setTime(date);
                    calendar.set(Calendar.MINUTE, 0);
                    calendar.set(Calendar.SECOND, 0);
                    Date startTime = calendar.getTime();
                    //拉长时间到59分59秒
                    calendar.set(Calendar.MINUTE, 59);
                    calendar.set(Calendar.SECOND, 59);
                    Date endTime = calendar.getTime();
                    //保存数据
                    threadPoolExecutor.execute(() -> saveHistoryCode(startTime, endTime, StatisticsTypeEnum.ZERO_HOUR.getType()));
                }
        );
    }

    @Transactional(rollbackFor = Exception.class)
    public void sendCumulativeFlowDifference(String startTime, String endTime) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(DateUtil.parse(startTime,"yyyy-MM-dd HH:mm:ss"));
        cal.add(Calendar.MINUTE, -5);
        ScadaQueryDto dto = new ScadaQueryDto();
        dto.setStartDate(DateUtil.format(cal.getTime(), "yyyy-MM-dd HH:mm:ss"));
        dto.setEndDate(endTime);
        dto.setTableName("SCADA_HISTORY");
        dto.setTableType(1);
        List<String> indexTypeList = new ArrayList<>();
        indexTypeList.add(IndexTypeInfoEnum.CUM_FLOW.getId());
        dto.setIndexTypeList(indexTypeList);
        List<ScadaIndexVo> stationIndexVos = scadaMapper.viewScadaDataShort(dto);
        List<Allocation> codes = allocationService.list(new QueryWrapper<Allocation>().lambda()
                .eq(Allocation::getType, IndexTypeInfoEnum.CUM_FLOW.getId()));
        List<Map<String, String>> dates = findDiffFiveDates(DateUtil.parse(startTime, "yyyy-MM-dd HH:mm:ss"),
                DateUtil.parse(endTime, "yyyy-MM-dd HH:mm:ss"));
        List<ScadaLjFlow> scadaLjFlowList = new ArrayList<>();
        dates.forEach(info ->  {
            List<ScadaIndexVo> stationIndexVosF = stationIndexVos.stream()
                    .filter(e -> e.getScadaTime().compareTo(info.get("startTime")) >= 0 && e.getScadaTime().compareTo(info.get("endTime")) <= 0)
                    .collect(Collectors.toList());
            if (CollUtil.isEmpty(stationIndexVosF)) {
                return;
            }
            Calendar calendar = Calendar.getInstance();
            calendar.setTime(DateUtil.parse(info.get("startTime"),"yyyy-MM-dd HH:mm:ss"));
            calendar.add(Calendar.MINUTE, -1);
            calendar.set(Calendar.SECOND, 59);
            Date endTimeLast = calendar.getTime();
            calendar.add(Calendar.MINUTE, -4);
            calendar.set(Calendar.SECOND, 0);
            Date startTimeLast = calendar.getTime();
            String start = DateUtil.format(startTimeLast,"yyyy-MM-dd HH:mm:ss");
            String end = DateUtil.format(endTimeLast,"yyyy-MM-dd HH:mm:ss");
            List<ScadaIndexVo> stationIndexVosLast = stationIndexVos.stream()
                    .filter(e -> e.getScadaTime().compareTo(start) >= 0 && e.getScadaTime().compareTo(end) <= 0)
                    .collect(Collectors.toList());
            codes.forEach(code -> {
                List<ScadaIndexVo> stationIndexVosFinal = stationIndexVosF.stream().filter(e -> e.getIndexCode().equals(code.getVariableCode())).collect(Collectors.toList());
                ScadaLjFlow scadaLjFlow = new ScadaLjFlow();
                scadaLjFlow.setScadaTime(DateUtil.parse(info.get("startTime"), "yyyy-MM-dd HH:mm:ss"));
                scadaLjFlow.setId(SnowFlake.nextId());
                scadaLjFlow.setCode(code.getVariableCode());
                scadaLjFlow.setOldValue(0.0);
                scadaLjFlow.setNewValue(0.0);
                scadaLjFlow.setDifferenceValue(0.0);
                if (CollUtil.isEmpty(stationIndexVosFinal)) {
                    scadaLjFlowList.add(scadaLjFlow);
                    return;
                }
                double newVal = Double.parseDouble(stationIndexVosFinal.get(0).getIndexValue());
                double oldVal = 0;
                if (CollUtil.isNotEmpty(stationIndexVosLast)) {
                    List<ScadaIndexVo> stationIndexVosLastFinal = stationIndexVosLast.stream().filter(e -> e.getIndexCode().equals(code.getVariableCode())).collect(Collectors.toList());
                    if (CollUtil.isNotEmpty(stationIndexVosLastFinal)) {
                        oldVal = Double.parseDouble(stationIndexVosLastFinal.get(0).getIndexValue());
                    }
                } else {
                    oldVal = Double.parseDouble(stationIndexVosFinal.get(stationIndexVosFinal.size() - 1).getIndexValue());
                }
                if (oldVal == 0 && newVal != 0) {
                    oldVal = newVal;
                }
                if (newVal == 0 && oldVal != 0) {
                    newVal = oldVal;
                }
                Double difVal = new BigDecimal(newVal).subtract(new BigDecimal(oldVal)).doubleValue();
                scadaLjFlow.setOldValue(oldVal);
                scadaLjFlow.setNewValue(newVal);
                scadaLjFlow.setDifferenceValue(difVal);
                scadaLjFlowList.add(scadaLjFlow);
            });
        });
        if (CollUtil.isEmpty(scadaLjFlowList)) {
            return;
        }
        // 先清空范围内时间数据 再插入
        scadaLjFlowService.remove(new QueryWrapper<ScadaLjFlow>().between("TO_CHAR(SCADA_TIME, 'yyyy-mm-dd hh24:mi:ss')", startTime, endTime));
        scadaLjFlowService.saveBatch(scadaLjFlowList);
    }


    /* ***************************************************自定义方法******************************************** */


    /**
     * 获取日期列表
     *
     * @param start 开始时间:2022-01-01 00:00:00
     * @param end   结束时间:2022-10-01 14:00:00
     * @param type  统计类型(0小时，1天，2月)
     * @return 日期列表
     */
    public static List<Date> findDates(Date start, Date end, Integer type) {
        Calendar s = Calendar.getInstance();
        s.setTime(start);
        Calendar e = Calendar.getInstance();
        e.setTime(end);
        if (s.get(Calendar.YEAR) == e.get(Calendar.YEAR)
                && s.get(Calendar.MONTH) == e.get(Calendar.MONTH)
                && s.get(Calendar.DAY_OF_MONTH) == e.get(Calendar.DAY_OF_MONTH)
                && s.get(Calendar.HOUR_OF_DAY) == e.get(Calendar.HOUR_OF_DAY)
                && type == 0) {
            List<Date> dates = new ArrayList<>();
            dates.add(start);
            return dates;
        }
        if (s.get(Calendar.YEAR) == e.get(Calendar.YEAR)
                && s.get(Calendar.MONTH) == e.get(Calendar.MONTH)
                && s.get(Calendar.DAY_OF_MONTH) == e.get(Calendar.DAY_OF_MONTH)
                && type == 1) {
            List<Date> dates = new ArrayList<>();
            dates.add(start);
            return dates;
        }
        if (s.get(Calendar.YEAR) == e.get(Calendar.YEAR)
                && s.get(Calendar.MONTH) == e.get(Calendar.MONTH)
                && type == 2) {
            List<Date> dates = new ArrayList<>();
            dates.add(start);
            return dates;
        }
        //结束时间在开始时间之后
        List<Date> dates = new ArrayList<>();
        dates.add(s.getTime());
        while (end.getTime() >= s.getTime().getTime()) {
            if (type == 0) {
                s.add(Calendar.HOUR_OF_DAY, 1);
            } else if (type == 1) {
                s.add(Calendar.DAY_OF_MONTH, 1);
            } else if (type == 2) {
                s.add(Calendar.MONTH, 1);
            }
            dates.add(s.getTime());
        }
        dates.remove(dates.size() - 1);
        return dates;
    }

    /**
     * 分code进行存储（用历史表数据）
     *
     * @param startTime 开始时间
     * @param endTime 结束时间
     * @param dateType 统计类型
     */
    @Transactional(rollbackFor = Exception.class)
    public void saveHistoryCode(Date startTime, Date endTime, Integer dateType) {
        List<Allocation> codes = allocationService.list();
        List<StatisticsScada> statisticsScadaList = new ArrayList<>();
        ScadaQueryDto dto = new ScadaQueryDto();
        String startDate = DateUtil.format(startTime, "yyyy-MM-dd HH:mm:ss");
        String endDate = DateUtil.format(endTime, "yyyy-MM-dd HH:mm:ss");
        dto.setStartDate(startDate);
        dto.setEndDate(endDate);
        dto.setTableName("SCADA_HISTORY");
        dto.setTableType(1);
        List<ScadaIndexVo> stationIndexVos = scadaMapper.viewScadaDataShort(dto);
        if(CollUtil.isEmpty(stationIndexVos)) {
            return;
        }
        // 获取上一时段指标数据列表
        Calendar calendarStart = Calendar.getInstance();
        Calendar calendarEnd = Calendar.getInstance();
        calendarStart.setTime(startTime);
        calendarEnd.setTime(endTime);
        if (Objects.equals(dateType, StatisticsTypeEnum.ZERO_HOUR.getType())) {
            calendarStart.add(Calendar.HOUR, -1);
            calendarEnd.add(Calendar.HOUR, -1);
        } else {
            calendarStart.add(Calendar.DATE, -1);
            calendarEnd.add(Calendar.DATE, -1);
        }
        dto.setStartDate(DateUtil.format(calendarStart.getTime(),"yyyy-MM-dd HH:mm:ss"));
        dto.setEndDate(DateUtil.format(calendarEnd.getTime(),"yyyy-MM-dd HH:mm:ss"));
        List<ScadaIndexVo> stationIndexVosLast = scadaMapper.viewScadaDataShort(dto);
//        List<ScadaReport> scadaReportList = scadaReportService.list();
        codes.forEach(code -> {
            List<ScadaIndexVo> stationIndexVosFinal = stationIndexVos.stream()
                    .filter(e -> e.getIndexCode().equals(code.getVariableCode())
                            && new BigDecimal(e.getIndexValue()).compareTo(BigDecimal.ZERO) > 0)
                    .collect(Collectors.toList());
            if (CollUtil.isEmpty(stationIndexVosFinal)) {
                return;
            }
            List<BigDecimal> indexValList = stationIndexVosFinal.stream()
                    .map(s -> new BigDecimal(s.getIndexValue()))
                    .filter(e -> e.compareTo(BigDecimal.ZERO) != 0)
                    .collect(Collectors.toList());
            if (CollUtil.isEmpty(indexValList)) {
                return;
            }
            // 计算最小值
            List<ScadaIndexVo> lastList = null;
            if (CollUtil.isNotEmpty(stationIndexVosLast)) {
                lastList = stationIndexVosLast.stream()
                        .filter(e -> e.getIndexCode().equals(code.getVariableCode())
                                && BigDecimal.valueOf(Double.parseDouble(e.getIndexValue())).compareTo(BigDecimal.ZERO) > 0)
                        .collect(Collectors.toList());
            }
            BigDecimal minVal;
            if (CollUtil.isNotEmpty(lastList) && !code.getType().equals(IndexTypeInfoEnum.INSTANTANEOUS_FLOW.getId())) {
                minVal = Collections.max(lastList.stream().map(s -> new BigDecimal(s.getIndexValue())).collect(Collectors.toList()));
            } else {
                minVal = Collections.min(indexValList);
            }
            // 计算最大值
            BigDecimal maxVal = Collections.max(indexValList);
            // 计算平均值
//            BigDecimal average = indexValList.stream().mapToDouble(Double::valueOf).average().getAsDouble();
            // 计算累计值
//            BigDecimal sum = indexValList.stream().mapToDouble(Double::valueOf).sum();
            // 计算达标率
//            int complianceNum = 0;
            BigDecimal totalNum = BigDecimal.valueOf(indexValList.size());
//            for (BigDecimal indexVal : indexValList) {
//                List<ScadaReport> reportList = scadaReportList.stream()
//                        .filter(e -> Objects.equals(e.getAllocationId(), String.valueOf(code.getId()))
//                                && indexVal.compareTo(new BigDecimal(e.getReportLower())) >= 0
//                                && indexVal.compareTo(new BigDecimal(e.getReportUpper())) <= 0)
//                        .collect(Collectors.toList());
//                if (CollUtil.isEmpty(reportList)) {
//                    complianceNum ++;
//                }
//            }
//            BigDecimal pass = BigDecimal.valueOf(complianceNum).divide(totalNum, 4, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100));
            StatisticsScada statisticsScada = new StatisticsScada();
            statisticsScada.setId(SnowFlake.nextId());
            statisticsScada.setCode(code.getVariableCode());
            statisticsScada.setValue(String.valueOf(stationIndexVosFinal.get(0).getIndexValue()));
//            statisticsScada.setAve(Double.toString(average));
            statisticsScada.setMax(maxVal.toString());
            statisticsScada.setMin(minVal.toString());
//            statisticsScada.setFold(Double.toString(sum));
//            statisticsScada.setPass(String.valueOf(pass));
            statisticsScada.setAllCount(String.valueOf(totalNum));
//            statisticsScada.setErrorCount(String.valueOf(totalNum.subtract(BigDecimal.valueOf(complianceNum))));
            statisticsScada.setStartTime(DateUtil.parse(startDate, "yyyy-MM-dd HH:mm:ss"));
            statisticsScada.setEndTime(DateUtil.parse(endDate, "yyyy-MM-dd HH:mm:ss"));
            statisticsScada.setStatisticsType(String.valueOf(dateType));
            statisticsScada.setDataType(String.valueOf(code.getType()));
            statisticsScada.setCreateTime(new Date());
            statisticsScadaList.add(statisticsScada);
        });
        if (CollUtil.isEmpty(statisticsScadaList)) {
            return;
        }
        statisticsScadaService.remove(new QueryWrapper<StatisticsScada>().lambda()
                .eq(StatisticsScada::getStatisticsType, dateType)
                .between(StatisticsScada::getStartTime, startTime, endTime));
        statisticsScadaService.saveBatch(statisticsScadaList);
    }

    /**
     * 分code进行存储（用缓存表数据）
     *
     * @param startTime 开始时间
     * @param endTime 结束时间
     */
    @Transactional(rollbackFor = Exception.class)
    public void saveStatisticsCode(Date startTime, Date endTime, Integer dateType) {
        List<Allocation> codes = allocationService.list();
        List<StatisticsScada> statisticsScadaList = new ArrayList<>();
        ScadaQueryDto dto = new ScadaQueryDto();
        String startDate = DateUtil.format(startTime, "yyyy-MM-dd HH:mm:ss");
        String endDate = DateUtil.format(endTime, "yyyy-MM-dd HH:mm:ss");
        dto.setStartDate(startDate);
        dto.setEndDate(endDate);
        dto.setTableName("STATISTICS_SCADA");
        dto.setTableType(2);
        dto.setStatisticsType(dateType - 1);
        List<ScadaIndexVo> stationIndexVos = scadaMapper.viewScadaDataShort(dto);
        if(CollUtil.isEmpty(stationIndexVos)) {
            return;
        }
        // 获取上一时段指标数据列表
        Calendar calendarStart = Calendar.getInstance();
        Calendar calendarEnd = Calendar.getInstance();
        calendarStart.setTime(startTime);
        calendarEnd.setTime(endTime);
        if (Objects.equals(dateType, StatisticsTypeEnum.TWO_MONTH.getType())) {
            calendarStart.add(Calendar.MONTH, -1);
            calendarEnd.add(Calendar.MONTH, -1);
        } else {
            calendarStart.add(Calendar.YEAR, -1);
            calendarEnd.add(Calendar.YEAR, -1);
        }
        dto.setStartDate(DateUtil.format(calendarStart.getTime(),"yyyy-MM-dd HH:mm:ss"));
        dto.setEndDate(DateUtil.format(calendarEnd.getTime(),"yyyy-MM-dd HH:mm:ss"));
        List<ScadaIndexVo> stationIndexVosLast = scadaMapper.viewScadaDataShort(dto);
        codes.forEach(code -> {
            List<ScadaIndexVo> stationIndexVosFinal = stationIndexVos.stream()
                    .filter(e -> e.getIndexCode().equals(code.getVariableCode())
                            && BigDecimal.valueOf(Double.parseDouble(e.getIndexValue())).compareTo(BigDecimal.ZERO) > 0)
                    .collect(Collectors.toList());
            if (CollUtil.isEmpty(stationIndexVosFinal)) {
                return;
            }
            List<ScadaIndexVo> lastMaxList = null;
            if (CollUtil.isNotEmpty(stationIndexVosLast)) {
                lastMaxList = stationIndexVosLast.stream()
                        .filter(e -> e.getIndexCode().equals(code.getVariableCode())
                                && BigDecimal.valueOf(Double.parseDouble(e.getIndexValue())).compareTo(BigDecimal.ZERO) > 0)
                        .collect(Collectors.toList());
            }
            List<Double> indexValMaxList = stationIndexVosFinal.stream().map(s -> Double.parseDouble(s.getIndexValueMax())).collect(Collectors.toList());
            List<Double> indexValMinList = stationIndexVosFinal.stream().map(s -> Double.parseDouble(s.getIndexValueMin())).collect(Collectors.toList());
            List<Double> indexValAvgList = stationIndexVosFinal.stream().map(s -> Double.parseDouble(s.getIndexValueAvg())).filter(e -> e != 0).collect(Collectors.toList());
            List<Double> indexValueFold = stationIndexVosFinal.stream().map(s -> Double.parseDouble(s.getIndexValueFold())).collect(Collectors.toList());
            List<Double> allCount = stationIndexVosFinal.stream().map(s -> Double.parseDouble(s.getAllCount())).collect(Collectors.toList());
            List<Double> passRate = stationIndexVosFinal.stream().map(s -> Double.parseDouble(s.getPassRate())).filter(e -> e != 0).collect(Collectors.toList());
            List<Double> errorCount = stationIndexVosFinal.stream().map(s -> Double.parseDouble(s.getErrorCount())).collect(Collectors.toList());
            StatisticsScada statisticsScada = new StatisticsScada();
            statisticsScada.setId(SnowFlake.nextId());
            statisticsScada.setCode(code.getVariableCode());
            statisticsScada.setValue(String.valueOf(stationIndexVosFinal.get(0).getIndexValue()));
            statisticsScada.setAve(CollUtil.isNotEmpty(indexValAvgList) ? Double.toString(indexValAvgList.stream().mapToDouble(Double::valueOf).average().getAsDouble()):"0");
            statisticsScada.setMax(Collections.max(indexValMaxList).toString());
            if (CollUtil.isNotEmpty(lastMaxList)) {
                statisticsScada.setMin(Collections.max(lastMaxList.stream().map(s -> Double.parseDouble(s.getIndexValueMax())).collect(Collectors.toList())).toString());
            } else {
                statisticsScada.setMin(Collections.min(indexValMinList).toString());
            }
            statisticsScada.setFold(Double.toString(indexValueFold.stream().mapToDouble(Double::valueOf).sum()));
            statisticsScada.setPass(CollUtil.isNotEmpty(passRate) ? String.valueOf(BigDecimal.valueOf(passRate.stream().mapToDouble(Double::valueOf).average().getAsDouble())):"0");
            statisticsScada.setAllCount(String.valueOf(BigDecimal.valueOf(allCount.stream().mapToDouble(Double::valueOf).sum())));
            statisticsScada.setErrorCount(String.valueOf(BigDecimal.valueOf(errorCount.stream().mapToDouble(Double::valueOf).sum())));
            statisticsScada.setStartTime(DateUtil.parse(startDate, "yyyy-MM-dd HH:mm:ss"));
            statisticsScada.setEndTime(DateUtil.parse(endDate, "yyyy-MM-dd HH:mm:ss"));
            statisticsScada.setStatisticsType(String.valueOf(dateType));
            statisticsScada.setDataType(String.valueOf(code.getType()));
            statisticsScada.setCreateTime(new Date());
            statisticsScadaList.add(statisticsScada);
        });
        if (CollUtil.isEmpty(statisticsScadaList)) {
            return;
        }
        statisticsScadaService.remove(new QueryWrapper<StatisticsScada>().lambda()
                .eq(StatisticsScada::getStatisticsType, dateType)
                .between(StatisticsScada::getStartTime, startTime, endTime));
        statisticsScadaService.saveBatch(statisticsScadaList);
    }

    /**
     *
     * @param startDate 开始时间 yyyy-MM-dd HH:mm:ss
     * @param endDate   结束时间 yyyy-MM-dd HH:mm:ss
     * @description 获取五分钟间隔时间列表
     */
    public static List<Map<String, String>> findDiffFiveDates(Date startDate, Date endDate) {
        List<Map<String, String>> dates = new ArrayList<>();
        Calendar startTime = Calendar.getInstance();
        Calendar midTime = Calendar.getInstance();
        startTime.setTime(startDate);
        midTime.setTime(startDate);
        midTime.set(Calendar.MINUTE, 4);
        midTime.set(Calendar.SECOND, 59);
        while (midTime.getTime().compareTo(endDate) <= 0) {
            Map<String, String> timeMap = new HashMap<>();
            timeMap.put("startTime", DateUtil.format(startTime.getTime(), "yyyy-MM-dd HH:mm:ss"));
            timeMap.put("endTime", DateUtil.format(midTime.getTime(), "yyyy-MM-dd HH:mm:ss"));
            dates.add(timeMap);
            startTime.add(Calendar.MINUTE, +5);
            midTime.add(Calendar.MINUTE, +5);
        }

        return dates;
    }

}
