package com.jiayue.ipfcst.console.service;

import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import com.jiayue.ipfcst.common.data.entity.*;
import com.jiayue.ipfcst.common.data.repository.AccuracyPassRateRepository;
import com.jiayue.ipfcst.common.data.repository.ForecastPowerShortTermHisRepository;
import com.jiayue.ipfcst.common.data.repository.ForecastPowerUltraShortTermHisRepository;
import com.jiayue.ipfcst.common.data.repository.PowerStationStatusDataRepository;
import com.jiayue.ipfcst.common.data.service.BaseService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.util.*;
import java.util.stream.Collectors;

/**
 *
 * 华北准确率生成业务
 *
 * @author yh
 * @version 2.0
 * @since 2020-04-16 11:19
 */
@Service
@Slf4j
public class AccuracyPassRateCalculateForHBService extends BaseService {

  private final PowerStationStatusDataRepository powerStationStatusDataRepository;
  private final ForecastPowerUltraShortTermHisRepository forecastPowerUltraShortTermHisRepository;
  private final ForecastPowerShortTermHisRepository forecastPowerShortTermHisRepository;
  private final SysParameterService sysParameterService;
  private final AccuracyPassRateRepository accuracyPassRateRepository;
  @Autowired
  public AccuracyPassRateCalculateForHBService(PowerStationStatusDataRepository powerStationStatusDataRepository,
                                               ForecastPowerUltraShortTermHisRepository forecastPowerUltraShortTermHisRepository,
                                               ForecastPowerShortTermHisRepository forecastPowerShortTermHisRepository,
                                               SysParameterService sysParameterService,
                                               AccuracyPassRateRepository accuracyPassRateRepository) {
    this.powerStationStatusDataRepository = powerStationStatusDataRepository;
    this.forecastPowerUltraShortTermHisRepository = forecastPowerUltraShortTermHisRepository;
    this.forecastPowerShortTermHisRepository = forecastPowerShortTermHisRepository;
    this.sysParameterService = sysParameterService;
    this.accuracyPassRateRepository = accuracyPassRateRepository;
  }

