package com.yeejoin.amos.boot.module.jxiop.biz.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.yeejoin.amos.boot.module.jxiop.biz.entity.IdxBizPvWeight;
import com.yeejoin.amos.boot.module.jxiop.biz.mapper2.IdxBizPvWeightMapper;
import com.yeejoin.amos.boot.module.jxiop.biz.service.IPvHealthIndexService;
import com.yeejoin.amos.boot.module.jxiop.biz.tdMapper2.PvHealthIndexMapper;
import com.yeejoin.amos.boot.module.jxiop.biz.tdengine.FanHealthIndex;
import com.yeejoin.amos.boot.module.jxiop.biz.tdengine.PvHealthIndex;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/**
 * 光伏 -  按时刻生成设备、子阵、场站和片区数据
 * <p>
 * ProjectName: amos-boot-zx-biz
 * PackageName: com.yeejoin.amos.boot.module.jxiop.biz.service.impl
 *
 * @author yangyang
 * @version v1.0
 * @date 2024/7/23 21:08
 */
@Component
@Slf4j
public class PvHealthIndexServiceImpl implements IPvHealthIndexService {

    @Autowired
    private PvHealthIndexMapper pvHealthIndexMapper;

    @Autowired
    private IdxBizPvWeightMapper idxBizPvWeightMapper;

    @Value ("${analysis.weightRounding:false}")
    private Boolean weightRounding;

    @Override
    public List<PvHealthIndex> getInfoListByGroupByCdPv(String startTime, String tableName, String analysisObjectType) {
        List<PvHealthIndex> pvHealthIndices = pvHealthIndexMapper.getInfoList(startTime, tableName, analysisObjectType);
        QueryWrapper<IdxBizPvWeight> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("type", "5");
        queryWrapper.isNotNull("value");
        List<IdxBizPvWeight> idxBizFanWeights = idxBizPvWeightMapper.selectList(queryWrapper);
        Map<String, Float> weightMap = idxBizFanWeights.stream().collect(Collectors.toMap(o -> o.getArae() + o.getStation() + o.getEquipmentName() + o.getSubarray() + o.getPointName(), IdxBizPvWeight::getValue,(item1, item2) -> item1));
        // 开始计算加权平均
        Map<String, List<PvHealthIndex>> healthIndicesMap = pvHealthIndices.stream().collect(Collectors.groupingBy(o -> o.getGatewayId() + o.getIndexAddress() + o.getStation() + o.getAnalysisObjType() + o.getAnalysisObjSeq() + o.getWeight() + o.getArea() + o.getSubarray() + o.getManufacturer() + o.getEquipmentName() + o.getPointName() + o.getDeviceType() + o.getKks() + o.getOrgCode()));
        List<PvHealthIndex> pvHealthIndexList = new ArrayList<>();
        healthIndicesMap.forEach((k, v) -> {
            PvHealthIndex pvHealthIndex = new PvHealthIndex();
            pvHealthIndex.setIndexAddress(v.get(0).getIndexAddress());
            pvHealthIndex.setGatewayId(v.get(0).getGatewayId());
            pvHealthIndex.setStation(v.get(0).getStation());
            pvHealthIndex.setAnalysisObjType("测点");
            pvHealthIndex.setAnalysisObjSeq(v.get(0).getAnalysisObjSeq());
            pvHealthIndex.setWeight(v.get(0).getWeight());
            pvHealthIndex.setArea(v.get(0).getArea());
            pvHealthIndex.setSubarray(v.get(0).getSubarray());
            pvHealthIndex.setManufacturer(v.get(0).getManufacturer());
            pvHealthIndex.setEquipmentName(v.get(0).getEquipmentName());
            pvHealthIndex.setPointName(v.get(0).getPointName());
            pvHealthIndex.setDeviceType(v.get(0).getDeviceType());
            pvHealthIndex.setKks(v.get(0).getKks());
            pvHealthIndex.setOrgCode(v.get(0).getOrgCode());
            // 例如 子系统 = (测点1 * 测点1权重 + 测点2 * 测点2权重 + .... 测点n权重) /  (测点1权重 + 测点2权重 + .... 测点n权重)
            // 但是测点是没有权重的，所以子系统无法计算加权平均
            // 设备 = (子系统1 * 子系统1权重 + 子系统2 * 子系统2权重 + .... 子系统n权重) /  (子系统1权重 + 子系统2权重 + .... 子系统n权重)
            // 但如果没有配置子系统的权重，如何计算？
            try {
                // 加权平均
                Function<PvHealthIndex, String> assembleKey = o -> o.getArea() + o.getStation() + o.getEquipmentName() + o.getSubarray() + o.getPointName();
                this.weightedMean(assembleKey, pvHealthIndex, v, weightMap);
            } catch (Exception e) {
                // 普通平均
                this.calculateAvg(pvHealthIndex, v);
            }
            pvHealthIndexList.add(pvHealthIndex);
        });
        return pvHealthIndexList;
    }

