package com.usky.vpp.util;

import com.usky.vpp.domain.VppDrEvent;
import com.usky.vpp.domain.VppDrExecution;
import com.usky.vpp.domain.VppDrParticipation;
import com.usky.vpp.service.vo.ResponseLoadPointVO;
import com.usky.vpp.service.vo.ResponseLoadStatRowVO;

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.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

/**
 * 响应监控辅助（15 分钟粒度曲线 + 统计表）
 */
public final class VppResponseMonitorHelper {

    public static final int BUCKET_MINUTES = 15;
    public static final int BASELINE_DAYS_AGO = 7;

    private static final DateTimeFormatter TIME_FMT = DateTimeFormatter.ofPattern("HH:mm");
    private static final DateTimeFormatter DATETIME_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    private static final DateTimeFormatter HOUR_FMT = DateTimeFormatter.ofPattern("HH:00");

    private VppResponseMonitorHelper() {
    }

    public static List<ResponseLoadPointVO> buildCurveFromTsdb(
            Map<String, Map<String, TreeMap<LocalDateTime, BigDecimal>>> actualData,
            Map<String, Map<String, TreeMap<LocalDateTime, BigDecimal>>> baselineData,
            LocalDate date,
            List<String> powerMetrics,
            BigDecimal declaredCapacityKw,
            Map<LocalDateTime, BigDecimal> executionActualByTime) {
        List<ResponseLoadPointVO> points = new ArrayList<>();
        for (int minute = 0; minute < 1440; minute += BUCKET_MINUTES) {
            LocalTime time = LocalTime.of(minute / 60, minute % 60);
            LocalDateTime bucketStart = date.atTime(time);
            LocalDateTime bucketEnd = bucketStart.plusMinutes(BUCKET_MINUTES).minusSeconds(1);

            BigDecimal actual = sumBucketPower(actualData, powerMetrics, bucketStart, bucketEnd);
            BigDecimal baseline = sumBucketPower(baselineData, powerMetrics,
                    date.minusDays(BASELINE_DAYS_AGO).atTime(time),
                    date.minusDays(BASELINE_DAYS_AGO).atTime(time).plusMinutes(BUCKET_MINUTES).minusSeconds(1));
            if (baseline == null || baseline.compareTo(BigDecimal.ZERO) <= 0) {
                baseline = actual;
            }

            BigDecimal responseLoad = executionActualByTime != null
                    ? executionActualByTime.getOrDefault(bucketStart, null)
                    : null;
            if (responseLoad == null) {
                responseLoad = baseline.subtract(actual).max(BigDecimal.ZERO);
            }

            ResponseLoadPointVO point = new ResponseLoadPointVO();
            point.setTime(time.format(TIME_FMT));
            point.setDatetime(bucketStart.format(DATETIME_FMT));
            point.setBaselineLoadKw(scale(baseline));
            point.setActualLoadKw(scale(actual));
            point.setDeclaredCapacityKw(declaredCapacityKw != null ? scale(declaredCapacityKw) : BigDecimal.ZERO);
            point.setActualResponseLoadKw(scale(responseLoad));
            points.add(point);
        }
        return points;
    }

    /*
    public static List<ResponseLoadPointVO> buildMockCurve(List<VppResourcePoint> resources,
                                                         List<VppDevice> devices,
                                                         Long siteId,
                                                         LocalDate date,
                                                         BigDecimal declaredCapacityKw) {
        List<ResponseLoadPointVO> points = new ArrayList<>();
        long seed = (siteId != null ? siteId : 0L) + date.toEpochDay();
        for (int minute = 0; minute < 1440; minute += BUCKET_MINUTES) {
            LocalTime time = LocalTime.of(minute / 60, minute % 60);
            double hourFactor = dayTimeFactor(time);
            BigDecimal baseline = mockAggregate(resources, devices, seed, minute, hourFactor, 0);
            BigDecimal actual = mockAggregate(resources, devices, seed, minute, hourFactor, 1);
            BigDecimal response = baseline.subtract(actual).max(BigDecimal.ZERO);

            ResponseLoadPointVO point = new ResponseLoadPointVO();
            point.setTime(time.format(TIME_FMT));
            point.setDatetime(date.atTime(time).format(DATETIME_FMT));
            point.setBaselineLoadKw(baseline);
            point.setActualLoadKw(actual);
            point.setDeclaredCapacityKw(declaredCapacityKw != null ? declaredCapacityKw : BigDecimal.ZERO);
            point.setActualResponseLoadKw(response);
            points.add(point);
        }
        return points;
    }
    */