  public void generateData(ElectricField electricField) {
    try {
      // 获取场站信息
      BigDecimal capacity = electricField.getCapacity();
      long moment15 = 900000L;
      //获取昨天 00:15
      long strartTime =DateUtil.offsetDay(DateUtil.beginOfDay(DateTime.now()), -1).getTime()+moment15;
      //获取今日凌晨00:00
      long endTime = DateUtil.beginOfDay(DateTime.now()).getTime();

        //获取实际功率
        List<PowerStationStatusData> rList = new ArrayList<>();
        //15分钟间隔实际功率
        List<PowerStationStatusData> rpList = new ArrayList<>();
        rList = powerStationStatusDataRepository.findByTimeBetween(new Date(strartTime), new Date(endTime));
        rList.sort(Comparator.comparing(PowerStationStatusData::getTime));
        //过滤出15分钟间隔的实际功率集合
        for (long i = strartTime; i <= endTime; i = i + moment15) {
          long finalI = i;
          List<PowerStationStatusData> rpResultList = new ArrayList<>();

          rpResultList = rList.stream().filter(t -> t.getTime().getTime() == finalI ).collect(Collectors.toList());

          if (rpResultList != null && rpResultList.size() > 0) {
            rpList.add(rpResultList.get(0));
          }
        }

        //短期准确率
        Float dq = null;
        //超短期准确率
        Float cdq = null;
        /**
         * 情况二：若全天96个点位实际功率都小于10MW，
         * 则不参与考核（短期准确率则为80%，超短期准确率则为85%）。
         * 若有任何一个点位大于等于10MW，则按照情况一考核。
         *
         * 考虑到有可能由于特殊情况 一天获取不到96个点。就只取一天所有的进行判断，不只限制于96个点
         */
        if (rpList != null && rpList.size() > 0) {
          List<PowerStationStatusData> rpFilter = new ArrayList<>();
          rpFilter = rpList.stream().filter(t -> t.getAbleValue().floatValue() < 10f).collect(Collectors.toList());
          if (rpFilter != null && (rpFilter.size() == rpList.size())) {
            dq = 80f;
            cdq = 85f;
          } else {
            /************** ***********************************计算短期准确率-start*****************************************************/
            List<ForecastPowerShortTermHis> stList = new ArrayList<>();
            List<Map<String, BigDecimal>> stResultList = new ArrayList<>();


            // 获取短期计算准确率获取该天是提前几天预测的
            String sysParameter = sysParameterService.getSysParameter("ACCURACY_DQ_HOWLONGAGO","1");
            int howLongAgoForDq = Integer.parseInt(sysParameter);

            stList = forecastPowerShortTermHisRepository.findByForecastTimeBetweenAndForecastHowLongAgo(strartTime, endTime, howLongAgoForDq);
            stResultList = this.dataFilter(stList, rpList, electricField.getElectricFieldTypeEnum().name(), "dq", strartTime, endTime);
            dq = this.calculationsData(stResultList,capacity);
            /*************************************************计算短期准确率-end*****************************************************/

            /*************************************************计算超短期准确率-start*****************************************************/
            List<ForecastPowerUltraShortTermHis> ustList = new ArrayList<>();
            List<Map<String, BigDecimal>> ustResultList = new ArrayList<>();
            int howLongAgoForCdq = 1;
            // 获取超短期计算准确率获取第几个点
            String sysParameterCdq = sysParameterService.getSysParameter("ACCURACY_CDQ_HOWLONGAGO","1");
            howLongAgoForCdq = Integer.parseInt(sysParameterCdq);


            ustList = forecastPowerUltraShortTermHisRepository.findByForecastTimeBetweenAndForecastHowLongAgo(strartTime, endTime, howLongAgoForCdq);
            ustResultList = this.dataFilter(ustList, rpList,  electricField.getElectricFieldTypeEnum().name(), "cdq", strartTime, endTime);
            cdq = this.calculationsData(ustResultList,capacity);
            /*************************************************计算超短期准确率-end*****************************************************/
          }
        }

        //入库
        AccuracyPassRate accuracyPassRate = new AccuracyPassRate();
        accuracyPassRate.setTime(DateUtil.offsetDay(DateUtil.beginOfDay(DateTime.now()), -1).getTime());
        if(dq == null){
          accuracyPassRate.setShortTermAccuracy("缺失数据,无法计算");
        }else{
          accuracyPassRate.setShortTermAccuracy(dq.toString());
        }
        if(cdq == null){
          accuracyPassRate.setUltraShortTermAccuracy("缺失数据,无法计算");
        }else{
          accuracyPassRate.setUltraShortTermAccuracy(cdq.toString());
        }

        accuracyPassRateRepository.save(accuracyPassRate);

    }catch (Exception e) {
      e.printStackTrace();
      log.error("华北计算短期和超短期准确率失败" + e);
    }

  }

  /**
   * 数据过滤
   *
   * @param list              需要过滤的集合
   * @param electricFieldType 场站类型
   * @param dataType          数据类型
   * @param rList             实际功率结果集
   * @param strartTime        开始时间
   * @param endTime           结束时间
   * @return 过滤后的结果集
   */
  private List<Map<String, BigDecimal>> dataFilter(List list, List<PowerStationStatusData> rList, String electricFieldType, String dataType, long strartTime, long endTime) {
    //思路：
    // 1.无论超短期还是短期，风电和光伏的数据，首先都需要过滤掉同一时刻缺少实际值或者缺少预测值或者2个值同时缺少数据的时刻
    // 2.当同一时刻，预测值和实际值都不缺少时候，再进行下一步
    // 3.由于cdq和dq对于场站类型来说，计算过滤条件都是相同的，所以只需要区分场站类型即可。不需要再区分dq和cdq的数据类型
    List<ForecastPowerShortTermHis> dqList = new ArrayList<>();
    List<ForecastPowerUltraShortTermHis> cdqList = new ArrayList<>();
    List<Map<String, BigDecimal>> result = new ArrayList<>();
    if (dataType.equals("dq")) {
      dqList = list;
    } else {
      cdqList = list;
    }
    //只要有一个集合为空，那么准确率就无法计算
    if ((rList != null && rList.size() > 0) && (list != null && list.size() > 0)) {
      for (long i = strartTime; i <= endTime; i = i + 900000L) {
        long stepTime = i;
        //过滤出该时间的预测值
        List ycFilter = null;
        if (dataType.equals("dq")) {
          ycFilter = dqList.stream().filter(t -> t.getForecastTime() == stepTime).collect(Collectors.toList());
        } else {
          ycFilter = cdqList.stream().filter(t -> t.getForecastTime() == stepTime).collect(Collectors.toList());
        }

        //过滤出该时间的实际功率值
        List<PowerStationStatusData> rpFilter = null;
        rpFilter = rList.stream().filter(t -> t.getTime().getTime() == stepTime).collect(Collectors.toList());
        //过滤掉同一时刻预测和实际值有缺失的数据
        if ((ycFilter != null && ycFilter.size() > 0) && (rpFilter != null && rpFilter.size() > 0)) {
          //实际值
          BigDecimal rpValue = rpFilter.get(0).getAbleValue();
          //预测值
          BigDecimal ycValue;
          if (dataType.equals("dq")) {
            ycValue = ((List<ForecastPowerShortTermHis>) ycFilter).get(0).getAbleValue();
          } else {
            ycValue = ((List<ForecastPowerUltraShortTermHis>) ycFilter).get(0).getAbleValue();
          }
          Map<String, BigDecimal> map = new HashMap<>();
          //区分光伏场站和风电场站
          if (electricFieldType.equals("E1")) {
            //光场无过滤条件
            map.put("yc", ycValue);
            map.put("rp", rpValue);
            result.add(map);
          } else {
            //风场站无过滤条件
            map.put("yc", ycValue);
            map.put("rp", rpValue);
            result.add(map);
          }


        }

      }
    }
    return result;

  }

