package com.usky.vpp.util;

import com.usky.vpp.constant.VppTsdbConstants;
import com.usky.vpp.service.VppTsdbQueryService;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

/**
 * MomentDataReport pointData 聚合（按量测点类型选择均值或累加）。
 */
public final class VppUnMomentDataHelper {

    public static final int WINDOW_MINUTES = 15;

    public enum AggregationMode {
        /** 窗口内时序均值，多设备再求和（功率、能力等瞬时值） */
        AVERAGE,
        /** 窗口内累计量增量（末值 − 首值），多设备再求和（电能 Wh） */
        ACCUMULATIVE
    }

    /**
     * 将参考时间对齐到 00/15/30/45 整分作为窗口结束时间。
     * 例：15:17 → 15:15；15:15 → 15:15。
     */
    public static LocalDateTime resolveWindowEnd(LocalDateTime referenceTime) {
        LocalDateTime base = referenceTime != null ? referenceTime : LocalDateTime.now();
        int flooredMinute = (base.getMinute() / WINDOW_MINUTES) * WINDOW_MINUTES;
        return base.withMinute(flooredMinute).withSecond(0).withNano(0);
    }

    public static LocalDateTime resolveWindowStart(LocalDateTime windowEnd) {
        return windowEnd != null ? windowEnd.minusMinutes(WINDOW_MINUTES) : null;
    }

    private VppUnMomentDataHelper() {
    }

    public static List<String> resolveTsdbMetrics(VppUnRegisterMetricDefinitions.RegisterMetric metric) {
        if (metric == null) {
            return Collections.emptyList();
        }
        switch (metric.getMetricName()) {
            case "AP":
            case "LOAD_FORECAST_XNDC":
                return VppBaselineHelper.POWER_METRICS;
            case "AP_E":
                return Collections.singletonList(VppTsdbConstants.METRIC_EPP);
            case "AP_PE":
                return Arrays.asList(VppTsdbConstants.METRIC_EPN, VppTsdbConstants.METRIC_EDAY);
            case "REGULATE_UP":
            case "F_REGULATE_UP":
                return VppTsdbConstants.UP_CAPACITY_METRICS;
            case "REGULATE_DOWN":
            case "F_REGULATE_DOWN":
                return VppTsdbConstants.DOWN_CAPACITY_METRICS;
            default:
                return Collections.emptyList();
        }
    }

    public static AggregationMode resolveAggregationMode(VppUnRegisterMetricDefinitions.RegisterMetric metric) {
        if (metric != null && "Wh".equals(metric.getSymbol())) {
            return AggregationMode.ACCUMULATIVE;
        }
        return AggregationMode.AVERAGE;
    }

    /**
     * 按量测点类型聚合：瞬时值类取窗口均值后跨设备求和；累计电能类取窗口增量后跨设备求和。
     */
    public static BigDecimal aggregateMetricValue(VppTsdbQueryService tsdbQueryService,
                                                  List<String> deviceUuids,
                                                  LocalDateTime windowStart,
                                                  LocalDateTime windowEnd,
                                                  VppUnRegisterMetricDefinitions.RegisterMetric registerMetric) {
        if (tsdbQueryService == null || deviceUuids == null || deviceUuids.isEmpty() || registerMetric == null) {
            return BigDecimal.ZERO;
        }
        List<String> tsdbMetrics = resolveTsdbMetrics(registerMetric);
        if (tsdbMetrics.isEmpty()) {
            return BigDecimal.ZERO;
        }
        AggregationMode mode = resolveAggregationMode(registerMetric);
        List<String> distinctUuids = new ArrayList<>(new LinkedHashSet<>(deviceUuids));
        Map<String, Map<String, TreeMap<LocalDateTime, BigDecimal>>> history =
                tsdbQueryService.queryDeviceMetricHistory(distinctUuids, windowStart, windowEnd, tsdbMetrics);
        if (history == null || history.isEmpty()) {
            return BigDecimal.ZERO;
        }
        BigDecimal total = BigDecimal.ZERO;
        for (String deviceUuid : distinctUuids) {
            Map<String, TreeMap<LocalDateTime, BigDecimal>> metricMap = history.get(deviceUuid);
            if (metricMap == null || metricMap.isEmpty()) {
                continue;
            }
            TreeMap<LocalDateTime, BigDecimal> series = pickSeries(metricMap, tsdbMetrics);
            if (series == null || series.isEmpty()) {
                continue;
            }
            BigDecimal deviceValue = aggregateDeviceSeries(series, windowStart, windowEnd, mode);
            if (deviceValue != null) {
                total = total.add(deviceValue);
            }
        }
        return scale(total);
    }

    private static BigDecimal aggregateDeviceSeries(TreeMap<LocalDateTime, BigDecimal> series,
                                                    LocalDateTime windowStart,
                                                    LocalDateTime windowEnd,
                                                    AggregationMode mode) {
        if (mode == AggregationMode.ACCUMULATIVE) {
            LocalDateTime endExclusive = windowEnd != null ? windowEnd.plusSeconds(1) : null;
            return VppEnergyUsageHelper.calcUsage(series, windowStart, endExclusive);
        }
        return average(series.values());
    }

    private static TreeMap<LocalDateTime, BigDecimal> pickSeries(Map<String, TreeMap<LocalDateTime, BigDecimal>> metricMap,
                                                                 List<String> preferredMetrics) {
        if (metricMap == null || metricMap.isEmpty()) {
            return null;
        }
        if (preferredMetrics != null) {
            for (String metric : preferredMetrics) {
                TreeMap<LocalDateTime, BigDecimal> series = metricMap.get(metric);
                if (series != null && !series.isEmpty()) {
                    return series;
                }
            }
        }
        for (TreeMap<LocalDateTime, BigDecimal> series : metricMap.values()) {
            if (series != null && !series.isEmpty()) {
                return series;
            }
        }
        return null;
    }

    private static BigDecimal average(Iterable<BigDecimal> values) {
        BigDecimal sum = BigDecimal.ZERO;
        int count = 0;
        for (BigDecimal value : values) {
            if (value != null) {
                sum = sum.add(value);
                count++;
            }
        }
        if (count == 0) {
            return BigDecimal.ZERO;
        }
        return sum.divide(BigDecimal.valueOf(count), 6, RoundingMode.HALF_UP);
    }

    private static BigDecimal scale(BigDecimal value) {
        if (value == null) {
            return BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
        }
        return value.setScale(2, RoundingMode.HALF_UP);
    }
}