    /**
     * 基于预计算的预测基线和今日实际，构建曲线点（替代直接操作 TSDB 原始时序）。
     */
    public static List<ResponseLoadPointVO> buildCurveWithBaseline(
            Map<String, BigDecimal> predictedBaseline,
            Map<String, BigDecimal> todayActual,
            LocalDate date,
            BigDecimal declaredCapacityKw,
            Map<LocalDateTime, BigDecimal> executionActualByTime,
            int intervalMinutes) {
        List<ResponseLoadPointVO> points = new ArrayList<>();
        for (int minute = 0; minute < 1440; minute += intervalMinutes) {
            LocalTime time = LocalTime.of(minute / 60, minute % 60);
            String timeKey = time.format(TIME_FMT);
            LocalDateTime bucketStart = date.atTime(time);

            BigDecimal baseline = predictedBaseline.getOrDefault(timeKey, BigDecimal.ZERO);
            BigDecimal actual = todayActual.getOrDefault(timeKey, BigDecimal.ZERO);

            BigDecimal responseLoad = executionActualByTime != null
                    ? executionActualByTime.getOrDefault(bucketStart, null)
                    : null;
            if (responseLoad == null) {
                responseLoad = baseline.subtract(actual).max(BigDecimal.ZERO);
            }

            ResponseLoadPointVO point = new ResponseLoadPointVO();
            point.setTime(timeKey);
            point.setDatetime(bucketStart.format(DATETIME_FMT));
            point.setBaselineLoadKw(scale(baseline));
            point.setActualLoadKw(scale(actual));
            point.setDeclaredCapacityKw(declaredCapacityKw != null ? scale(declaredCapacityKw) : BigDecimal.ZERO);
            point.setActualResponseLoadKw(scale(responseLoad));
            points.add(point);
        }
        return points;
    }

    public static List<ResponseLoadStatRowVO> buildStatistics(List<ResponseLoadPointVO> points, LocalDate date) {
        if (points == null || points.isEmpty()) {
            return Collections.emptyList();
        }
        ResponseLoadStatRowVO actualRow = buildStatRow(points, "ACTUAL", "实际用电量", true);
        ResponseLoadStatRowVO predictedRow = buildStatRow(points, "PREDICTED", "预测用电量", false);
        List<ResponseLoadStatRowVO> rows = new ArrayList<>(2);
        rows.add(actualRow);
        rows.add(predictedRow);
        return rows;
    }

    public static BigDecimal resolveDeclaredCapacity(VppDrEvent event,
                                                     List<VppDrParticipation> participations,
                                                     List<VppDrExecution> executions) {
        if (executions != null && !executions.isEmpty()) {
            BigDecimal total = BigDecimal.ZERO;
            for (VppDrExecution execution : executions) {
                if (execution.getTargetKw() != null) {
                    total = total.add(execution.getTargetKw());
                }
            }
            if (total.compareTo(BigDecimal.ZERO) > 0) {
                return total;
            }
        }
        if (participations != null && !participations.isEmpty()) {
            BigDecimal total = BigDecimal.ZERO;
            for (VppDrParticipation participation : participations) {
                BigDecimal cap = participation.getClearedCapacityKw() != null
                        ? participation.getClearedCapacityKw()
                        : participation.getDeclaredCapacityKw();
                if (cap != null) {
                    total = total.add(cap);
                }
            }
            if (total.compareTo(BigDecimal.ZERO) > 0) {
                return total;
            }
        }
        if (event != null && event.getClearedCapacityKw() != null) {
            return event.getClearedCapacityKw();
        }
        if (event != null && event.getTargetCapacityKw() != null) {
            return event.getTargetCapacityKw();
        }
        return BigDecimal.ZERO;
    }

    private static ResponseLoadStatRowVO buildStatRow(List<ResponseLoadPointVO> points,
                                                      String dataType,
                                                      String label,
                                                      boolean actual) {
        ResponseLoadStatRowVO row = new ResponseLoadStatRowVO();
        row.setDataType(dataType);
        row.setDataTypeLabel(label);

        Map<String, List<BigDecimal>> hourlyBuckets = new LinkedHashMap<>();
        for (int h = 0; h < 24; h++) {
            hourlyBuckets.put(LocalTime.of(h, 0).format(HOUR_FMT), new ArrayList<>());
        }

        List<BigDecimal> allKw = new ArrayList<>();
        for (ResponseLoadPointVO point : points) {
            BigDecimal kw = actual ? point.getActualLoadKw() : point.getBaselineLoadKw();
            if (kw == null) {
                continue;
            }
            // baselineLoadKw 已是修正后的预测基线（原始基线 × K），无需伪随机调整
            allKw.add(kw);
            String hourKey = point.getTime().substring(0, 2) + ":00";
            hourlyBuckets.computeIfAbsent(hourKey, k -> new ArrayList<>()).add(kw);
        }

        Map<String, BigDecimal> hourlyValues = new LinkedHashMap<>();
        BigDecimal dayTotalKwh = BigDecimal.ZERO;
        for (Map.Entry<String, List<BigDecimal>> entry : hourlyBuckets.entrySet()) {
            BigDecimal hourKwh = average(entry.getValue())
                    .multiply(BigDecimal.valueOf(BUCKET_MINUTES))
                    .divide(BigDecimal.valueOf(60), 2, RoundingMode.HALF_UP);
            hourlyValues.put(entry.getKey(), hourKwh);
            dayTotalKwh = dayTotalKwh.add(hourKwh);
        }

        row.setHourlyValues(hourlyValues);
        row.setTotal(dayTotalKwh.setScale(2, RoundingMode.HALF_UP));
        row.setMax(max(allKw));
        row.setMin(min(allKw));
        row.setAvg(average(allKw));
        return row;
    }