    /**
     * 设备加权平均
     * 加权平均数 = (w1 * x1 + w2 * x2 + … + wn * xn) / (w1 + w2 + … + wn)
     * w1, w2, …, wn 是各个数据点的权重；
     * x1, x2, …, xn 是各个数据点的数值；
     * n 是数据点的总数。
     *
     * @param startTime          startTime
     * @param tableName          tableName
     * @param analysisObjectType analysisObjectType
     * @return {@link  List< FanHealthIndex>}
     * @throws
     * @author yangyang
     * @date 2024/7/23 21:02
     */
    @Override
    public List<PvHealthIndex> getInfoListByGroupBySbPv(String startTime,
                                                        String tableName,
                                                        String analysisObjectType) {
        List<PvHealthIndex> pvHealthIndices = pvHealthIndexMapper.getInfoList(startTime, tableName, analysisObjectType);
        QueryWrapper<IdxBizPvWeight> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("type", "5");
        queryWrapper.isNotNull("value");
        List<IdxBizPvWeight> idxBizFanWeights = idxBizPvWeightMapper.selectList(queryWrapper);
        Map<String, Float> weightMap = idxBizFanWeights.stream().collect(Collectors.toMap(o -> o.getArae() + o.getStation() + o.getEquipmentName() + o.getSubarray() + o.getPointName(), IdxBizPvWeight::getValue,(item1, item2) -> item1));
        // 开始计算加权平均
        Map<String, List<PvHealthIndex>> healthIndicesMap = pvHealthIndices.stream().collect(Collectors.groupingBy(o -> o.getGatewayId() + o.getStation() + o.getAnalysisObjType() + o.getArea() + o.getSubarray() + o.getOrgCode() + o.getEquipmentName()));
        List<PvHealthIndex> pvHealthIndexList = new ArrayList<>();
        healthIndicesMap.forEach((k, v) -> {
            PvHealthIndex pvHealthIndex = new PvHealthIndex();
            pvHealthIndex.setGatewayId(v.get(0).getGatewayId());
            pvHealthIndex.setStation(v.get(0).getStation());
            pvHealthIndex.setAnalysisObjType("设备");
            pvHealthIndex.setArea(v.get(0).getArea());
            pvHealthIndex.setSubarray(v.get(0).getSubarray());
            pvHealthIndex.setEquipmentName(v.get(0).getEquipmentName());
            pvHealthIndex.setOrgCode(v.get(0).getOrgCode());
            // 例如 子系统 = (测点1 * 测点1权重 + 测点2 * 测点2权重 + .... 测点n权重) /  (测点1权重 + 测点2权重 + .... 测点n权重)
            // 但是测点是没有权重的，所以子系统无法计算加权平均
            // 设备 = (子系统1 * 子系统1权重 + 子系统2 * 子系统2权重 + .... 子系统n权重) /  (子系统1权重 + 子系统2权重 + .... 子系统n权重)
            // 但如果没有配置子系统的权重，如何计算？
            try {
                // 加权平均
                Function<PvHealthIndex, String> assembleKey = o -> o.getArea() + o.getStation() + o.getEquipmentName() + o.getSubarray() + o.getPointName();
                this.weightedMean(assembleKey, pvHealthIndex, v, weightMap);
            } catch (Exception e) {
                // 普通平均
                this.calculateAvg(pvHealthIndex, v);
            }
            pvHealthIndexList.add(pvHealthIndex);
        });
        return pvHealthIndexList;
    }

