package com.usky.ems.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.usky.common.core.bean.ApiResult;
import com.usky.common.security.utils.SecurityUtils;
import com.usky.demo.RemoteTsdbProxyService;
import com.usky.demo.domain.EnergyItemSumQueryVO;
import com.usky.demo.domain.EnergyItemSumResultVO;
import com.usky.demo.domain.EnergyItemTrendPointVO;
import com.usky.demo.domain.EnergyItemTrendQueryVO;
import com.usky.demo.domain.EnergyItemTrendResultVO;
import com.usky.ems.domain.*;
import com.usky.ems.mapper.BaseSpaceGatewayMapper;
import com.usky.ems.mapper.DmpProductMapper;
import com.usky.ems.mapper.EmsDeviceItemCodeMapper;
import com.usky.ems.mapper.EmsDeviceMapper;
import com.usky.ems.mapper.EmsEnergyItemCodeMapper;
import com.usky.ems.mapper.EmsProjectConversionFactorMapper;
import com.usky.ems.mapper.EmsProjectDeviceSystemMapper;
import com.usky.ems.mapper.EmsProjectMapper;
import com.usky.ems.service.EmsModelService;
import com.usky.ems.service.EmsOverviewService;
import com.usky.ems.service.BaseSpaceService;
import com.usky.ems.service.DmpDeviceService;
import com.usky.ems.service.vo.EmsEnergyTypeVO;
import com.usky.ems.service.vo.EmsOverviewDeviceSystemStatVO;
import com.usky.ems.service.vo.EmsOverviewEnergyItemVO;
import com.usky.ems.service.vo.EmsProjectResponse;
import com.usky.ems.service.vo.EmsSummaryResponse;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

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.time.format.DateTimeFormatterBuilder;
import java.time.temporal.ChronoField;
import java.time.temporal.TemporalAdjusters;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.LinkedHashMap;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 能源总览服务实现
 */
@Service
public class EmsOverviewServiceImpl implements EmsOverviewService {