    /*
    private static BigDecimal mockAggregate(List<VppResourcePoint> resources,
                                            List<VppDevice> devices,
                                            long seed,
                                            int minuteOfDay,
                                            double hourFactor,
                                            int seriesSalt) {
        if (resources == null || resources.isEmpty()) {
            double base = 580 * hourFactor + pseudoRandom(seed, 0L, minuteOfDay, seriesSalt) * 80;
            return BigDecimal.valueOf(base).setScale(2, RoundingMode.HALF_UP);
        }
        BigDecimal total = BigDecimal.ZERO;
        for (VppResourcePoint resource : resources) {
            BigDecimal cap = resource.getCapacityKw() != null ? resource.getCapacityKw() : BigDecimal.valueOf(100);
            double noise = 0.85 + pseudoRandom(seed, resource.getId() != null ? resource.getId() : 0L,
                    minuteOfDay, seriesSalt) * 0.3;
            double value = cap.doubleValue() * hourFactor * 0.6 * noise;
            total = total.add(BigDecimal.valueOf(value));
        }
        if (seriesSalt == 1) {
            total = total.multiply(BigDecimal.valueOf(0.95 + pseudoRandom(seed, 999, minuteOfDay, seriesSalt) * 0.08));
        }
        return total.setScale(2, RoundingMode.HALF_UP);
    }
    */

    private static BigDecimal sumBucketPower(Map<String, Map<String, TreeMap<LocalDateTime, BigDecimal>>> deviceData,
                                             List<String> metricCandidates,
                                             LocalDateTime bucketStart,
                                             LocalDateTime bucketEnd) {
        if (deviceData == null || deviceData.isEmpty()) {
            return BigDecimal.ZERO;
        }
        BigDecimal total = BigDecimal.ZERO;
        for (Map<String, TreeMap<LocalDateTime, BigDecimal>> metricMap : deviceData.values()) {
            TreeMap<LocalDateTime, BigDecimal> series = pickSeries(metricMap, metricCandidates);
            if (series == null || series.isEmpty()) {
                continue;
            }
            BigDecimal avg = averageInRange(series, bucketStart, bucketEnd);
            if (avg != null) {
                total = total.add(avg);
            }
        }
        return total;
    }

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

    private static BigDecimal averageInRange(TreeMap<LocalDateTime, BigDecimal> series,
                                              LocalDateTime start,
                                              LocalDateTime end) {
        BigDecimal sum = BigDecimal.ZERO;
        int count = 0;
        for (Map.Entry<LocalDateTime, BigDecimal> entry : series.subMap(start, true, end, true).entrySet()) {
            if (entry.getValue() != null) {
                sum = sum.add(entry.getValue());
                count++;
            }
        }
        if (count == 0) {
            return null;
        }
        return sum.divide(BigDecimal.valueOf(count), 4, RoundingMode.HALF_UP);
    }

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

    private static BigDecimal average(List<BigDecimal> values) {
        if (values == null || values.isEmpty()) {
            return BigDecimal.ZERO;
        }
        BigDecimal sum = BigDecimal.ZERO;
        for (BigDecimal v : values) {
            sum = sum.add(v);
        }
        return sum.divide(BigDecimal.valueOf(values.size()), 2, RoundingMode.HALF_UP);
    }

    private static BigDecimal max(List<BigDecimal> values) {
        return values.stream().max(BigDecimal::compareTo).orElse(BigDecimal.ZERO).setScale(2, RoundingMode.HALF_UP);
    }

    private static BigDecimal min(List<BigDecimal> values) {
        return values.stream().min(BigDecimal::compareTo).orElse(BigDecimal.ZERO).setScale(2, RoundingMode.HALF_UP);
    }

    /*
    private static double pseudo(String time) {
        if (time == null) {
            return 0.5;
        }
        return (time.hashCode() & 0x7FFFFFFF) % 100 / 100.0;
    }

    private static double pseudoRandom(long seed, long a, int b, int c) {
        long x = seed * 31 + a * 17 + b * 13L + c;
        x = ((x >> 16) ^ x) * 0x45d9f3b;
        x = ((x >> 16) ^ x) * 0x45d9f3b;
        x = (x >> 16) ^ x;
        return (x & 0x7FFFFFFF) / (double) 0x7FFFFFFF;
    }

    private static double dayTimeFactor(LocalTime time) {
        int hour = time.getHour();
        int minute = time.getMinute();
        double h = hour + minute / 60.0;
        if (h < 6) {
            return 0.45 + h / 6 * 0.15;
        }
        if (h < 9) {
            return 0.6 + (h - 6) / 3 * 0.35;
        }
        if (h < 12) {
            return 0.95 + (h - 9) / 3 * 0.05;
        }
        if (h < 14) {
            return 0.85;
        }
        if (h < 18) {
            return 0.75 + (h - 14) / 4 * 0.25;
        }
        if (h < 21) {
            return 1.0 - (h - 18) / 3 * 0.2;
        }
        return 0.55 - (h - 21) / 3 * 0.15;
    }
    */
}
