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.util.*;
import java.util.stream.Collectors;

/**
 * 华东准确率生成业务
 *
 * @author yh
 * @version 2.0
 * @since 2019/7/17 9:30
 */
@Service
@Slf4j
public class AccuracyPassRateCalculateForHDService extends BaseService {

  private final PowerStationStatusDataRepository powerStationStatusDataRepository;
  private final ForecastPowerUltraShortTermHisRepository forecastPowerUltraShortTermHisRepository;
  private final ForecastPowerShortTermHisRepository forecastPowerShortTermHisRepository;
  private final SysParameterService sysParameterService;
  private final AccuracyPassRateRepository accuracyPassRateRepository;

  @Autowired
  public AccuracyPassRateCalculateForHDService(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 {
      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);
            /*************************************************计算短期准确率-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);
            /*************************************************计算超短期准确率-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) {
    /**
     * 光伏：
     * 短期功率预测：
     * 情况一：白天11：00—14:00不管发电功率多少都参与考核包含11:00和14:00整点时刻（此期间实际功率若为零，则实际功率按照0.01MW计算），
     * 其余时间发电功率需要大于等于10MW时参与考核，小于10MW时不参与考核，不统计入考核点数（包括PMi为零的时候）；
     * 超短期功率预测：
     * 和日前功率预测（短期功率预测）考核规则一样。超短期目前考核点位为第一个
     * （建议设置可零活配置的点位）。
     */

    /**
     * 风电：
     * 短期功率预测：
     * 情况一：发电功率需要大于等于10MW时参与考核，小于10MW时不参与考核，不统计考核点数（包括PMi为零的时候）。
     * 超短期功率预测：
     * 和日前功率预测（短期功率预测）考核规则一样。超短期目前考核点位为第一个
     * （建议设置可零活配置的点位）
     */

    //思路：
    // 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 + 1000 * 60 * 15L) {
        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();
          }
          //区分光伏场站和风电场站
          if (electricFieldType.equals("E1")) {
            //光伏电站过滤条件
            //计算出该天11：00和14：00点的时间戳
            Calendar calendar = Calendar.getInstance();
            calendar.setTimeInMillis(strartTime);
            calendar.set(Calendar.HOUR_OF_DAY, 11);
            calendar.set(Calendar.MINUTE, 0);
            calendar.set(Calendar.SECOND, 0);
            calendar.set(Calendar.MILLISECOND, 0);
            long time11 = calendar.getTimeInMillis();
            calendar.set(Calendar.HOUR_OF_DAY, 14);
            long time14 = calendar.getTimeInMillis();
            //如果时间是在11点和14点之间（包含11点和14点）
            if (stepTime >= time11 && stepTime <= time14) {
              //无论实际功率是否大于10MW 都计算
              Map<String, BigDecimal> map = new HashMap<>();
              map.put("yc", ycValue);
              //如果实际功率为0就让实际功率=0.01MW
              if (rpValue.floatValue() == 0) {
                map.put("rp",new BigDecimal("0.01" ));
              } else {
                map.put("rp", rpValue);
              }
              result.add(map);
            } else {
              //不在11点和14点之间的实际功率只有大于等于10MW的才进行计算
              if (rpValue.floatValue() >= 10f) {
                Map<String, BigDecimal> map = new HashMap<>();
                map.put("yc", ycValue);
                map.put("rp", rpValue);
                result.add(map);
              }
            }

          } else {
            //风电电站过滤条件
            //无论何时，只有实际功率>=10MW才会计算
            if (rpValue.floatValue() >= 10f) {
              Map<String, BigDecimal> map = new HashMap<>();
              map.put("yc", ycValue);
              map.put("rp", rpValue);
              result.add(map);
            }
          }


        }

      }
    }

    return result;
  }

  /**
   * 计算准确率
   *
   * @param queryData 预测值和实际值的集合
   * @return 准确率
   */
  private Float calculationsData(List<Map<String, BigDecimal>> queryData) 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);
        //divisionVlaue = DifferenceValue/实际值
        BigDecimal divisionVlaue = DifferenceValue.divide(rp, 9, BigDecimal.ROUND_HALF_UP);
        //square = divisionVlaue*divisionVlaue
        BigDecimal square = divisionVlaue.multiply(divisionVlaue).setScale(9, BigDecimal.ROUND_HALF_UP);
        sum = sum.add(square);
      }
      BigDecimal number = new BigDecimal(1);
      //样本总数
      BigDecimal totalValue = new BigDecimal(queryData.size());
      //系数
      BigDecimal radio = number.divide(totalValue, 9, BigDecimal.ROUND_HALF_UP);

      //(1/n)*(总和)
      BigDecimal multiplicationVlaue = radio.multiply(sum).setScale(9, BigDecimal.ROUND_HALF_UP);
      //开根号
      double sqrt = Math.sqrt(multiplicationVlaue.doubleValue());
      BigDecimal sqrtValue = new BigDecimal(sqrt).setScale(9, BigDecimal.ROUND_HALF_UP);
      //1-sqrtValue
      BigDecimal subtractValue = number.subtract(sqrtValue).setScale(9, BigDecimal.ROUND_HALF_UP);
      //*100%
      BigDecimal resultBig = subtractValue.multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP);

      result = resultBig.floatValue();

    }

    return result;
  }

}
