package com.usky.vpp.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.usky.common.core.exception.BusinessException;
import com.usky.common.security.utils.SecurityUtils;
import com.usky.vpp.constant.VppTsdbConstants;
import com.usky.vpp.domain.VppDevice;
import com.usky.vpp.domain.VppResourcePoint;
import com.usky.vpp.mapper.VppDeviceMapper;
import com.usky.vpp.mapper.VppResourcePointMapper;
import com.usky.vpp.service.VppResourceOverviewService;
import com.usky.vpp.service.VppTsdbQueryService;
import com.usky.vpp.service.vo.AlarmLevelStatVO;
import com.usky.vpp.service.vo.EnergyTodayVO;
import com.usky.vpp.service.vo.ResourceCurvePointVO;
import com.usky.vpp.service.vo.ResourceCurveSeriesVO;
import com.usky.vpp.service.vo.ResourceOverviewRealtimeCurveVO;
import com.usky.vpp.service.vo.ResourceTypeStatVO;
import com.usky.vpp.util.VppAuditHelper;
import com.usky.vpp.util.VppResourceTypeHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;
import java.util.stream.Collectors;

@Service
public class VppResourceOverviewServiceImpl implements VppResourceOverviewService {

    private static final List<String> RESOURCE_TYPES = Arrays.asList("PV", "ESS", "EVCS", "IND_LOAD", "COM_BLDG");
    private static final DateTimeFormatter TIME_HH_MM = DateTimeFormatter.ofPattern("HH:mm");
    private static final DateTimeFormatter TSDB_TIME = DateTimeFormatter.ofPattern(VppTsdbConstants.TIME_FORMAT);

    @Autowired
    private VppResourcePointMapper resourcePointMapper;
    @Autowired
    private VppDeviceMapper deviceMapper;
    @Autowired
    private VppTsdbQueryService vppTsdbQueryService;

    @Override
    public List<ResourceTypeStatVO> getTypeStats() {
        return buildTypeStats(listActiveResources());
    }

    @Override
    public EnergyTodayVO getEnergyToday() {
        List<VppResourcePoint> resources = listActiveResources();
        Map<Long, VppDevice> deviceMap = loadDeviceMap(resources);
        return buildEnergyToday(resources, deviceMap);
    }

    @Override
    public List<AlarmLevelStatVO> getAlarmStats() {
        // return buildAlarmStatsMock();
        return buildAlarmStats();
    }

    @Override
    public ResourceOverviewRealtimeCurveVO getRealtimeCurves(String startTime, String endTime) {
        LocalDateTime end = parseDateTime(endTime, LocalDateTime.now());
        LocalDateTime start = parseDateTime(startTime, LocalDate.now().atStartOfDay());
        if (start.isAfter(end)) {
            throw new BusinessException("开始时间不能晚于结束时间");
        }

        List<VppDevice> devices = listActiveDevices();
        Map<Long, String> deviceResourceTypeMap = buildDeviceResourceTypeMap();
        List<String> allDeviceUuids = resolveDeviceUuids(devices);
        List<String> pvDeviceUuids = resolveDeviceUuidsByResourceType(devices, deviceResourceTypeMap, "PV");
        List<String> essDeviceUuids = resolveDeviceUuidsByResourceType(devices, deviceResourceTypeMap, "ESS");

        ResourceOverviewRealtimeCurveVO curve = new ResourceOverviewRealtimeCurveVO();
        curve.setLoadCurve(buildPowerCurveSeries("实时负荷曲线", "kW", allDeviceUuids, start, end));
        curve.setPvOutputCurve(buildPowerCurveSeries("光伏出力曲线", "kW", pvDeviceUuids, start, end));
        curve.setEssSocCurve(buildSocCurveSeries("储能SOC曲线", essDeviceUuids, start, end));
        return curve;
    }

    private List<VppResourcePoint> listActiveResources() {
        return resourcePointMapper.selectList(
                new LambdaQueryWrapper<VppResourcePoint>()
                        .eq(VppResourcePoint::getDeleteFlag, VppAuditHelper.NOT_DELETED)
                        .eq(VppResourcePoint::getTenantId, SecurityUtils.getTenantId()));
    }

    private List<VppDevice> listActiveDevices() {
        return deviceMapper.selectList(
                new LambdaQueryWrapper<VppDevice>()
                        .eq(VppDevice::getDeleteFlag, VppAuditHelper.NOT_DELETED)
                        .eq(VppDevice::getTenantId, SecurityUtils.getTenantId()));
    }