  /**
   * 计算准确率
   *
   * @param queryData 预测值和实际值的集合
   * @return 准确率
   */
  private Float calculationsData(List<Map<String, BigDecimal>> queryData,BigDecimal capacity) throws Exception {
    Float result = null;

    if (queryData != null && queryData.size() > 0) {
      //总和
      BigDecimal sum = new BigDecimal(0);

      for (Map<String, BigDecimal> map : queryData) {
        //预测值
        BigDecimal yc = map.get("yc").setScale(2, BigDecimal.ROUND_HALF_UP);
        //实际值
        BigDecimal rp = map.get("rp").setScale(4, BigDecimal.ROUND_HALF_UP);

        //DifferenceValue = |实际值-预测值|
        BigDecimal DifferenceValue = rp.subtract(yc).setScale(4, BigDecimal.ROUND_HALF_UP).abs();

        sum = sum.add(DifferenceValue).setScale(2, BigDecimal.ROUND_HALF_UP);
      }
      BigDecimal number = new BigDecimal(1);
      BigDecimal QASum = new BigDecimal(0);
      for(Map<String, BigDecimal> map : queryData){
        //预测值
        BigDecimal yc = map.get("yc").setScale(2, BigDecimal.ROUND_HALF_UP);
        //实际值
        BigDecimal rp = map.get("rp").setScale(4, BigDecimal.ROUND_HALF_UP);
        //实际-预测的立方除以sum
        BigDecimal QAResult = ((rp.subtract(yc).pow(3)).setScale(4, BigDecimal.ROUND_HALF_UP)).abs();
        BigDecimal value = QAResult.divide(sum,2, BigDecimal.ROUND_HALF_UP);
        QASum = QASum.add(value);
      }
      //开根号
      BigDecimal sqrt = sqrt(QASum,2);
      BigDecimal divideValue = sqrt.divide(capacity,2, BigDecimal.ROUND_HALF_UP);
      BigDecimal subtractValue = number.subtract(divideValue);

      //*100%
      BigDecimal resultBig = subtractValue.multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP);

      result = resultBig.floatValue();

    }

    return result;
  }

    //开平方
  public static BigDecimal sqrt(BigDecimal value, int scale) {
    BigDecimal num2 = BigDecimal.valueOf(2);
    int precision = 100;
    MathContext mc = new MathContext(precision, RoundingMode.HALF_UP);
    BigDecimal deviation = value;
    int cnt = 0;
    while (cnt < precision) {
      deviation = (deviation.add(value.divide(deviation, mc))).divide(num2, mc);
      cnt++;
    }
    deviation = deviation.setScale(scale, BigDecimal.ROUND_HALF_UP);
    return deviation;
  }
}