    /**
     * 子阵加权平均
     * 加权平均数 = (w1 * x1 + w2 * x2 + … + wn * xn) / (w1 + w2 + … + wn)
     * w1, w2, …, wn 是各个数据点的权重；
     * x1, x2, …, xn 是各个数据点的数值；
     * n 是数据点的总数。
     *
     * @param startTime          startTime
     * @param tableName          tableName
     * @param analysisObjectType analysisObjectType
     * @return {@link  List< FanHealthIndex>}
     * @throws
     * @author yangyang
     * @date 2024/7/23 21:02
     */
    @Override
    public List<PvHealthIndex> getInfoListByGroupByZzPv(String startTime,
                                                        String tableName,
                                                        String analysisObjectType) {
        List<PvHealthIndex> pvHealthIndices = pvHealthIndexMapper.getInfoList(startTime, tableName, analysisObjectType);
        QueryWrapper<IdxBizPvWeight> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("type", "4");
        queryWrapper.isNotNull("value");
        List<IdxBizPvWeight> idxBizFanWeights = idxBizPvWeightMapper.selectList(queryWrapper);
        Map<String, Float> weightMap = idxBizFanWeights.stream().collect(Collectors.toMap(o -> o.getArae() + o.getStation() + o.getEquipmentName() + o.getSubarray(), IdxBizPvWeight::getValue,(item1, item2) -> item1));
        // 开始计算加权平均
        Map<String, List<PvHealthIndex>> healthIndicesMap = pvHealthIndices.stream().collect(Collectors.groupingBy(o -> o.getGatewayId() + o.getStation() + o.getAnalysisObjType() + o.getArea() + o.getSubarray() + o.getOrgCode()));
        List<PvHealthIndex> pvHealthIndexList = new ArrayList<>();
        healthIndicesMap.forEach((k, v) -> {
            PvHealthIndex pvHealthIndex = new PvHealthIndex();
            pvHealthIndex.setGatewayId(v.get(0).getGatewayId());
            pvHealthIndex.setStation(v.get(0).getStation());
            pvHealthIndex.setAnalysisObjType("子阵");
            pvHealthIndex.setArea(v.get(0).getArea());
            pvHealthIndex.setSubarray(v.get(0).getSubarray());
            pvHealthIndex.setOrgCode(v.get(0).getOrgCode());
            // 例如 子系统 = (测点1 * 测点1权重 + 测点2 * 测点2权重 + .... 测点n权重) /  (测点1权重 + 测点2权重 + .... 测点n权重)
            // 但是测点是没有权重的，所以子系统无法计算加权平均
            // 设备 = (子系统1 * 子系统1权重 + 子系统2 * 子系统2权重 + .... 子系统n权重) /  (子系统1权重 + 子系统2权重 + .... 子系统n权重)
            // 但如果没有配置子系统的权重，如何计算？
            try {
                // 加权平均
                Function<PvHealthIndex, String> assembleKey = o -> o.getArea() + o.getStation() + o.getEquipmentName() + o.getSubarray();
                this.weightedMean(assembleKey, pvHealthIndex, v, weightMap);
            } catch (Exception e) {
                // 普通平均
                this.calculateAvg(pvHealthIndex, v);
            }
            pvHealthIndexList.add(pvHealthIndex);
        });
        return pvHealthIndexList;
    }