    private Map<Long, String> buildDeviceResourceTypeMap() {
        Map<Long, String> deviceResourceTypeMap = new HashMap<>();
        for (VppResourcePoint resource : listActiveResources()) {
            if (resource.getDeviceId() == null || !StringUtils.hasText(resource.getResourceType())) {
                continue;
            }
            deviceResourceTypeMap.putIfAbsent(resource.getDeviceId(), resource.getResourceType().toUpperCase());
        }
        return deviceResourceTypeMap;
    }

    private ResourceCurveSeriesVO buildPowerCurveSeries(String name, String unit, List<String> deviceUuids,
                                                        LocalDateTime start, LocalDateTime end) {
        Map<LocalDateTime, BigDecimal> aggregated = queryAggregatedSum(deviceUuids, start, end,
                Collections.singletonList(VppTsdbConstants.METRIC_P));
        return toCurveSeries(name, unit, aggregated);
    }

    private ResourceCurveSeriesVO buildSocCurveSeries(String name, List<String> deviceUuids,
                                                      LocalDateTime start, LocalDateTime end) {
        Map<LocalDateTime, BigDecimal> aggregated = queryAggregatedAverage(deviceUuids, start, end,
                Collections.singletonList(VppTsdbConstants.METRIC_SOC));
        return toCurveSeries(name, "%", aggregated);
    }

    private Map<LocalDateTime, BigDecimal> queryAggregatedSum(List<String> deviceUuids, LocalDateTime start,
                                                              LocalDateTime end, List<String> metrics) {
        if (CollectionUtils.isEmpty(deviceUuids)) {
            return Collections.emptyMap();
        }
        Map<String, Map<String, TreeMap<LocalDateTime, BigDecimal>>> history =
                vppTsdbQueryService.queryDeviceMetricHistory(deviceUuids, start, end, metrics);
        return aggregateSumByTime(history, metrics.get(0));
    }

    private Map<LocalDateTime, BigDecimal> queryAggregatedAverage(List<String> deviceUuids, LocalDateTime start,
                                                                  LocalDateTime end, List<String> metrics) {
        if (CollectionUtils.isEmpty(deviceUuids)) {
            return Collections.emptyMap();
        }
        Map<String, Map<String, TreeMap<LocalDateTime, BigDecimal>>> history =
                vppTsdbQueryService.queryDeviceMetricHistory(deviceUuids, start, end, metrics);
        return aggregateAverageByTime(history, metrics.get(0));
    }

    private Map<LocalDateTime, BigDecimal> aggregateSumByTime(
            Map<String, Map<String, TreeMap<LocalDateTime, BigDecimal>>> history, String metric) {
        if (CollectionUtils.isEmpty(history)) {
            return Collections.emptyMap();
        }
        Map<LocalDateTime, BigDecimal> result = new TreeMap<>();
        for (Map<String, TreeMap<LocalDateTime, BigDecimal>> deviceMetrics : history.values()) {
            TreeMap<LocalDateTime, BigDecimal> series = deviceMetrics.get(metric);
            if (series == null || series.isEmpty()) {
                continue;
            }
            for (Map.Entry<LocalDateTime, BigDecimal> entry : series.entrySet()) {
                result.merge(entry.getKey(), entry.getValue(), BigDecimal::add);
            }
        }
        return result;
    }

    private Map<LocalDateTime, BigDecimal> aggregateAverageByTime(
            Map<String, Map<String, TreeMap<LocalDateTime, BigDecimal>>> history, String metric) {
        if (CollectionUtils.isEmpty(history)) {
            return Collections.emptyMap();
        }
        Map<LocalDateTime, List<BigDecimal>> samples = new TreeMap<>();
        for (Map<String, TreeMap<LocalDateTime, BigDecimal>> deviceMetrics : history.values()) {
            TreeMap<LocalDateTime, BigDecimal> series = deviceMetrics.get(metric);
            if (series == null || series.isEmpty()) {
                continue;
            }
            for (Map.Entry<LocalDateTime, BigDecimal> entry : series.entrySet()) {
                samples.computeIfAbsent(entry.getKey(), key -> new ArrayList<>()).add(entry.getValue());
            }
        }
        Map<LocalDateTime, BigDecimal> result = new TreeMap<>();
        samples.forEach((timestamp, values) -> {
            BigDecimal avg = averageValues(values);
            if (avg != null) {
                result.put(timestamp, avg);
            }
        });
        return result;
    }

