package com.usky.vpp.util;

import com.usky.vpp.service.VppTsdbQueryService;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

/**
 * IntervalDataReport 明日预测曲线（15 分钟整点，按量测点类型均值/累加）。
 */
public final class VppUnIntervalDataHelper {

    public static final int SLOT_MINUTES = VppUnMomentDataHelper.WINDOW_MINUTES;

    private static final DateTimeFormatter TIME_KEY_FMT = DateTimeFormatter.ofPattern("HH:mm");

    private VppUnIntervalDataHelper() {
    }

    public static LocalDate resolveForecastDate() {
        return LocalDate.now().plusDays(1);
    }

    public static List<LocalDateTime> buildDayQuarterHourSlots(LocalDate forecastDate) {
        List<LocalDateTime> slots = new ArrayList<>();
        if (forecastDate == null) {
            return slots;
        }
        LocalDateTime cursor = forecastDate.atStartOfDay();
        LocalDateTime dayEnd = forecastDate.plusDays(1).atStartOfDay();
        while (cursor.isBefore(dayEnd)) {
            slots.add(cursor);
            cursor = cursor.plusMinutes(SLOT_MINUTES);
        }
        return slots;
    }

    /**
     * 基于典型历史日预测 forecastDate 全天 96 个 15 分钟点。
     */
    public static List<Map<String, Object>> buildForecastCurveValues(VppTsdbQueryService tsdbQueryService,
                                                                     List<String> deviceUuids,
                                                                     List<LocalDate> referenceDates,
                                                                     LocalDate forecastDate,
                                                                     VppUnRegisterMetricDefinitions.RegisterMetric registerMetric) {
        List<LocalDateTime> slots = buildDayQuarterHourSlots(forecastDate);
        if (slots.isEmpty()) {
            return Collections.emptyList();
        }
        if (tsdbQueryService == null || deviceUuids == null || deviceUuids.isEmpty()
                || referenceDates == null || referenceDates.isEmpty() || registerMetric == null) {
            return buildZeroCurve(slots);
        }
        List<String> tsdbMetrics = VppUnMomentDataHelper.resolveTsdbMetrics(registerMetric);
        if (tsdbMetrics.isEmpty()) {
            return buildZeroCurve(slots);
        }
        List<String> distinctUuids = new ArrayList<>(new LinkedHashSet<>(deviceUuids));
        if (VppUnMomentDataHelper.resolveAggregationMode(registerMetric)
                == VppUnMomentDataHelper.AggregationMode.AVERAGE) {
            return buildAverageForecastCurve(tsdbQueryService, distinctUuids, referenceDates, slots, tsdbMetrics);
        }
        return buildAccumulativeForecastCurve(tsdbQueryService, distinctUuids, referenceDates, slots, tsdbMetrics);
    }

    private static List<Map<String, Object>> buildAverageForecastCurve(
            VppTsdbQueryService tsdbQueryService,
            List<String> deviceUuids,
            List<LocalDate> referenceDates,
            List<LocalDateTime> slots,
            List<String> tsdbMetrics) {
        Map<String, BigDecimal> avgByTime = tsdbQueryService.queryBaselineAvgByTimePoint(
                deviceUuids,
                referenceDates,
                LocalTime.MIN,
                LocalTime.of(23, 59, 59),
                tsdbMetrics,
                SLOT_MINUTES,
                null);
        List<Map<String, Object>> values = new ArrayList<>(slots.size());
        for (LocalDateTime slot : slots) {
            String timeKey = slot.format(TIME_KEY_FMT);
            BigDecimal value = avgByTime != null ? avgByTime.get(timeKey) : null;
            values.add(VppUnMessageBuilder.buildIntervalCurveValue(scale(value), slot, "good"));
        }
        return values;
    }

    private static List<Map<String, Object>> buildAccumulativeForecastCurve(
            VppTsdbQueryService tsdbQueryService,
            List<String> deviceUuids,
            List<LocalDate> referenceDates,
            List<LocalDateTime> slots,
            List<String> tsdbMetrics) {
        Map<String, List<BigDecimal>> samplesByTimeKey = new TreeMap<>();
        for (LocalDate referenceDate : referenceDates) {
            LocalDateTime dayStart = referenceDate.atStartOfDay();
            LocalDateTime dayEnd = referenceDate.plusDays(1).atStartOfDay();
            Map<String, Map<String, TreeMap<LocalDateTime, BigDecimal>>> history =
                    tsdbQueryService.queryDeviceMetricHistory(deviceUuids, dayStart, dayEnd, tsdbMetrics);
            if (history == null || history.isEmpty()) {
                continue;
            }
            for (LocalDateTime slot : slots) {
                LocalDateTime slotStart = LocalDateTime.of(referenceDate, slot.toLocalTime());
                LocalDateTime slotEnd = slotStart.plusMinutes(SLOT_MINUTES);
                LocalDateTime endExclusive = slotEnd.plusSeconds(1);
                BigDecimal slotTotal = BigDecimal.ZERO;
                for (String deviceUuid : deviceUuids) {
                    Map<String, TreeMap<LocalDateTime, BigDecimal>> metricMap = history.get(deviceUuid);
                    if (metricMap == null || metricMap.isEmpty()) {
                        continue;
                    }
                    TreeMap<LocalDateTime, BigDecimal> series = pickSeries(metricMap, tsdbMetrics);
                    BigDecimal usage = VppEnergyUsageHelper.calcUsage(series, slotStart, endExclusive);
                    if (usage != null) {
                        slotTotal = slotTotal.add(usage);
                    }
                }
                samplesByTimeKey.computeIfAbsent(slot.format(TIME_KEY_FMT), key -> new ArrayList<>())
                        .add(slotTotal);
            }
        }
        List<Map<String, Object>> values = new ArrayList<>(slots.size());
        for (LocalDateTime slot : slots) {
            List<BigDecimal> samples = samplesByTimeKey.get(slot.format(TIME_KEY_FMT));
            values.add(VppUnMessageBuilder.buildIntervalCurveValue(
                    scale(average(samples)), slot, "good"));
        }
        return values;
    }

    private static List<Map<String, Object>> buildZeroCurve(List<LocalDateTime> slots) {
        List<Map<String, Object>> values = new ArrayList<>(slots.size());
        for (LocalDateTime slot : slots) {
            values.add(VppUnMessageBuilder.buildIntervalCurveValue(BigDecimal.ZERO, slot, "good"));
        }
        return 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(List<BigDecimal> values) {
        if (values == null || values.isEmpty()) {
            return BigDecimal.ZERO;
        }
        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);
    }
}