    /**
     * 场站加权平均
     * 加权平均数 = (w1 * x1 + w2 * x2 + … + wn * xn) / (w1 + w2 + … + wn)
     * w1, w2, …, wn 是各个数据点的权重；
     * x1, x2, …, xn 是各个数据点的数值；
     * n 是数据点的总数。
     *
     * @param startTime          startTime
     * @param tableName          tableName
     * @param analysisObjectType analysisObjectType
     * @return {@link  List< FanHealthIndex>}
     * @throws
     * @author yangyang
     * @date 2024/7/23 21:02
     */
    @Override
    public List<PvHealthIndex> getInfoListByGroupByCzPv(String startTime,
                                                        String tableName,
                                                        String analysisObjectType) {
        List<PvHealthIndex> pvHealthIndices = pvHealthIndexMapper.getInfoList(startTime, tableName, analysisObjectType);
        QueryWrapper<IdxBizPvWeight> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("type", "3");
        queryWrapper.isNotNull("value");
        List<IdxBizPvWeight> idxBizFanWeights = idxBizPvWeightMapper.selectList(queryWrapper);
        Map<String, Float> weightMap = idxBizFanWeights.stream().collect(Collectors.toMap(o -> o.getArae() + o.getStation() + o.getEquipmentName(), IdxBizPvWeight::getValue,(item1, item2) -> item1));
        // 开始计算加权平均
        Map<String, List<PvHealthIndex>> healthIndicesMap = pvHealthIndices.stream().collect(Collectors.groupingBy(o ->  o.getStation() + o.getAnalysisObjType() + o.getArea() + o.getOrgCode()));
        List<PvHealthIndex> pvHealthIndexList = new ArrayList<>();
        healthIndicesMap.forEach((k, v) -> {
            PvHealthIndex pvHealthIndex = new PvHealthIndex();
            pvHealthIndex.setGatewayId(v.get(0).getGatewayId());
            pvHealthIndex.setStation(v.get(0).getStation());
            pvHealthIndex.setAnalysisObjType("场站");
            pvHealthIndex.setArea(v.get(0).getArea());
            pvHealthIndex.setOrgCode(v.get(0).getOrgCode());
            // 例如 子系统 = (测点1 * 测点1权重 + 测点2 * 测点2权重 + .... 测点n权重) /  (测点1权重 + 测点2权重 + .... 测点n权重)
            // 但是测点是没有权重的，所以子系统无法计算加权平均
            // 设备 = (子系统1 * 子系统1权重 + 子系统2 * 子系统2权重 + .... 子系统n权重) /  (子系统1权重 + 子系统2权重 + .... 子系统n权重)
            // 但如果没有配置子系统的权重，如何计算？
            try {
                // 加权平均
                Function<PvHealthIndex, String> assembleKey = o -> o.getArea() + o.getStation() + o.getEquipmentName();
                this.weightedMean(assembleKey, pvHealthIndex, v, weightMap);
            } catch (Exception e) {
                // 普通平均
                this.calculateAvg(pvHealthIndex, v);
            }
            pvHealthIndexList.add(pvHealthIndex);
        });
        return pvHealthIndexList;
    }