    private BigDecimal averageValues(List<BigDecimal> values) {
        if (CollectionUtils.isEmpty(values)) {
            return null;
        }
        List<BigDecimal> nonNullValues = values.stream()
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
        if (nonNullValues.isEmpty()) {
            return null;
        }
        BigDecimal sum = nonNullValues.stream().reduce(BigDecimal.ZERO, BigDecimal::add);
        return sum.divide(BigDecimal.valueOf(nonNullValues.size()), 4, RoundingMode.HALF_UP);
    }

    private ResourceCurveSeriesVO toCurveSeries(String name, String unit, Map<LocalDateTime, BigDecimal> aggregated) {
        ResourceCurveSeriesVO series = new ResourceCurveSeriesVO();
        series.setName(name);
        series.setUnit(unit);
        List<ResourceCurvePointVO> points = new ArrayList<>();
        for (Map.Entry<LocalDateTime, BigDecimal> entry : aggregated.entrySet()) {
            ResourceCurvePointVO point = new ResourceCurvePointVO();
            point.setTime(entry.getKey().format(TIME_HH_MM));
            point.setValue(entry.getValue().setScale(2, RoundingMode.HALF_UP));
            points.add(point);
        }
        series.setPoints(points);
        series.setCurrentValue(aggregated.entrySet().stream()
                .max(Map.Entry.comparingByKey())
                .map(entry -> entry.getValue().setScale(2, RoundingMode.HALF_UP))
                .orElse(BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP)));
        return series;
    }

    private List<String> resolveDeviceUuids(List<VppDevice> devices) {
        if (CollectionUtils.isEmpty(devices)) {
            return Collections.emptyList();
        }
        return devices.stream()
                .map(VppDevice::getDeviceUuid)
                .filter(StringUtils::hasText)
                .map(String::trim)
                .distinct()
                .collect(Collectors.toList());
    }

    private List<String> resolveDeviceUuidsByResourceType(List<VppDevice> devices,
                                                          Map<Long, String> deviceResourceTypeMap,
                                                          String resourceType) {
        if (CollectionUtils.isEmpty(devices) || !StringUtils.hasText(resourceType)) {
            return Collections.emptyList();
        }
        String targetType = resourceType.toUpperCase();
        Set<String> uuids = new LinkedHashSet<>();
        for (VppDevice device : devices) {
            if (device.getId() == null || !StringUtils.hasText(device.getDeviceUuid())) {
                continue;
            }
            if (targetType.equals(deviceResourceTypeMap.get(device.getId()))) {
                uuids.add(device.getDeviceUuid().trim());
            }
        }
        return new ArrayList<>(uuids);
    }

    private LocalDateTime parseDateTime(String value, LocalDateTime defaultValue) {
        if (!StringUtils.hasText(value)) {
            return defaultValue;
        }
        try {
            return LocalDateTime.parse(value.trim(), TSDB_TIME);
        } catch (DateTimeParseException ex) {
            throw new BusinessException("时间格式无效，请使用 yyyy-MM-dd HH:mm:ss");
        }
    }

    private List<ResourceTypeStatVO> buildTypeStats(List<VppResourcePoint> resources) {
        Map<String, ResourceTypeStatVO> statMap = new LinkedHashMap<>();
        for (String type : RESOURCE_TYPES) {
            ResourceTypeStatVO stat = new ResourceTypeStatVO();
            stat.setResourceType(type);
            stat.setResourceTypeLabel(VppResourceTypeHelper.resourceTypeLabel(type));
            stat.setTotalCount(0L);
            stat.setTotalCapacityKw(BigDecimal.ZERO);
            stat.setTotalAdjustableCapacityKw(BigDecimal.ZERO);
            statMap.put(type, stat);
        }
        for (VppResourcePoint resource : resources) {
            String type = resource.getResourceType() != null ? resource.getResourceType().toUpperCase() : "OTHER";
            ResourceTypeStatVO stat = statMap.computeIfAbsent(type, key -> {
                ResourceTypeStatVO s = new ResourceTypeStatVO();
                s.setResourceType(key);
                s.setResourceTypeLabel(VppResourceTypeHelper.resourceTypeLabel(key));
                s.setTotalCount(0L);
                s.setTotalCapacityKw(BigDecimal.ZERO);
                s.setTotalAdjustableCapacityKw(BigDecimal.ZERO);
                return s;
            });
            stat.setTotalCount(stat.getTotalCount() + 1);
            BigDecimal cap = resource.getCapacityKw() != null ? resource.getCapacityKw() : BigDecimal.ZERO;
            stat.setTotalCapacityKw(stat.getTotalCapacityKw().add(cap));
            if (resource.getIsControl() != null && resource.getIsControl() == 1) {
                stat.setTotalAdjustableCapacityKw(stat.getTotalAdjustableCapacityKw().add(cap));
            }
        }
        return new ArrayList<>(statMap.values());
    }

    private EnergyTodayVO buildEnergyToday(List<VppResourcePoint> resources, Map<Long, VppDevice> deviceMap) {
        List<VppResourcePoint> pvResources = resources.stream()
                .filter(r -> "PV".equalsIgnoreCase(r.getResourceType()))
                .collect(Collectors.toList());
        List<VppResourcePoint> loadResources = resources.stream()
                .filter(r -> !"PV".equalsIgnoreCase(r.getResourceType()))
                .collect(Collectors.toList());

        List<String> pvDeviceUuids = resolveDeviceUuids(pvResources, deviceMap);
        List<String> loadDeviceUuids = resolveDeviceUuids(loadResources, deviceMap);

        LocalDateTime todayStart = LocalDate.now().atStartOfDay();
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime yesterdayStart = todayStart.minusDays(1);
        LocalDateTime yesterdayEnd = yesterdayStart.plus(ChronoUnit.MINUTES.between(todayStart, now), ChronoUnit.MINUTES);

        BigDecimal totalGeneration = queryTodayEnergy(pvDeviceUuids, todayStart, now,
                Collections.singletonList(VppTsdbConstants.METRIC_EDAY), true);
        BigDecimal totalConsumption = queryTodayEnergy(loadDeviceUuids, todayStart, now,
                Collections.singletonList(VppTsdbConstants.METRIC_EPP), false);
        BigDecimal yesterdayGeneration = queryTodayEnergy(pvDeviceUuids, yesterdayStart, yesterdayEnd,
                Collections.singletonList(VppTsdbConstants.METRIC_EDAY), true);
        BigDecimal yesterdayConsumption = queryTodayEnergy(loadDeviceUuids, yesterdayStart, yesterdayEnd,
                Collections.singletonList(VppTsdbConstants.METRIC_EPP), false);

        BigDecimal totalGenerationScaled = totalGeneration.setScale(2, RoundingMode.HALF_UP);
        BigDecimal totalConsumptionScaled = totalConsumption.setScale(2, RoundingMode.HALF_UP);
        BigDecimal greenEnergyRatio = calculateGreenEnergyRatio(totalGenerationScaled, totalConsumptionScaled);
        BigDecimal yesterdayGreenEnergyRatio = calculateGreenEnergyRatio(
                yesterdayGeneration.setScale(2, RoundingMode.HALF_UP),
                yesterdayConsumption.setScale(2, RoundingMode.HALF_UP));

        EnergyTodayVO energy = new EnergyTodayVO();
        energy.setTotalGenerationKwh(totalGenerationScaled);
        energy.setTotalGenerationKwhRingRatio(calculateRingRatio(totalGeneration, yesterdayGeneration));
        energy.setTotalConsumptionKwh(totalConsumptionScaled);
        energy.setTotalConsumptionKwhRingRatio(calculateRingRatio(totalConsumption, yesterdayConsumption));
        energy.setGreenEnergyRatioPercent(greenEnergyRatio);
        energy.setGreenEnergyRatioPercentRingRatio(calculateRingRatio(greenEnergyRatio, yesterdayGreenEnergyRatio));
        return energy;
    }

    private BigDecimal calculateGreenEnergyRatio(BigDecimal generationKwh, BigDecimal consumptionKwh) {
        if (consumptionKwh == null || consumptionKwh.compareTo(BigDecimal.ZERO) <= 0) {
            return BigDecimal.ZERO;
        }
        BigDecimal generation = generationKwh != null ? generationKwh : BigDecimal.ZERO;
        return generation.multiply(BigDecimal.valueOf(100))
                .divide(consumptionKwh, 2, RoundingMode.HALF_UP)
                .min(BigDecimal.valueOf(100));
    }

    private BigDecimal calculateRingRatio(BigDecimal current, BigDecimal previous) {
        if (current == null) {
            current = BigDecimal.ZERO;
        }
        if (previous == null || previous.compareTo(BigDecimal.ZERO) == 0) {
            return BigDecimal.ZERO;
        }
        return current.subtract(previous)
                .multiply(BigDecimal.valueOf(100))
                .divide(previous, 2, RoundingMode.HALF_UP);
    }

    private BigDecimal queryTodayEnergy(List<String> deviceUuids, LocalDateTime startTime, LocalDateTime endTime,
                                          List<String> metrics, boolean useLatestValue) {
        if (CollectionUtils.isEmpty(deviceUuids)) {
            return BigDecimal.ZERO;
        }
        Map<String, Map<String, TreeMap<LocalDateTime, BigDecimal>>> history =
                vppTsdbQueryService.queryDeviceMetricHistory(deviceUuids, startTime, endTime, metrics);
        if (CollectionUtils.isEmpty(history)) {
            return BigDecimal.ZERO;
        }
        String metric = metrics.get(0);
        return useLatestValue ? sumLatestMetric(history, metric) : sumDiffMetric(history, metric);
    }

    private BigDecimal sumLatestMetric(Map<String, Map<String, TreeMap<LocalDateTime, BigDecimal>>> history, String metric) {
        BigDecimal total = BigDecimal.ZERO;
        for (Map<String, TreeMap<LocalDateTime, BigDecimal>> deviceMetrics : history.values()) {
            TreeMap<LocalDateTime, BigDecimal> series = deviceMetrics.get(metric);
            if (series == null || series.isEmpty()) {
                continue;
            }
            total = total.add(series.lastEntry().getValue());
        }
        return total;
    }

    private BigDecimal sumDiffMetric(Map<String, Map<String, TreeMap<LocalDateTime, BigDecimal>>> history, String metric) {
        BigDecimal total = BigDecimal.ZERO;
        for (Map<String, TreeMap<LocalDateTime, BigDecimal>> deviceMetrics : history.values()) {
            TreeMap<LocalDateTime, BigDecimal> series = deviceMetrics.get(metric);
            if (series == null || series.isEmpty()) {
                continue;
            }
            BigDecimal first = series.firstEntry().getValue();
            BigDecimal last = series.lastEntry().getValue();
            BigDecimal diff = last.subtract(first);
            if (diff.compareTo(BigDecimal.ZERO) > 0) {
                total = total.add(diff);
            }
        }
        return total;
    }

    private Map<Long, VppDevice> loadDeviceMap(List<VppResourcePoint> resources) {
        Set<Long> deviceIds = resources.stream()
                .map(VppResourcePoint::getDeviceId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        if (deviceIds.isEmpty()) {
            return Collections.emptyMap();
        }
        return deviceMapper.selectBatchIds(deviceIds).stream()
                .filter(d -> !VppAuditHelper.isDeleted(d.getDeleteFlag()))
                .collect(Collectors.toMap(VppDevice::getId, d -> d, (a, b) -> a));
    }

    private List<String> resolveDeviceUuids(List<VppResourcePoint> resources, Map<Long, VppDevice> deviceMap) {
        if (CollectionUtils.isEmpty(resources) || CollectionUtils.isEmpty(deviceMap)) {
            return Collections.emptyList();
        }
        Set<String> uuids = new LinkedHashSet<>();
        for (VppResourcePoint resource : resources) {
            if (resource.getDeviceId() == null) {
                continue;
            }
            VppDevice device = deviceMap.get(resource.getDeviceId());
            if (device == null || !StringUtils.hasText(device.getDeviceUuid())) {
                continue;
            }
            uuids.add(device.getDeviceUuid().trim());
        }
        return new ArrayList<>(uuids);
    }

    private List<AlarmLevelStatVO> buildAlarmStats() {
        List<AlarmLevelStatVO> stats = new ArrayList<>();
        stats.add(buildAlarmStat(1, "紧急", 0L));
        stats.add(buildAlarmStat(2, "重要", 0L));
        stats.add(buildAlarmStat(3, "一般", 0L));
        return stats;
    }

    /*
    private List<AlarmLevelStatVO> buildAlarmStatsMock() {
        List<AlarmLevelStatVO> stats = new ArrayList<>();
        stats.add(buildAlarmStat(1, "紧急", 3L));
        stats.add(buildAlarmStat(2, "重要", 8L));
        stats.add(buildAlarmStat(3, "一般", 15L));
        return stats;
    }
    */

    private AlarmLevelStatVO buildAlarmStat(int level, String label, long count) {
        AlarmLevelStatVO stat = new AlarmLevelStatVO();
        stat.setAlarmLevel(level);
        stat.setAlarmLevelLabel(label);
        stat.setCount(count);
        stat.setAlarmListPath("/alarm?alarmLevel=" + level);
        return stat;
    }
}