    private static final DateTimeFormatter TSDB_TIME_FORMAT =
            DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
    /** 兼容 TDengine/JDBC 返回的可变小数秒，如 2026-06-03 00:00:00.0 */
    private static final DateTimeFormatter TSDB_FLEXIBLE_TIME_FORMAT = new DateTimeFormatterBuilder()
            .appendPattern("yyyy-MM-dd HH:mm:ss")
            .optionalStart()
            .appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true)
            .optionalEnd()
            .toFormatter();
    private static final BigDecimal FALLBACK_CO2_FACTOR = new BigDecimal("2.4589");
    private static final BigDecimal FALLBACK_COAL_FACTOR = new BigDecimal("0.70");
    private static final String FACTOR_NAME_COAL = "coal";
    private static final String FACTOR_NAME_CO2 = "co2";

    @Autowired
    private EmsModelService emsModelService;

    @Autowired
    private EmsDeviceMapper emsDeviceMapper;

    @Autowired
    private BaseSpaceService baseSpaceService;

    @Autowired
    private EmsEnergyItemCodeMapper emsEnergyItemCodeMapper;

    @Autowired
    private EmsDeviceItemCodeMapper emsDeviceItemCodeMapper;

    @Autowired
    private EmsProjectMapper emsProjectMapper;

    @Autowired
    private EmsProjectConversionFactorMapper emsProjectConversionFactorMapper;

    @Autowired
    private EmsProjectDeviceSystemMapper emsProjectDeviceSystemMapper;

    @Autowired
    private DmpProductMapper dmpProductMapper;

    @Autowired
    private BaseSpaceGatewayMapper baseSpaceGatewayMapper;

    @Autowired
    private DmpDeviceService dmpDeviceService;

    @Autowired
    private RemoteTsdbProxyService remoteTsdbProxyService;

    @Override
    public EmsProjectResponse getProject(Integer projectId) {
        LambdaQueryWrapper<EmsProject> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(EmsProject::getTenantId, SecurityUtils.getTenantId());
        EmsProject project;
        if (projectId != null) {
            wrapper.eq(EmsProject::getId, projectId);
            project = emsProjectMapper.selectOne(wrapper);
        } else {
            List<EmsProject> list = emsProjectMapper.selectList(wrapper);
            project = list.isEmpty() ? null : list.get(0);
        }
        if (project == null) {
            return null;
        }
        EmsProjectResponse resp = new EmsProjectResponse();
        resp.setId(project.getId());
        resp.setSpaceId(project.getSpaceId());
        resp.setName(project.getName());
        resp.setAbbreviation(project.getAbbreviation());
        resp.setArea(project.getArea());
        resp.setCommonArea(project.getCommonArea());
        resp.setAirConditionedArea(project.getAirConditionedArea());
        // 源字段为 Integer，这里转为 BigDecimal 以匹配 VO 定义
        if (project.getResidentPopulation() != null) {
            resp.setResidentPopulation(java.math.BigDecimal.valueOf(project.getResidentPopulation()));
        }

        // 设备系统集合
        List<EmsProjectDeviceSystem> sysList = emsProjectDeviceSystemMapper.selectList(
                new LambdaQueryWrapper<EmsProjectDeviceSystem>()
                        .eq(EmsProjectDeviceSystem::getProjectId, project.getId())
        );
        if (sysList != null && !sysList.isEmpty()) {
            resp.setDeviceSystemList(sysList.stream()
                    .map(EmsProjectDeviceSystem::getDeviceSystem)
                    .collect(Collectors.toList()));
        }

        resp.setProvinceCode(project.getProvinceCode());
        resp.setCityCode(project.getCityCode());
        resp.setDistrictCode(project.getDistrictCode());
        resp.setProvinceName(project.getProvinceName());
        resp.setCityName(project.getCityName());
        resp.setDistrictName(project.getDistrictName());
        resp.setLocation(project.getLocation());
        resp.setAddress(project.getAddress());
        resp.setTypeId(project.getTypeId());
        resp.setTypeName(project.getTypeName());
        resp.setImage(project.getImage());
        // imageUrl 视实际文件服务而定，这里暂不拼接，直接沿用 image 字段或留空
        resp.setImageUrl(project.getImage());
        resp.setIntroduction(project.getIntroduction());
        resp.setPlatformName(project.getPlatformName());
        resp.setLogo(project.getLogo());
        resp.setLogoUrl(project.getLogo());
        resp.setLogoMin(project.getLogoMin());
        resp.setLogoMinUrl(project.getLogoMin());
        resp.setUpdatedBy(project.getUpdatedBy());
        resp.setCreatedBy(project.getCreatedBy());
        if (project.getUpdateTime() != null) {
            resp.setUpdateTime(project.getUpdateTime().toString());
        }
        if (project.getCreateTime() != null) {
            resp.setCreateTime(project.getCreateTime().toString());
        }
        // 单位能耗暂无法从现有表直接计算，先置为空，后续可按业务补充
        resp.setAreaCoal(null);
        return resp;
    }

    @Override
    public EmsSummaryResponse getSummary(Long projectId, String timeDimension, String timeValue) {
        EmsSummaryResponse resp = new EmsSummaryResponse();
        resp.setTimeDimension(timeDimension);
        resp.setTimeValue(timeValue);
        return resp;
    }

    @Override
    public List<EmsOverviewEnergyItemVO> queryOverviewItem() {
        return emsModelService.getEnergyTypeList().stream()
                .map(type -> {
                    EmsOverviewEnergyItemVO vo = new EmsOverviewEnergyItemVO();
                    vo.setName(type.getName());
                    vo.setItemCode(type.getCode());
                    vo.setEnergyType(type.getId() != null ? type.getId().intValue() : null);
                    return vo;
                })
                .collect(Collectors.toList());
    }

    @Override
    public List<EmsOverviewDeviceSystemStatVO> queryOverviewDeviceInfo(Long projectId) {
//        Long pid = resolveProjectId(projectId);
//        if (pid == null) {
//            return java.util.Collections.emptyList();
//        }
//
//        // 1. 查询项目已配置的设备系统
//        List<EmsProjectDeviceSystem> systemList = emsProjectDeviceSystemMapper.selectList(
//                new LambdaQueryWrapper<EmsProjectDeviceSystem>()
//                        .eq(EmsProjectDeviceSystem::getProjectId, pid)
//        );
//        if (systemList == null || systemList.isEmpty()) {
//            return java.util.Collections.emptyList();
//        }
//        List<Integer> systems = systemList.stream()
//                .map(EmsProjectDeviceSystem::getDeviceSystem)
//                .collect(Collectors.toList());
//
//        // 2. 查询设备：同项目、启用状态、设备系统在上述集合中
//        List<EmsDevice> devices = emsDeviceMapper.selectList(
//                new LambdaQueryWrapper<EmsDevice>()
//                        .eq(EmsDevice::getProjectId, pid)
//                        .eq(EmsDevice::getStatus, 1)
//                        .in(EmsDevice::getDeviceSystem, systems)
//        );
//        java.util.Map<Integer, Long> deviceCountMap = devices.stream()
//                .filter(d -> d.getDeviceSystem() != null)
//                .collect(Collectors.groupingBy(EmsDevice::getDeviceSystem, Collectors.counting()));

        // 3. 组装统计 VO
        List<EmsOverviewDeviceSystemStatVO> result = new java.util.ArrayList<>();
//        for (EmsProjectDeviceSystem s : systemList) {
//            Integer code = s.getDeviceSystem();
//            EmsOverviewDeviceSystemStatVO vo = new EmsOverviewDeviceSystemStatVO();
//            vo.setDeviceSystem(code);
//            vo.setName(deviceSystemName(code));
//            vo.setValue(deviceCountMap.getOrDefault(code, 0L).intValue());
//            result.add(vo);
//        }
        return result;
    }

    /**
     * 分类能耗统计：按能源类型关联产品汇总 TSDB 用量，并计算环比/同比及折标煤、碳排放。
     */
    @Override
    public Map<String, Object> queryClassificationEnergy(Integer dateType, Integer energyType, Long projectId) {
        if (dateType == null || energyType == null) {
            return null;
        }

        Long resolvedProjectId = resolveProjectId(projectId);
        Map<String, BigDecimal> factorMap = loadConversionFactorMap(resolvedProjectId, energyType);
        BigDecimal coalFactor = factorMap.getOrDefault(FACTOR_NAME_COAL, FALLBACK_COAL_FACTOR);
        BigDecimal co2Factor = factorMap.getOrDefault(FACTOR_NAME_CO2, FALLBACK_CO2_FACTOR);

        List<String> productCodeList = dmpProductMapper.selectProductCodesByEnergyType(
                SecurityUtils.getTenantId(), energyType);
        if (productCodeList == null || productCodeList.isEmpty()) {
            return buildClassificationResult(BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO,
                    coalFactor, co2Factor);
        }

        String identifier = resolveRootIdentifierByEnergyType(energyType);
        if (identifier == null) {
            return buildClassificationResult(BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO,
                    coalFactor, co2Factor);
        }

        LocalDateTime endTime = LocalDateTime.now();
        LocalDateTime startTime = getStartTime(dateType, endTime);
        if (startTime == null) {
            return null;
        }

        BigDecimal data = sumEnergyByProductCodes(productCodeList, identifier, startTime, endTime);

        LocalDateTime pariPassuEndTime = endTime.minusYears(1);
        LocalDateTime pariPassuStartTime = getStartTime(dateType, pariPassuEndTime);
        BigDecimal pariPassuData = sumEnergyByProductCodes(productCodeList, identifier, pariPassuStartTime, pariPassuEndTime);

        LocalDateTime sequentialEndTime;
        if (dateType == 1) {
            sequentialEndTime = endTime.minusDays(1);
        } else if (dateType == 2) {
            sequentialEndTime = endTime.minusMonths(1);
        } else {
            sequentialEndTime = endTime.minusYears(1);
        }
        LocalDateTime sequentialStartTime = getStartTime(dateType, sequentialEndTime);
        BigDecimal sequentialData = sumEnergyByProductCodes(productCodeList, identifier, sequentialStartTime, sequentialEndTime);

        return buildClassificationResult(data, sequentialData, pariPassuData, coalFactor, co2Factor);
    }

    /**
     * 解析当前租户项目 ID（未传则取第一个项目）
     */
    private Long resolveProjectId(Long projectId) {
        LambdaQueryWrapper<EmsProject> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(EmsProject::getTenantId, SecurityUtils.getTenantId());
        if (projectId != null) {
            wrapper.eq(EmsProject::getId, projectId);
            EmsProject project = emsProjectMapper.selectOne(wrapper);
            return project != null ? project.getId() : null;
        }
        List<EmsProject> list = emsProjectMapper.selectList(wrapper);
        return list.isEmpty() ? null : list.get(0).getId();
    }

    /**
     * 加载折算系数：优先项目专属配置，否则退回 project_id=0 的公共配置
     */
    private Map<String, BigDecimal> loadConversionFactorMap(Long projectId, Integer energyType) {
        LambdaQueryWrapper<EmsProjectConversionFactor> wrapper = Wrappers.lambdaQuery();
        wrapper.eq(EmsProjectConversionFactor::getEnergyType, energyType);
        if (projectId != null) {
            wrapper.in(EmsProjectConversionFactor::getProjectId, projectId, 0L);
        } else {
            wrapper.eq(EmsProjectConversionFactor::getProjectId, 0L);
        }
        List<EmsProjectConversionFactor> factorList = emsProjectConversionFactorMapper.selectList(wrapper);
        if (factorList == null || factorList.isEmpty()) {
            return Collections.emptyMap();
        }
        List<EmsProjectConversionFactor> effectiveFactors = factorList;
        if (projectId != null) {
            effectiveFactors = factorList.stream()
                    .filter(f -> Objects.equals(f.getProjectId(), projectId))
                    .collect(Collectors.toList());
            if (effectiveFactors.isEmpty()) {
                effectiveFactors = factorList;
            }
        }
        return effectiveFactors.stream()
                .filter(f -> f.getName() != null && f.getValue() != null)
                .collect(Collectors.toMap(EmsProjectConversionFactor::getName,
                        EmsProjectConversionFactor::getValue, (oldVal, newVal) -> oldVal));
    }

    private BigDecimal sumEnergyByProductCodes(List<String> productCodeList, String fieldIdentifier,
                                               LocalDateTime startTime, LocalDateTime endTime) {
        return sumEnergyByProductCodes(productCodeList, fieldIdentifier, startTime, endTime, null);
    }

    private BigDecimal sumEnergyByProductCodes(List<String> productCodeList, String fieldIdentifier,
                                               LocalDateTime startTime, LocalDateTime endTime,
                                               List<String> deviceUuidList) {
        BigDecimal total = BigDecimal.ZERO;
        String start = formatTsdbTime(startTime);
        String end = formatTsdbTime(endTime);
        List<String> effectiveDeviceUuids = deviceUuidList == null ? null : deviceUuidList.stream()
                .filter(StringUtils::isNotBlank)
                .map(String::trim)
                .distinct()
                .collect(Collectors.toList());
        for (String productCode : productCodeList) {
            if (StringUtils.isBlank(productCode)) {
                continue;
            }
            EnergyItemSumQueryVO queryVO = new EnergyItemSumQueryVO();
            queryVO.setIdentifier(fieldIdentifier);
            queryVO.setSuperTable("super_" + productCode.trim());
            queryVO.setStartTime(start);
            queryVO.setEndTime(end);
            if (effectiveDeviceUuids != null && !effectiveDeviceUuids.isEmpty()) {
                queryVO.setDeviceUuid(effectiveDeviceUuids);
            }
            EnergyItemSumResultVO apiResult = remoteTsdbProxyService.sumEnergyItem(queryVO);
            if (apiResult == null || apiResult.getSumDiff() == null) {
                continue;
            }
            total = total.add(apiResult.getSumDiff());
        }
        return total;
    }

    private Map<String, Object> buildClassificationResult(BigDecimal data, BigDecimal sequentialData,
                                                          BigDecimal pariPassuData,
                                                          BigDecimal coal, BigDecimal co2Factor) {
        if (data == null) {
            data = BigDecimal.ZERO;
        }
        if (sequentialData == null) {
            sequentialData = BigDecimal.ZERO;
        }
        if (pariPassuData == null) {
            pariPassuData = BigDecimal.ZERO;
        }
        BigDecimal cost = BigDecimal.ZERO;

        BigDecimal coalAmount = data.multiply(coal).setScale(2, RoundingMode.UP);
        BigDecimal co2Amount = coalAmount.multiply(co2Factor).setScale(2, RoundingMode.UP);

        BigDecimal plantTree = BigDecimal.ZERO;
        if (co2Amount.compareTo(BigDecimal.ZERO) > 0) {
            plantTree = co2Amount.divide(new BigDecimal("18"), 2, RoundingMode.UP);
        }

        BigDecimal sequentialCon = BigDecimal.ZERO;
        BigDecimal sequentialCost = BigDecimal.ZERO;
        BigDecimal sequentialCoal = BigDecimal.ZERO;
        BigDecimal sequentialCo2 = BigDecimal.ZERO;
        BigDecimal sequentialPlantTree = BigDecimal.ZERO;
        if (sequentialData.compareTo(BigDecimal.ZERO) != 0) {
            BigDecimal sequentialCoalAmountData = sequentialData.multiply(coal).setScale(2, RoundingMode.UP);
            BigDecimal sequentialCo2AmountData = sequentialCoalAmountData.multiply(co2Factor)
                    .setScale(2, RoundingMode.UP);
            BigDecimal sequentialPlantTreeAmountData = sequentialCo2AmountData.divide(new BigDecimal("18"), 2, RoundingMode.UP);
            sequentialCon = data.subtract(sequentialData).divide(sequentialData, 2, RoundingMode.UP);
            sequentialCoal = coalAmount.subtract(sequentialCoalAmountData)
                    .divide(sequentialCoalAmountData, 2, RoundingMode.UP);
            sequentialCo2 = co2Amount.subtract(sequentialCo2AmountData)
                    .divide(sequentialCo2AmountData, 2, RoundingMode.UP);
            sequentialPlantTree = plantTree.subtract(sequentialPlantTreeAmountData)
                    .divide(sequentialPlantTreeAmountData, 2, RoundingMode.UP);
        }

        BigDecimal pariPassCon = BigDecimal.ZERO;
        BigDecimal pariPassCost = BigDecimal.ZERO;
        BigDecimal pariPassCoal = BigDecimal.ZERO;
        BigDecimal pariPassCo2 = BigDecimal.ZERO;
        BigDecimal pariPassPlantTree = BigDecimal.ZERO;
        if (pariPassuData.compareTo(BigDecimal.ZERO) != 0) {
            BigDecimal pariPassCoalAmountData = pariPassuData.multiply(coal).setScale(2, RoundingMode.UP);
            BigDecimal pariPassCo2AmountData = pariPassCoalAmountData.multiply(co2Factor)
                    .setScale(2, RoundingMode.UP);
            BigDecimal pariPassPlantTreeAmount = pariPassCo2AmountData.divide(new BigDecimal("18"), 2, RoundingMode.UP);
            pariPassCon = data.subtract(pariPassuData).divide(pariPassuData, 2, RoundingMode.UP);
            pariPassCoal = coalAmount.subtract(pariPassCoalAmountData)
                    .divide(pariPassCoalAmountData, 2, RoundingMode.UP);
            pariPassCo2 = co2Amount.subtract(pariPassCo2AmountData)
                    .divide(pariPassCo2AmountData, 2, RoundingMode.UP);
            pariPassPlantTree = plantTree.subtract(pariPassPlantTreeAmount)
                    .divide(pariPassPlantTreeAmount, 2, RoundingMode.UP);
        }

        Map<String, Object> map = new HashMap<>();
        map.put("consume", data);
        map.put("sequentialCon", sequentialCon);
        map.put("pariPassCon", pariPassCon);
        map.put("sequential", sequentialData);
        map.put("pariPass", pariPassuData);
        map.put("cost", cost);
        map.put("sequentialCost", sequentialCost);
        map.put("pariPassCost", pariPassCost);
        map.put("coalAmount", coalAmount);
        map.put("sequentialCoal", sequentialCoal);
        map.put("pariPassCoal", pariPassCoal);
        map.put("co2Amount", co2Amount);
        map.put("sequentialCo2", sequentialCo2);
        map.put("pariPassCo2", pariPassCo2);
        map.put("plantTree", plantTree);
        map.put("sequentialPlantTree", sequentialPlantTree);
        map.put("pariPassPlantTree", pariPassPlantTree);
        return map;
    }

    private static String formatTsdbTime(LocalDateTime time) {
        return time.format(TSDB_TIME_FORMAT);
    }

    /**
     * 能耗用能趋势：按产品+时间范围查询 TSDB 得 time/value，再按原系统 getListResult 逻辑封装。
     */
    @Override
    public List<Map<String, Object>> queryEnergyTrend(Integer dateType, Integer energyType) {
        if (dateType == null|| energyType == null) {
            return new ArrayList<>();
        }

        List<String> productCodeList = dmpProductMapper.selectProductCodesByEnergyType(
                SecurityUtils.getTenantId(), energyType);
        if (productCodeList == null || productCodeList.isEmpty()) {
            return new ArrayList<>();
        }

        String identifier = resolveRootIdentifierByEnergyType(energyType);
        if (identifier == null) {
            return new ArrayList<>();
        }

        if (dateType == 1 || dateType == 2 || dateType == 3) {
            return buildEnergyTrendResult(productCodeList, identifier, dateType);
        }
        return new ArrayList<>();
    }

    /**
     * 与原系统 queryEnergyTrend 一致：拉取当前期/去年同期 time-value 序列，构建 dateList 后 getListResult 封装。
     */
    private List<Map<String, Object>> buildEnergyTrendResult(List<String> productCodeList, String identifier,
                                                             Integer dateType) {
        LocalDateTime endThisTime = getTrendEndTime(dateType);
        LocalDateTime startThisTime = getStartTime(dateType, endThisTime);
        LocalDateTime endOldTime = endThisTime.minusYears(1);
        LocalDateTime startOldTime = getStartTime(dateType, endOldTime);

        String fieldIdentifier = identifier.toLowerCase();

        List<TrendDataPoint> pointVOList = queryTrendDataPoints(productCodeList, fieldIdentifier,
                startThisTime, endThisTime, dateType);
        Map<Integer, TrendDataPoint> pointVOMap = toTrendPointMap(pointVOList, dateType);

        List<TrendDataPoint> pointOldList = queryTrendDataPoints(productCodeList, fieldIdentifier,
                startOldTime, endOldTime, dateType);
        Map<Integer, TrendDataPoint> pointOldMap = toTrendPointMap(pointOldList, dateType);

        List<String> dateList = buildTrendDateList(dateType, startThisTime, endThisTime);

        return getTrendListResult(dateList, pointVOMap, pointOldMap, dateType);
    }

    /**
     * 按产品列表查询 TSDB，返回合并后的 time/value 序列（同时间点跨产品累加）。
     */
    private List<TrendDataPoint> queryTrendDataPoints(List<String> productCodeList, String fieldIdentifier,
                                                      LocalDateTime startTime, LocalDateTime endTime,
                                                      Integer dateType) {
        Map<String, BigDecimal> merged = new HashMap<>();
        String start = formatTsdbTime(startTime);
        String end = formatTsdbTime(endTime);
        for (String productCode : productCodeList) {
            if (StringUtils.isBlank(productCode)) {
                continue;
            }
            EnergyItemTrendQueryVO queryVO = new EnergyItemTrendQueryVO();
            queryVO.setIdentifier(fieldIdentifier);
            queryVO.setSuperTable("super_" + productCode.trim());
            queryVO.setStartTime(start);
            queryVO.setEndTime(end);
            queryVO.setDateType(dateType);
            EnergyItemTrendResultVO apiResult = remoteTsdbProxyService.sumEnergyItemTrend(queryVO);
            if (apiResult == null || apiResult.getPoints() == null) {
                continue;
            }
            for (EnergyItemTrendPointVO point : apiResult.getPoints()) {
                if (point == null || StringUtils.isBlank(point.getTime())) {
                    continue;
                }
                BigDecimal value = point.getValue() != null ? point.getValue() : BigDecimal.ZERO;
                merged.merge(point.getTime(), value, BigDecimal::add);
            }
        }
        List<TrendDataPoint> result = new ArrayList<>();
        for (Map.Entry<String, BigDecimal> entry : merged.entrySet()) {
            TrendDataPoint dataPoint = new TrendDataPoint();
            dataPoint.setTime(entry.getKey());
            dataPoint.setValue(entry.getValue());
            result.add(dataPoint);
        }
        return result;
    }

    /** 将 time/value 列表转为以时间刻度为 key 的 Map（与原系统 pointVOMap 一致） */
    private Map<Integer, TrendDataPoint> toTrendPointMap(List<TrendDataPoint> points, Integer dateType) {
        Map<Integer, TrendDataPoint> map = new HashMap<>();
        for (TrendDataPoint point : points) {
            LocalDateTime parsed = parseTrendTimestamp(point.getTime());
            if (parsed == null) {
                continue;
            }
            int timeKey = getTrendTimeKey(dateType, parsed);
            map.merge(timeKey, point, (existing, incoming) -> {
                TrendDataPoint merged = new TrendDataPoint();
                merged.setTime(existing.getTime());
                merged.setValue(existing.getValue().add(incoming.getValue()));
                return merged;
            });
        }
        return map;
    }

    /** 构建时间轴 dateList（与原系统一致） */
    private List<String> buildTrendDateList(Integer dateType, LocalDateTime startTime, LocalDateTime endTime) {
        List<String> dateList = new ArrayList<>();
        LocalDateTime time = startTime;
        while (true) {
            if (dateType == 1) {
                if (time.getHour() > endTime.getHour()) {
                    break;
                }
            } else if (dateType == 2) {
                if (time.getDayOfMonth() > endTime.getDayOfMonth()) {
                    break;
                }
            } else if (dateType == 3) {
                if (time.getMonthValue() > endTime.getMonthValue()) {
                    break;
                }
            } else {
                break;
            }
            dateList.add(formatTsdbTime(time));
            if (dateType == 1) {
                time = time.plusHours(1);
            } else if (dateType == 2) {
                time = time.plusDays(1);
            } else {
                time = time.plusMonths(1);
            }
            if (time.isAfter(endTime)) {
                break;
            }
        }
        return dateList;
    }

    /**
     * 与原系统 getListResult 一致：遍历 dateList，匹配 thisValue/oldValue/time/unit。
     */
    private List<Map<String, Object>> getTrendListResult(List<String> dateList,
                                                         Map<Integer, TrendDataPoint> pointVOMap,
                                                         Map<Integer, TrendDataPoint> pointOldMap,
                                                         Integer dateType) {
        List<Map<String, Object>> list = new ArrayList<>();

        for (String date : dateList) {
            Map<String, Object> map = new HashMap<>();
            int time = parseTrendTimeKey(dateType, date);

            TrendDataPoint pointVO = pointVOMap.get(time);
            if (pointVO == null) {
                map.put("thisValue", "-");
            } else {
                map.put("thisValue", pointVO.getValue());
            }

            TrendDataPoint pointOldVO = pointOldMap.get(time);
            if (pointOldVO == null) {
                map.put("oldValue", "-");
            } else {
                map.put("oldValue", pointOldVO.getValue());
            }

            map.put("time", time);
            list.add(map);
        }
        return list;
    }

    /**
     * 按能源类型取根分项 identifier（parent_code=0）。
     */
    private String resolveRootIdentifierByEnergyType(Integer energyType) {
        if (energyType == null) {
            return null;
        }
        EmsEnergyItemCode rootItem = emsEnergyItemCodeMapper.selectOne(
                Wrappers.lambdaQuery(EmsEnergyItemCode.class)
                        .eq(EmsEnergyItemCode::getParentCode, "0")
                        .eq(EmsEnergyItemCode::getEnergyType, energyType)
                        .last("LIMIT 1"));
        if (rootItem == null || StringUtils.isBlank(rootItem.getIdentifier())) {
            return null;
        }
        return rootItem.getIdentifier().trim().toLowerCase();
    }

    private EmsEnergyItemCode resolveEnergyItemCode(String identifier) {
        EmsEnergyItemCode itemCode = emsEnergyItemCodeMapper.selectOne(
                Wrappers.lambdaQuery(EmsEnergyItemCode.class).eq(EmsEnergyItemCode::getCode, identifier));
        if (itemCode == null) {
            itemCode = emsEnergyItemCodeMapper.selectOne(
                    Wrappers.lambdaQuery(EmsEnergyItemCode.class).eq(EmsEnergyItemCode::getIdentifier, identifier));
        }
        return itemCode;
    }

    /** 趋势时序点（对应原系统 DataPointVO 的 time/value） */
    private static class TrendDataPoint {
        private String time;
        private BigDecimal value;

        public String getTime() {
            return time;
        }

        public void setTime(String time) {
            this.time = time;
        }

        public BigDecimal getValue() {
            return value;
        }

        public void setValue(BigDecimal value) {
            this.value = value;
        }
    }

    private int parseTrendTimeKey(Integer dateType, String date) {
        LocalDateTime time = parseTrendTimestamp(date);
        return time != null ? getTrendTimeKey(dateType, time) : 0;
    }

    private LocalDateTime parseTrendTimestamp(String ts) {
        if (StringUtils.isBlank(ts)) {
            return null;
        }
        String normalized = ts.trim();
        DateTimeFormatter[] formatters = {
                TSDB_FLEXIBLE_TIME_FORMAT,
                TSDB_TIME_FORMAT,
                DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"),
                DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss")
        };
        for (DateTimeFormatter formatter : formatters) {
            try {
                return LocalDateTime.parse(normalized, formatter);
            } catch (Exception ignored) {
                // try next formatter
            }
        }
        int dotIndex = normalized.indexOf('.');
        if (dotIndex > 0) {
            try {
                return LocalDateTime.parse(normalized.substring(0, dotIndex),
                        DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
            } catch (Exception ignored) {
                // fall through
            }
        }
        return null;
    }

    private LocalDateTime getTrendEndTime(Integer dateType) {
        if (dateType == 1) {
            return LocalDateTime.of(LocalDate.now(), LocalTime.MAX);
        }
        if (dateType == 2) {
            return LocalDateTime.of(LocalDate.now().with(TemporalAdjusters.lastDayOfMonth()), LocalTime.MAX);
        }
        if (dateType == 3) {
            return LocalDateTime.of(LocalDate.now().with(TemporalAdjusters.lastDayOfYear()), LocalTime.MAX);
        }
        return null;
    }

    private int getTrendTimeKey(Integer dateType, LocalDateTime time) {
        if (dateType == 1) {
            return time.getHour();
        }
        if (dateType == 2) {
            return time.getDayOfMonth();
        }
        return time.getMonthValue();
    }

    /**
     * 计算指定时间类型的开始时间
     * dateType: 1-日（当天 00:00:00）、2-月（当月第一天 00:00:00）、3-年（当年第一天 00:00:00）
     */
    private LocalDateTime getStartTime(Integer dateType, LocalDateTime endTime) {
        if (dateType == null || endTime == null) {
            return null;
        }
        if (dateType == 1) {
            return LocalDateTime.of(endTime.toLocalDate(), LocalTime.MIN);
        } else if (dateType == 2) {
            LocalDate firstDayOfMonth = endTime.toLocalDate().withDayOfMonth(1);
            return LocalDateTime.of(firstDayOfMonth, LocalTime.MIN);
        } else if (dateType == 3) {
            LocalDate firstDayOfYear = endTime.toLocalDate().with(TemporalAdjusters.firstDayOfYear());
            return LocalDateTime.of(firstDayOfYear, LocalTime.MIN);
        }
        return LocalDateTime.of(endTime.toLocalDate(), LocalTime.MIN);
    }

    /**
     * 建筑能耗分析：从 base_space 取 type=3 建筑，经 base_space_gateway → dmp_device 定位设备，
     * 再按能源类型关联产品调用 TSDB 分项汇总。
     */
    @Override
    public List<Map<String, Object>> queryBuildingRanking(Integer dateType, Integer energyType) {
        if (dateType == null || energyType == null) {
            return new ArrayList<>();
        }

        BaseSpace project = baseSpaceService.getOne(
                new LambdaQueryWrapper<BaseSpace>()
                        .eq(BaseSpace::getParentId, 0)
                        .eq(BaseSpace::getTenantId, SecurityUtils.getTenantId()));
        if (project == null || project.getId() == null) {
            return new ArrayList<>();
        }

        List<BaseSpace> spaceList = baseSpaceService.list(
                new LambdaQueryWrapper<BaseSpace>()
                        .eq(BaseSpace::getParentId, project.getId())
                        .eq(BaseSpace::getType, 3));
        if (spaceList == null || spaceList.isEmpty()) {
            return new ArrayList<>();
        }

        List<String> productCodeList = dmpProductMapper.selectProductCodesByEnergyType(
                SecurityUtils.getTenantId(), energyType);
        if (productCodeList == null || productCodeList.isEmpty()) {
            return new ArrayList<>();
        }
        Set<String> productCodeSet = productCodeList.stream()
                .filter(StringUtils::isNotBlank)
                .map(String::trim)
                .collect(Collectors.toSet());

        Map<Long, List<String>> spaceDeviceUuidMap = loadSpaceDeviceUuidMap(spaceList, productCodeSet);

        LocalDateTime endTime = LocalDateTime.now();
        LocalDateTime startTime = getStartTime(dateType, endTime);
        if (startTime == null) {
            return new ArrayList<>();
        }
        String identifier = resolveRootIdentifierByEnergyType(energyType);
        if (identifier == null) {
            return new ArrayList<>();
        }

        Map<Long, BigDecimal> dataMap = new LinkedHashMap<>();
        for (BaseSpace space : spaceList) {
            if (space == null || space.getId() == null) {
                continue;
            }
            List<String> deviceUuids = spaceDeviceUuidMap.get(space.getId());
            if (deviceUuids == null || deviceUuids.isEmpty()) {
                continue;
            }
            BigDecimal value = sumEnergyByProductCodes(productCodeList, identifier,
                    startTime, endTime, deviceUuids);
            if (value != null) {
                dataMap.put(space.getId(), value);
            }
        }

        Map<Long, BaseSpace> spaceMap = spaceList.stream()
                .filter(space -> space.getId() != null)
                .collect(Collectors.toMap(BaseSpace::getId, space -> space, (oldVal, newVal) -> oldVal));

        return dataMap.entrySet().stream()
                .sorted(Map.Entry.comparingByValue())
                .map(entry -> {
                    Map<String, Object> map = new HashMap<>();
                    BaseSpace space = spaceMap.get(entry.getKey());
                    map.put("name", space != null ? space.getName() : null);
                    map.put("value", entry.getValue());
                    return map;
                })
                .collect(Collectors.toList());
    }

    /**
     * 按空间批量解析网关及子设备 UUID（base_space_gateway → dmp_device）。
     */
    private Map<Long, List<String>> loadSpaceDeviceUuidMap(List<BaseSpace> spaceList, Set<String> productCodeSet) {
        List<Long> spaceIds = spaceList.stream()
                .map(BaseSpace::getId)
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
        if (spaceIds.isEmpty()) {
            return Collections.emptyMap();
        }

        List<BaseSpaceGateway> links = baseSpaceGatewayMapper.selectList(
                Wrappers.lambdaQuery(BaseSpaceGateway.class).in(BaseSpaceGateway::getSpaceId, spaceIds));
        if (links == null || links.isEmpty()) {
            return Collections.emptyMap();
        }

        Map<String, Long> gatewayToSpace = new HashMap<>();
        for (BaseSpaceGateway link : links) {
            if (link.getSpaceId() == null || StringUtils.isBlank(link.getGatewayUuid())) {
                continue;
            }
            gatewayToSpace.putIfAbsent(link.getGatewayUuid().trim(), link.getSpaceId());
        }
        if (gatewayToSpace.isEmpty()) {
            return Collections.emptyMap();
        }

        Map<Long, List<String>> spaceDeviceUuidMap = new HashMap<>();
        for (String gatewayUuid : gatewayToSpace.keySet()) {
            Long spaceId = gatewayToSpace.get(gatewayUuid);
            List<DmpDevice> devices = dmpDeviceService.getDevicesByGatewayUuid(gatewayUuid);
            if (devices == null || devices.isEmpty()) {
                continue;
            }
            for (DmpDevice device : devices) {
                if (device == null || StringUtils.isBlank(device.getDeviceUuid())
                        || StringUtils.isBlank(device.getProductCode())) {
                    continue;
                }
                if (!productCodeSet.contains(device.getProductCode().trim())) {
                    continue;
                }
                spaceDeviceUuidMap.computeIfAbsent(spaceId, key -> new ArrayList<>())
                        .add(device.getDeviceUuid().trim());
            }
        }

        spaceDeviceUuidMap.replaceAll((spaceId, uuids) -> uuids.stream().distinct().collect(Collectors.toList()));
        return spaceDeviceUuidMap;
    }

    /**
     * 单位综合能耗、综合累计能耗、分类能耗占比（总览顶部）
     * - 能源类型来自 EmsModelService.getEnergyTypeList（电/水/气）
     * - 折算系数来自 leo.ems_project_conversion_factor（优先取项目专属，其次 project_id=0 的公共配置）
     */
    @Override
    public Map<String, Object> queryOverviewTop(Integer dateType, Long projectId) {
        Map<String, Object> resultMap = new HashMap<>();
        if (dateType == null) {
            return resultMap;
        }

        // 时间范围，目前仅用于保持与原接口结构一致
        LocalDateTime endTime = LocalDateTime.now();
        LocalDateTime startTime = getStartTime(dateType, endTime);
        if (startTime == null) {
            return resultMap;
        }

        // 项目信息（含面积、根空间 ID）
        Long resolvedProjectId = resolveProjectId(projectId);
        EmsProjectResponse project = getProject(Math.toIntExact(resolvedProjectId));
        if (project == null || project.getSpaceId() == null) {
            return resultMap;
        }

        // 1. 能源类型列表（电/水/气）
        List<EmsEnergyTypeVO> energyTypes = emsModelService.getEnergyTypeList();
        if (energyTypes == null || energyTypes.isEmpty()) {
            return resultMap;
        }

        List<Integer> typeIds = energyTypes.stream()
                .filter(t -> t.getId() != null)
                .map(t -> t.getId().intValue())
                .collect(Collectors.toList());
        if (typeIds.isEmpty()) {
            return resultMap;
        }

        // 2. 查询折算系数（项目专属 + 公共）
        List<EmsProjectConversionFactor> factorList = emsProjectConversionFactorMapper.selectList(
                new LambdaQueryWrapper<EmsProjectConversionFactor>()
                        .in(EmsProjectConversionFactor::getEnergyType, typeIds)
                        .in(EmsProjectConversionFactor::getProjectId, resolvedProjectId, 0L)
        );
        if (factorList == null || factorList.isEmpty()) {
            return resultMap;
        }

        Map<Integer, BigDecimal> map = new HashMap();

        Map<Integer, EmsEnergyItemCode> rootItemByEnergyType = emsEnergyItemCodeMapper.selectList(
                        Wrappers.<EmsEnergyItemCode>lambdaQuery()
                                .eq(EmsEnergyItemCode::getParentCode, "0")
                ).stream()
                .filter(item -> item.getEnergyType() != null)
                .collect(Collectors.toMap(EmsEnergyItemCode::getEnergyType, item -> item, (a, b) -> a));

        for(Integer energyType : typeIds) {
            if (energyType != 5) {
                EmsEnergyItemCode itemCode = rootItemByEnergyType.get(energyType);
                if (itemCode == null) {
                    return null;
                }


                List<String> productCodeList = dmpProductMapper.selectProductCodesByEnergyType(
                        SecurityUtils.getTenantId(), energyType);
                if (productCodeList == null || productCodeList.isEmpty()) {
                    return resultMap;
                }

                BigDecimal data = sumEnergyByProductCodes(productCodeList, itemCode.getIdentifier(), startTime, endTime);
                map.put(energyType, data);
            }
        }

        Map<Integer, List<EmsProjectConversionFactor>> listMap = (Map)factorList.stream().collect(Collectors.groupingBy(EmsProjectConversionFactor::getEnergyType));
        BigDecimal coalTotal = BigDecimal.ZERO;
        BigDecimal co2Total = BigDecimal.ZERO;
        BigDecimal totalCon = (BigDecimal)map.values().stream().reduce(BigDecimal.ZERO, BigDecimal::add);
        List<Map<String, Object>> ratioList = new ArrayList();

        for(Integer energyType : typeIds) {
            if (energyType != 5) {
                BigDecimal con = (BigDecimal)map.get(energyType);
                List<EmsProjectConversionFactor> conversionFactorList = (List)listMap.get(energyType);
                if (conversionFactorList != null && !conversionFactorList.isEmpty()) {
                    Map<String, BigDecimal> factorMap = (Map)conversionFactorList.stream().collect(Collectors.toMap(EmsProjectConversionFactor::getName, EmsProjectConversionFactor::getValue));
                    BigDecimal coal = (BigDecimal)factorMap.get("coal");
                    BigDecimal coalAmount = coal.multiply(con).setScale(2, RoundingMode.UP);
                    BigDecimal co2Amount = coalAmount.multiply(new BigDecimal("2.4589")).setScale(2, RoundingMode.UP);
                    coalTotal = coalTotal.add(coalAmount);
                    co2Total = co2Total.add(co2Amount);

                    EmsEnergyItemCode itemCode = rootItemByEnergyType.get(energyType);
                    if (itemCode == null) {
                        return null;
                    }
                    Map<String, Object> ratioMap = new HashMap();
                    ratioMap.put("name", itemCode.getName());
                    ratioMap.put("consume", con.multiply(coal).setScale(2, RoundingMode.UP));
                    ratioMap.put("totalConsume", totalCon.multiply(coal).setScale(2, RoundingMode.UP));
                    ratioList.add(ratioMap);
                }
            }
        }

        BigDecimal unitCoal = BigDecimal.ZERO;
        if (project.getArea() != null && project.getArea().compareTo(BigDecimal.ZERO) != 0) {
            unitCoal = coalTotal.divide(project.getArea(), 2, RoundingMode.UP);
        }

        resultMap.put("unitCoal", unitCoal);
        resultMap.put("ratioList", ratioList);
        resultMap.put("coalTotal", coalTotal);
        resultMap.put("co2Total", co2Total);
        return resultMap;


    }

    /**
     * 分项能耗占比
     * - 根分项按 energyType 从 ems_energy_item_code 解析；
     * - 冷/热（3/4）不展开下级，仅返回根分项；
     * - 其余类型按 parent_code 展开子分项；
     * - 各分项设备范围来自 ems_device_item_code，再调用 TSDB 汇总。
     */
    @Override
    public List<Map<String, Object>> queryItemRatio(Integer dateType, Integer energyType) {
        List<Map<String, Object>> resultList = new ArrayList<>();
        if (dateType == null || energyType == null) {
            return resultList;
        }

        EmsEnergyItemCode parent = emsEnergyItemCodeMapper.selectOne(
                Wrappers.lambdaQuery(EmsEnergyItemCode.class)
                        .eq(EmsEnergyItemCode::getParentCode, "0")
                        .eq(EmsEnergyItemCode::getEnergyType, energyType)
                        .last("LIMIT 1"));
        if (parent == null || StringUtils.isBlank(parent.getIdentifier())) {
            return resultList;
        }

        List<String> productCodeList = dmpProductMapper.selectProductCodesByEnergyType(
                SecurityUtils.getTenantId(), energyType);
        if (productCodeList == null || productCodeList.isEmpty()) {
            return resultList;
        }

        LocalDateTime endTime = LocalDateTime.now();
        LocalDateTime startTime = getStartTime(dateType, endTime);
        if (startTime == null) {
            return resultList;
        }

        String parentIdentifier = parent.getIdentifier().trim().toLowerCase();

        if (energyType == 3 || energyType == 4) {

            BigDecimal total = sumEnergyForItemCode(productCodeList, parentIdentifier, parent.getCode(), startTime, endTime);
            Map<String, Object> map = new HashMap<>();
            map.put("name", parent.getName());
            map.put("consume", total);
            map.put("total", total);
            resultList.add(map);
            return resultList;
        }


        BigDecimal total = sumEnergyForItemCode(productCodeList, parentIdentifier, null, startTime, endTime);
        List<EmsEnergyItemCode> children = emsEnergyItemCodeMapper.selectList(
                Wrappers.lambdaQuery(EmsEnergyItemCode.class)
                        .eq(EmsEnergyItemCode::getParentCode, parent.getCode()));
        if (children == null || children.isEmpty()) {
            Map<String, Object> map = new HashMap<>();
            map.put("name", parent.getName());
            map.put("consume", total);
            map.put("total", total);
            resultList.add(map);
            return resultList;
        }

        for (EmsEnergyItemCode child : children) {
            if (child == null || StringUtils.isBlank(child.getIdentifier())) {
                continue;
            }
            String childIdentifier = child.getIdentifier().trim().toLowerCase();
            BigDecimal consume = sumEnergyForItemCode(productCodeList, childIdentifier, child.getCode(), startTime, endTime);
            Map<String, Object> map = new HashMap<>();
            map.put("name", child.getName());
            map.put("consume", consume);
            map.put("total", total);
            resultList.add(map);
        }

        return resultList;
    }

    private BigDecimal sumEnergyForItemCode(List<String> productCodeList, String fieldIdentifier, String itemCode, LocalDateTime startTime, LocalDateTime endTime) {
        if(itemCode == null || itemCode.isEmpty()) {
            return sumEnergyByProductCodes(productCodeList, fieldIdentifier, startTime, endTime, null);
        }
        List<String> deviceUuids = resolveDeviceUuidsByItemCode(itemCode);
        if (deviceUuids.isEmpty()) {
            return BigDecimal.ZERO;
        }
        return sumEnergyByProductCodes(productCodeList, fieldIdentifier, startTime, endTime, deviceUuids);
    }

    private List<String> resolveDeviceUuidsByItemCode(String itemCode) {
        if (StringUtils.isBlank(itemCode)) {
            return Collections.emptyList();
        }
        List<String> deviceUuids = emsDeviceItemCodeMapper.selectDeviceUuidsByItemCode(
                SecurityUtils.getTenantId(), itemCode.trim());
        if (deviceUuids == null || deviceUuids.isEmpty()) {
            return Collections.emptyList();
        }
        return deviceUuids.stream()
                .filter(StringUtils::isNotBlank)
                .map(String::trim)
                .distinct()
                .collect(Collectors.toList());
    }

    /** 根据设备系统编码返回名称（与 dmp_product.deviceType 含义保持一致） */
    private String deviceSystemName(Integer deviceSystem) {
        if (deviceSystem == null) return "";
        switch (deviceSystem) {
            case 501:
                return "监控系统";
            case 502:
                return "门禁系统";
            case 503:
                return "梯控系统";
            case 504:
                return "机房系统";
            case 509:
                return "环境系统";
            case 510:
                return "照明系统";
            default:
                return "";
        }
    }
}