    /**
     * 片区加权平均
     * 加权平均数 = (w1 * x1 + w2 * x2 + … + wn * xn) / (w1 + w2 + … + wn)
     * w1, w2, …, wn 是各个数据点的权重；
     * x1, x2, …, xn 是各个数据点的数值；
     * n 是数据点的总数。
     *
     * @param startTime          startTime
     * @param tableName          tableName
     * @param analysisObjectType analysisObjectType
     * @return {@link  List< FanHealthIndex>}
     * @throws
     * @author yangyang
     * @date 2024/7/23 21:02
     */
    @Override
    public List<PvHealthIndex> getInfoListByGroupByQyPv(String startTime,
                                                        String tableName,
                                                        String analysisObjectType) {
        List<PvHealthIndex> pvHealthIndices = pvHealthIndexMapper.getInfoList(startTime, tableName, analysisObjectType);
        QueryWrapper<IdxBizPvWeight> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("type", "2");
        queryWrapper.isNotNull("value");
        List<IdxBizPvWeight> idxBizFanWeights = idxBizPvWeightMapper.selectList(queryWrapper);
        Map<String, Float> weightMap = idxBizFanWeights.stream().collect(Collectors.toMap(o -> o.getArae() + o.getStation(), IdxBizPvWeight::getValue,(item1, item2) -> item1));
        // 开始计算加权平均
        Map<String, List<PvHealthIndex>> healthIndicesMap = pvHealthIndices.stream().collect(Collectors.groupingBy(o -> o.getAnalysisObjType() + o.getArea() + o.getOrgCode()));
        List<PvHealthIndex> pvHealthIndexList = new ArrayList<>();
        healthIndicesMap.forEach((k, v) -> {
            PvHealthIndex pvHealthIndex = new PvHealthIndex();
            pvHealthIndex.setAnalysisObjType("片区");
            pvHealthIndex.setArea(v.get(0).getArea());
            pvHealthIndex.setOrgCode(v.get(0).getOrgCode());
            // 例如 子系统 = (测点1 * 测点1权重 + 测点2 * 测点2权重 + .... 测点n权重) /  (测点1权重 + 测点2权重 + .... 测点n权重)
            // 但是测点是没有权重的，所以子系统无法计算加权平均
            // 设备 = (子系统1 * 子系统1权重 + 子系统2 * 子系统2权重 + .... 子系统n权重) /  (子系统1权重 + 子系统2权重 + .... 子系统n权重)
            // 但如果没有配置子系统的权重，如何计算？
            try {
                // 加权平均
                Function<PvHealthIndex, String> assembleKey = o -> o.getArea() + o.getStation();
                this.weightedMean(assembleKey, pvHealthIndex, v, weightMap);
            } catch (Exception e) {
                // 普通平均
                this.calculateAvg(pvHealthIndex, v);
            }
            pvHealthIndexList.add(pvHealthIndex);
        });
        return pvHealthIndexList;
    }

    /**
     * 全域【所有 / 全国】加权平均
     * 加权平均数 = (w1 * x1 + w2 * x2 + … + wn * xn) / (w1 + w2 + … + wn)
     * w1, w2, …, wn 是各个数据点的权重；
     * x1, x2, …, xn 是各个数据点的数值；
     * n 是数据点的总数。
     *
     * @param startTime          startTime
     * @param tableName          tableName
     * @param analysisObjectType analysisObjectType
     * @return {@link  List< FanHealthIndex>}
     * @throws
     * @author yangyang
     * @date 2024/7/23 21:02
     */
    @Override
    public List<PvHealthIndex> getInfoListByGroupByQgPv(String startTime,
                                                        String tableName,
                                                        String analysisObjectType) {
        List<PvHealthIndex> pvHealthIndices = pvHealthIndexMapper.getInfoList(startTime, tableName, analysisObjectType);
        QueryWrapper<IdxBizPvWeight> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("type", "1");
        queryWrapper.isNotNull("value");
        List<IdxBizPvWeight> idxBizFanWeights = idxBizPvWeightMapper.selectList(queryWrapper);
        Map<String, Float> weightMap = idxBizFanWeights.stream().collect(Collectors.toMap(o -> o.getArae(), IdxBizPvWeight::getValue,(item1, item2) -> item1));
        // 开始计算加权平均
        Map<String, List<PvHealthIndex>> healthIndicesMap = pvHealthIndices.stream().collect(Collectors.groupingBy(o -> o.getAnalysisObjType() + o.getOrgCode()));
        List<PvHealthIndex> pvHealthIndexList = new ArrayList<>();
        healthIndicesMap.forEach((k, v) -> {
            PvHealthIndex pvHealthIndex = new PvHealthIndex();
            pvHealthIndex.setAnalysisObjType("全域");
            pvHealthIndex.setOrgCode(v.get(0).getOrgCode());
            // 例如 子系统 = (测点1 * 测点1权重 + 测点2 * 测点2权重 + .... 测点n权重) /  (测点1权重 + 测点2权重 + .... 测点n权重)
            // 但是测点是没有权重的，所以子系统无法计算加权平均
            // 设备 = (子系统1 * 子系统1权重 + 子系统2 * 子系统2权重 + .... 子系统n权重) /  (子系统1权重 + 子系统2权重 + .... 子系统n权重)
            // 但如果没有配置子系统的权重，如何计算？
            try {
                // 加权平均
                Function<PvHealthIndex, String> assembleKey = o -> o.getArea();
                this.weightedMean(assembleKey, pvHealthIndex, v, weightMap);
            } catch (Exception e) {
                // 普通平均
                this.calculateAvg(pvHealthIndex, v);
            }
            pvHealthIndexList.add(pvHealthIndex);
        });
        return pvHealthIndexList;
    }

    /**
     * 加权平均
     *
     * @param assembleKey assembleKey
     * @param healthIndex healthIndex
     * @param v           v
     * @param weightMap   weightMap
     * @return {@link }
     * @throws
     * @author yangyang
     * @date 2024/7/25 17:36
     */
    public void weightedMean(Function<PvHealthIndex, String> assembleKey, PvHealthIndex healthIndex, List<PvHealthIndex> v, Map<String, Float> weightMap) {
        Double totalHealthIndex = v.stream().mapToDouble(o -> getWeight(() -> assembleKey.apply(o), weightMap) * o.getHealthIndex()).sum();
        Double totalAnomaly = v.stream().mapToDouble(o -> getWeight(() -> assembleKey.apply(o), weightMap) * o.getAnomaly()).sum();
        Double totalWeight = v.stream().mapToDouble(o -> getWeight(() -> assembleKey.apply(o), weightMap)).sum();
        healthIndex.setAnomaly(division(totalAnomaly, totalWeight));
        healthIndex.setHealthIndex(division(totalHealthIndex, totalWeight));
    }

    /**
     * 普通平均
     *
     * @param healthIndex healthIndex
     * @param v           v
     * @return {@link }
     * @throws
     * @author yangyang
     * @date 2024/7/25 17:37
     */
    public void calculateAvg(PvHealthIndex healthIndex, List<PvHealthIndex> v) {
        // 普通平均
        Double avgHealthIndex = v.stream().mapToDouble(o -> o.getHealthIndex()).sum();
        Double avgAnomaly = v.stream().mapToDouble(o -> o.getAnomaly()).sum();
        Long totalHealthIndex = v.stream().filter(o -> o.getHealthIndex() != null).mapToDouble(o -> o.getHealthIndex()).count();
        Long totalAnomaly = v.stream().filter(o -> o.getAnomaly() != null).mapToDouble(o -> o.getAnomaly()).count();
        healthIndex.setAnomaly(division(avgAnomaly, totalAnomaly.doubleValue()));
        healthIndex.setHealthIndex(division(avgHealthIndex, totalHealthIndex.doubleValue()));
    }

    public Double getWeight(Supplier<String> key, Map<String, Float> weightMap) {
        Float weight = weightMap.get(key.get());
        if (weight == null) {
            log.error("计算加权平均异常【" + key.get() + "】未配置权重");
            throw new RuntimeException("未配置权重");
        }
        return weight.doubleValue();
    }

    /**
     * 计算的时候，会使用原始数据计算，页面显示数据会四舍五入到1位小数
     *
     * 启用weightRounding 配置则会四舍五入到小数点后1位
     *
     * @param a a
     * @param b b
     * @return {@link  Double}
     * @throws
     * @author yangyang
     * @date 2024/7/25 17:28
     */
    private Double division(Double a, Double b) {
        if (weightRounding) {
            BigDecimal c = new BigDecimal(a).divide(new BigDecimal(b), 1, BigDecimal.ROUND_HALF_UP);
            return c.doubleValue();
        }
        return a / b;
    }

}
