package com.hdkj.lt.si.service.recon.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

import com.hdkj.lt.base.exception.ExceptionCast;
import com.hdkj.lt.core.bizms.modle.po.Jxz;
import com.hdkj.lt.core.rest.config.HttpClientConfig;
import com.hdkj.lt.core.sys.dao.JxzMapper;
import com.hdkj.lt.modle.dto.recon.*;
import com.hdkj.lt.modle.po.*;
import com.hdkj.lt.modle.vo.recon.ReconSaveResultVO;
import com.hdkj.lt.si.config.EstimationAlgorithmProp;
import com.hdkj.lt.si.dao.*;
import com.hdkj.lt.si.service.recon.ReconResultService;
import com.hdkj.lt.utils.security.StrConvertUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.Charsets;
import org.apache.commons.collections.MapUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StreamUtils;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;

/**
 * 重构结果入库服务实现.
 * 单事务写入主表 + 7 子表, 任一失败全部回滚.
 * 类型转换: String(ISO8601)->LocalDateTime, JsonNode->JSON串, Boolean->tinyint(由MP处理), 数值->BigDecimal.
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class ReconResultServiceImpl implements ReconResultService {

    private final FhzgReconResultMapper resultMapper;
    private final FhzgReconSwitchOpMapper switchOpMapper;
    private final FhzgReconSwitchSeqMapper switchSeqMapper;
    private final FhzgReconFeederResultMapper feederResultMapper;
    private final FhzgReconVolResultMapper volResultMapper;
    private final FhzgReconVolDetailMapper volDetailMapper;
    private final FhzgReconSwitchResultDetailMapper switchResultDetailMapper;
    private final FhzgReconMetricsMapper metricsMapper;
    private final EstimationAlgorithmProp algorithmProp;
    private final JxzMapper jxzMapper;

    private static final ObjectMapper OM = new ObjectMapper()
            .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    private static final DateTimeFormatter ISO_OFFSET = DateTimeFormatter.ISO_OFFSET_DATE_TIME;

    @Value("${remote.recon.request-uri}")
    private String requestUri;

    @Value("${remote.recon.recon-uri}")
    private String reconUri;

    @Resource(name = HttpClientConfig.CONN_POOL_REST_TEMPLATE)
    private RestTemplate restTemplate;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ReconSaveResultVO saveReconResult(String eventId, Map<String, Object> params) {
        // 1. 构造请求参数
        Map<String, Object> req = buildReconRequestParams(params);
        String requestUrl = requestUri.concat(reconUri);
        HttpHeaders headers = new HttpHeaders();
        headers.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
        headers.set(HttpHeaders.ACCEPT_CHARSET, Charsets.UTF_8.toString());
        HttpEntity<Object> httpEntity = new HttpEntity<>(req, headers);
        String str;
        try {
            ResponseEntity<Object> restRespEntity = restTemplate.postForEntity(requestUrl, httpEntity, Object.class);
            if (!restRespEntity.getStatusCode().is2xxSuccessful() || Objects.isNull(restRespEntity.getBody())) {
                String errorMsg = String.format("调用重构方案接口失败, requestUrl: %s, response: %s",
                        requestUrl, JSON.toJSONString(restRespEntity));
                log.error(errorMsg);
                ExceptionCast.cast(errorMsg);
            }
            Object body = restRespEntity.getBody();
            str = JSON.toJSONString(body);
            log.info("调用重构方案接口成功, requestUrl: {}, result: {}", requestUrl, str);
        } catch (Exception e) {
            throw new RuntimeException("调用重构方案接口异常", e);
        }

        // 2. 提取 raw_result 节点JSON字符串
        String rawResultJson;
        try {
            JsonNode root = OM.readTree(str);
            JsonNode rawNode = root.get("raw_result");
            if (rawNode == null || rawNode.isNull()) {
                throw new RuntimeException("算法响应缺少 raw_result 节点");
            }
            rawResultJson = OM.writeValueAsString(rawNode);
        } catch (IOException e) {
            throw new RuntimeException("解析算法响应 raw_result 失败", e);
        }

        // 3. 反序列化并入库
        return parseAndSaveReconResult(rawResultJson, eventId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ReconSaveResultVO parseAndSaveReconResult(String rawResultJson, String eventId) {
        // 1. 反序列化 raw_result JSON -> RawResultDTO
        RawResultDTO dto;
        try {
            dto = OM.readValue(rawResultJson, RawResultDTO.class);
        } catch (IOException e) {
            throw new RuntimeException("反序列化 raw_result JSON 失败", e);
        }

        Map<String, Integer> counts = new LinkedHashMap<>();
        //获取方案列表
        List<ReconfigurationSchemeDTO> schemes = extractSchemes(dto);
        int i = 1;
        for (ReconfigurationSchemeDTO scheme : schemes) {
            // 1. 主表
            FhzgReconResult main = buildMain(dto,scheme,i++);
            main.setEventId(eventId);
            main.setRawResult(rawResultJson);
            resultMapper.insert(main);
            Long resultId = main.getId();
            counts.merge("result", 1, Integer::sum);

            // 2. 开关操作(简化版)
            if (scheme.getSwitchOp() != null) {
                counts.merge("switch_op", insertSwitchOp(scheme.getSwitchOp(), resultId), Integer::sum);
            }

            // 3. 开关操作序列(详细)
            if (scheme.getSwitchSeq() != null) {
                counts.merge("switch_seq", insertSwitchSeq(scheme.getSwitchSeq(), resultId), Integer::sum);
            }

            // 4. 馈线结果
            if (scheme.getFeederResult() != null) {
                counts.merge("feeder_result", insertFeederResult(scheme.getFeederResult(), resultId), Integer::sum);
            }

            // 5. 电压结果(汇总+详情)
            if (scheme.getVolResult() != null) {
                if (scheme.getVolResult().getSummary() != null) {
                    volResultMapper.insert(buildVolResult(scheme.getVolResult().getSummary(), resultId));
                    counts.merge("vol_result", 1, Integer::sum);
                }
                if (scheme.getVolResult().getDetails() != null) {
                    counts.merge("vol_detail", insertVolDetail(scheme.getVolResult().getDetails(), resultId), Integer::sum);
                }
            }

            // 6. 开关状态详情(summary 折叠进主表)
            if (scheme.getSwitchResult() != null && scheme.getSwitchResult().getDetails() != null) {
                counts.merge("switch_result_detail", insertSwitchResultDetail(scheme.getSwitchResult().getDetails(), resultId), Integer::sum);
            }

            // 7. 运行指标
            if (scheme.getMetrics() != null) {
                metricsMapper.insert(buildMetrics(scheme.getMetrics(), resultId));
                counts.merge("metrics", 1, Integer::sum);
            }
        }

        log.info("重构方案counts:{}", JSON.toJSONString(counts));
        ReconSaveResultVO vo = new ReconSaveResultVO();
        vo.setSuccess(true);
//        vo.setMainId(resultId);
        vo.setEventId(eventId);
        vo.setCounts(counts);
        return vo;
    }

    /**
     * 构建请求参数
     */
    private Map<String, Object> buildReconRequestParams(Map<String, Object> params){
        Map<String, Object> root = new HashMap<>();
        if(Objects.nonNull(params)) {
            String feederId = MapUtils.getString(params, "feeder_id");
            String pointTime = MapUtils.getString(params, "point_time");
            List<Jxz> jxzList = getFeederGroup(feederId);
            List<String> feederIds = jxzList.stream().map(Jxz::getFeederId).distinct().collect(Collectors.toList());
            if (!CollectionUtils.isEmpty(jxzList)) {
                Jxz jxz = jxzList.get(0);
                // county
                Map<String, Object> county = new HashMap<>();
                county.put("county_id", jxz.getMaintOrg());
                county.put("county_sid", jxz.getMaintOrgName());
                county.put("county_name", jxz.getMaintOrgName());
                root.put("county", county);

                // snapshot
                Map<String, Object> snapshot = new HashMap<>();
                snapshot.put("snapshot_id", pointTime);
                snapshot.put("alarm_state_estimation_time", pointTime);
                snapshot.put("point_time", pointTime);
                root.put("snapshot", snapshot);

                // problem
                Map<String, Object> problem = new HashMap<>();
                problem.put("problem_feeder_ids", Collections.singletonList(feederId));
                problem.put("trigger_type", "load_rate");
                problem.put("trigger_reason", "feeder_load_rate_over_limit");
                problem.put("trigger_source", "customer_backend_alarm");
                root.put("problem", problem);

                // feeder_group
                Map<String, Object> feederGroup = new HashMap<>();
                feederGroup.put("group_id", jxz.getJxzId());
                feederGroup.put("group_sid", jxz.getJxzId());
                feederGroup.put("group_name", jxz.getJxzName());
                feederGroup.put("feeder_ids", feederIds);
                root.put("feeder_group", feederGroup);
                // algorithm_config【外部可传入覆盖】
                root.put("algorithm_config", algorithmProp.toAlgorithmConfigMap());
            }
        }
        return root;
    }
    private List<Jxz> getFeederGroup(String feederId){
         return jxzMapper.queryJxzXl(feederId);
    }
    // ==================== 构建主表 ====================

    /** [FHZG-RECON-MULTI] 取方案列表; 容器或 list 为空时返回空表(不报错, 入 0 条)。 */
    private List<ReconfigurationSchemeDTO> extractSchemes(RawResultDTO dto) {
        if (dto.getReconfigurationSchemes() == null || dto.getReconfigurationSchemes().getList() == null) {
            return Collections.emptyList();
        }
        return dto.getReconfigurationSchemes().getList();
    }

    private FhzgReconResult buildMain(RawResultDTO dto, ReconfigurationSchemeDTO scheme,int i) {
        FhzgReconResult m = new FhzgReconResult();
        // 共享标量(每个方案主记录重复)
        m.setId(IdWorker.getId());
        m.setGroupId(dto.getGroupId());
        m.setGroupSid(dto.getGroupSid());
        m.setGroupName(dto.getGroupName());
        m.setStAreaId(dto.getStAreaId());
        m.setStatus(dto.getStatus());
        m.setAlgo(dto.getAlgo());
        m.setComputeResult(dto.getComputeResult());
        m.setDetalDesc(dto.getDetalDesc());

        // [FHZG-RECON-MULTI] 方案标识 + objective
        m.setRank(scheme.getRank());
        m.setSchemeId(scheme.getSchemeId());
        m.setSchemeName(String.format("方案%s", StrConvertUtils.toChineseStr(i)));
        m.setObjective(toJson(scheme.getObjective()));

        // finalCheck 折叠(per-scheme)
        if (scheme.getFinalCheck() != null) {
            m.setFinalCheckIsSafe(scheme.getFinalCheck().getSafe());
            m.setFinalCheckDetails(scheme.getFinalCheck().getDetails());
        }
        // switchResult.summary 折叠(per-scheme)
        if (scheme.getSwitchResult() != null) {
            m.setSwitchResultSummary(scheme.getSwitchResult().getSummary());
        }
        m.setSnapshotTime(LocalDateTime.now());
        return m;
    }

    // ==================== 开关操作(简化版) ====================

    private int insertSwitchOp(SwitchOpDTO dto, Long resultId) {
        if (dto.getListSwitchOp() == null) return 0;
        int i = 1;
        for (BreakerOpDTO b : dto.getListSwitchOp()) {
            FhzgReconSwitchOp e = new FhzgReconSwitchOp();
            e.setResultId(resultId);
            e.setOpNo(i++);
            e.setBreakerId(b.getBreakerId());
            e.setName(b.getName());
            e.setPsrType(b.getPsrType());
            e.setAutoType(b.getAutoType());
            e.setOperationType(b.getOperationType());
            e.setTimestamp(parseTime(b.getTimestamp()));
            switchOpMapper.insert(e);
        }
        return i;
    }

    // ==================== 开关操作序列(详细, 扁平) ====================

    private int insertSwitchSeq(SwitchSeqDTO dto, Long resultId) {
        if (dto.getListSwitchOp() == null) return 0;
        int i = 1;
        for (SwitchSeqDTO.SwitchSeqItemDTO it : dto.getListSwitchOp()) {
            FhzgReconSwitchSeq e = new FhzgReconSwitchSeq();
            e.setResultId(resultId);
            e.setSeqNo(i++);
            fillBreaker(e, it.getCloseBreakerOp(), true);
            fillBreaker(e, it.getOpenBreakerOp(), false);
            HotCheckDTO h = it.getHotCheck();
            if (h != null) {
                e.setHotCheckIsSafe(h.getSafe());
                e.setHotCheckReason(h.getReason());
            }
            fillFeeder(e, it.getFeederIn(), true);
            fillFeeder(e, it.getFeederOut(), false);
            e.setDeltaLoss(it.getDeltaLoss());
            e.setDeltaI(it.getDeltaI());
            switchSeqMapper.insert(e);
        }
        return i;
    }

    private void fillBreaker(FhzgReconSwitchSeq e, BreakerOpDTO b, boolean close) {
        if (b == null) return;
        if (close) {
            e.setCloseBreakerId(b.getBreakerId());
            e.setCloseName(b.getName());
            e.setClosePsrType(b.getPsrType());
            e.setCloseAutoType(b.getAutoType());
            e.setCloseOperationType(b.getOperationType());
            e.setCloseTimestamp(parseTime(b.getTimestamp()));
        } else {
            e.setOpenBreakerId(b.getBreakerId());
            e.setOpenName(b.getName());
            e.setOpenPsrType(b.getPsrType());
            e.setOpenAutoType(b.getAutoType());
            e.setOpenOperationType(b.getOperationType());
            e.setOpenTimestamp(parseTime(b.getTimestamp()));
        }
    }

    private void fillFeeder(FhzgReconSwitchSeq e, FeederFlowDTO f, boolean in) {
        if (f == null) return;
        if (in) {
            e.setFeederInPsrId(f.getPsrId());
            e.setFeederInPsrType(f.getPsrType());
            e.setFeederInName(f.getName());
            e.setFeederInRateCurrent(f.getRateCurrent());
            e.setFeederInBeforeI(f.getBeforeI());
            e.setFeederInAfterI(f.getAfterI());
            e.setFeederInBeforeDayI(f.getBeforeDayI());
            e.setFeederInAfterDayI(f.getAfterDayI());
            e.setFeederInLineLossBefore(f.getLineLossBefore());
            e.setFeederInLineLossAfter(f.getLineLossAfter());
        } else {
            e.setFeederOutPsrId(f.getPsrId());
            e.setFeederOutPsrType(f.getPsrType());
            e.setFeederOutName(f.getName());
            e.setFeederOutRateCurrent(f.getRateCurrent());
            e.setFeederOutBeforeI(f.getBeforeI());
            e.setFeederOutAfterI(f.getAfterI());
            e.setFeederOutBeforeDayI(f.getBeforeDayI());
            e.setFeederOutAfterDayI(f.getAfterDayI());
            e.setFeederOutLineLossBefore(f.getLineLossBefore());
            e.setFeederOutLineLossAfter(f.getLineLossAfter());
        }
    }

    // ==================== 馈线结果 ====================

    private int insertFeederResult(FeederResultDTO dto, Long resultId) {
        if (dto.getListFeeder() == null) return 0;
        int i = 1;
        for (FeederFlowDTO f : dto.getListFeeder()) {
            FhzgReconFeederResult e = new FhzgReconFeederResult();
            e.setResultId(resultId);
            e.setFeederNo(i++);
            e.setPsrId(f.getPsrId());
            e.setPsrType(f.getPsrType());
            e.setName(f.getName());
            e.setRateCurrent(f.getRateCurrent());
            e.setBeforeI(f.getBeforeI());
            e.setAfterI(f.getAfterI());
            e.setBeforeDayI(f.getBeforeDayI());
            e.setAfterDayI(f.getAfterDayI());
            e.setLineLossBefore(f.getLineLossBefore());
            e.setLineLossAfter(f.getLineLossAfter());
            feederResultMapper.insert(e);
        }
        return i;
    }

    // ==================== 电压结果-汇总 ====================

    private FhzgReconVolResult buildVolResult(VolResultDTO.VolSummaryDTO s, Long resultId) {
        FhzgReconVolResult e = new FhzgReconVolResult();
        e.setResultId(resultId);
        e.setCommentText(s.getComment());
        e.setVlimitMax(s.getVlimitMax());
        e.setVlimitMin(s.getVlimitMin());
        e.setLoadCount(s.getLoadCount());
        e.setLoadCountMvtran(s.getLoadCountMvtran());
        e.setLoadCountMvcustomer(s.getLoadCountMvcustomer());
        e.setLoadCountMvpv(s.getLoadCountMvpv());
        fillVolExtremes(e, s.getBefore(), true);
        fillVolExtremes(e, s.getAfter(), false);
        return e;
    }

    private void fillVolExtremes(FhzgReconVolResult e, VolExtremesDTO x, boolean before) {
        if (x == null) return;
        if (before) {
            e.setBeforeMaxVol(x.getMaxVol());
            e.setBeforeOverMaxCount(x.getOverMaxCount());
            e.setBeforeOverMaxList(toJson(x.getOverMaxList()));
            e.setBeforeMinVol(x.getMinVol());
            e.setBeforeUnderMinCount(x.getUnderMinCount());
            e.setBeforeUnderMinList(toJson(x.getUnderMinList()));
        } else {
            e.setAfterMaxVol(x.getMaxVol());
            e.setAfterOverMaxCount(x.getOverMaxCount());
            e.setAfterOverMaxList(toJson(x.getOverMaxList()));
            e.setAfterMinVol(x.getMinVol());
            e.setAfterUnderMinCount(x.getUnderMinCount());
            e.setAfterUnderMinList(toJson(x.getUnderMinList()));
        }
    }

    // ==================== 电压结果-节点详情 ====================

    private int insertVolDetail(List<VolResultDTO.VolDetailDTO> details, Long resultId) {
        int i = 1;
        for (VolResultDTO.VolDetailDTO d : details) {
            FhzgReconVolDetail e = new FhzgReconVolDetail();
            e.setResultId(resultId);
            e.setDetailNo(i++);
            e.setPsrId(d.getPsrId());
            e.setPsrType(d.getPsrType());
            e.setName(d.getName());
            e.setLoadType(d.getLoadType());
            e.setBeforeU(d.getBeforeU());
            e.setAfterU(d.getAfterU());
            e.setBeforeUlv(d.getBeforeUlv());
            e.setAfterUlv(d.getAfterUlv());
            e.setBeforeP(d.getBeforeP());
            e.setBeforeQ(d.getBeforeQ());
            volDetailMapper.insert(e);
        }
        return i;
    }

    // ==================== 开关状态-详情 ====================

    private int insertSwitchResultDetail(List<SwitchResultDTO.SwitchResultDetailDTO> details, Long resultId) {
        int i = 1;
        for (SwitchResultDTO.SwitchResultDetailDTO d : details) {
            FhzgReconSwitchResultDetail e = new FhzgReconSwitchResultDetail();
            e.setResultId(resultId);
            e.setDetailNo(i++);
            e.setPsrId(d.getPsrId());
            e.setPsrType(d.getPsrType());
            e.setNode1Rdfid(d.getNode1Rdfid());
            e.setNode2Rdfid(d.getNode2Rdfid());
            e.setAutoType(d.getAutoType());
            e.setBeforeState(d.getBeforeState());
            e.setBeforeI(d.getBeforeI());
            e.setBeforeU(d.getBeforeU());
            e.setAfterState(d.getAfterState());
            e.setAfterI(d.getAfterI());
            e.setAfterU(d.getAfterU());
            switchResultDetailMapper.insert(e);
        }
        return i;
    }

    // ==================== 运行指标(扁平 + JSON) ====================

    private FhzgReconMetrics buildMetrics(MetricsDTO dto, Long resultId) {
        FhzgReconMetrics e = new FhzgReconMetrics();
        e.setResultId(resultId);
        fillMetricsSide(e, dto.getMetricsBefore(), true);
        fillMetricsSide(e, dto.getMetricsAfter(), false);
        e.setDelta(toJson(dto.getDelta()));
        e.setJudgement(toJson(dto.getJudgement()));
        return e;
    }

    /** 按 before/after 将指标子对象扁平写入实体对应字段; val 为 null 时跳过. */
    private void fillMetricsSide(FhzgReconMetrics e, MetricsDTO.MetricsBeforeAfterDTO m, boolean before) {
        if (m == null) return;

        MetricsDTO.LoadingDTO ld = m.getLoading();
        if (ld != null) {
            side(e, before, FhzgReconMetrics::setBeforeLoadingLineCount, FhzgReconMetrics::setAfterLoadingLineCount, ld.getLineCount());
            side(e, before, FhzgReconMetrics::setBeforeLoadingExtGridCount, FhzgReconMetrics::setAfterLoadingExtGridCount, ld.getExtGridCount());
            side(e, before, FhzgReconMetrics::setBeforeLoadingMaxLoadingPct, FhzgReconMetrics::setAfterLoadingMaxLoadingPct, ld.getMaxLoadingPct());
            side(e, before, FhzgReconMetrics::setBeforeLoadingAvgLoadingPct, FhzgReconMetrics::setAfterLoadingAvgLoadingPct, ld.getAvgLoadingPct());
            side(e, before, FhzgReconMetrics::setBeforeLoadingOverLimitCount, FhzgReconMetrics::setAfterLoadingOverLimitCount, ld.getOverLimitCount());
            side(e, before, FhzgReconMetrics::setBeforeLoadingLoadingLimitPct, FhzgReconMetrics::setAfterLoadingLoadingLimitPct, ld.getLoadingLimitPct());
            side(e, before, FhzgReconMetrics::setBeforeLoadingUnit, FhzgReconMetrics::setAfterLoadingUnit, ld.getUnit());
            side(e, before, FhzgReconMetrics::setBeforeLoadingLoadingSource, FhzgReconMetrics::setAfterLoadingLoadingSource, ld.getLoadingSource());
            side(e, before, FhzgReconMetrics::setBeforeFeederLoadRatesPct, FhzgReconMetrics::setAfterFeederLoadRatesPct, toJson(ld.getFeederLoadRatesPct()));
            side(e, before, FhzgReconMetrics::setBeforeLoadingLineMaxLoadingPct, FhzgReconMetrics::setAfterLoadingLineMaxLoadingPct, ld.getLineMaxLoadingPct());
            side(e, before, FhzgReconMetrics::setBeforeLoadingLineAvgLoadingPct, FhzgReconMetrics::setAfterLoadingLineAvgLoadingPct, ld.getLineAvgLoadingPct());
            side(e, before, FhzgReconMetrics::setBeforeLoadingLineOverLimitCount, FhzgReconMetrics::setAfterLoadingLineOverLimitCount, ld.getLineOverLimitCount());
        }

        MetricsDTO.LossDTO ls = m.getLoss();
        if (ls != null) {
            side(e, before, FhzgReconMetrics::setBeforeLossLineLossMw, FhzgReconMetrics::setAfterLossLineLossMw, ls.getLineLossMw());
            side(e, before, FhzgReconMetrics::setBeforeLossTrafoLossMw, FhzgReconMetrics::setAfterLossTrafoLossMw, ls.getTrafoLossMw());
            side(e, before, FhzgReconMetrics::setBeforeLossTotalLossMw, FhzgReconMetrics::setAfterLossTotalLossMw, ls.getTotalLossMw());
            side(e, before, FhzgReconMetrics::setBeforeLossUnit, FhzgReconMetrics::setAfterLossUnit, ls.getUnit());
        }

        MetricsDTO.VoltageDTO v = m.getVoltage();
        if (v != null) {
            side(e, before, FhzgReconMetrics::setBeforeVoltageMinVmPu, FhzgReconMetrics::setAfterVoltageMinVmPu, v.getMinVmPu());
            side(e, before, FhzgReconMetrics::setBeforeVoltageMaxVmPu, FhzgReconMetrics::setAfterVoltageMaxVmPu, v.getMaxVmPu());
            side(e, before, FhzgReconMetrics::setBeforeVoltageAvgVmPu, FhzgReconMetrics::setAfterVoltageAvgVmPu, v.getAvgVmPu());
            side(e, before, FhzgReconMetrics::setBeforeVoltageUnderVminCount, FhzgReconMetrics::setAfterVoltageUnderVminCount, v.getUnderVminCount());
            side(e, before, FhzgReconMetrics::setBeforeVoltageOverVmaxCount, FhzgReconMetrics::setAfterVoltageOverVmaxCount, v.getOverVmaxCount());
            side(e, before, FhzgReconMetrics::setBeforeVoltageVminPu, FhzgReconMetrics::setAfterVoltageVminPu, v.getVminPu());
            side(e, before, FhzgReconMetrics::setBeforeVoltageVmaxPu, FhzgReconMetrics::setAfterVoltageVmaxPu, v.getVmaxPu());
            side(e, before, FhzgReconMetrics::setBeforeVoltageBusCount, FhzgReconMetrics::setAfterVoltageBusCount, v.getBusCount());
            side(e, before, FhzgReconMetrics::setBeforeVoltageUnit, FhzgReconMetrics::setAfterVoltageUnit, v.getUnit());
        }

        MetricsDTO.ReliabilityDTO r = m.getReliability();
        if (r != null) {
            side(e, before, FhzgReconMetrics::setBeforeReliabilityEnabled, FhzgReconMetrics::setAfterReliabilityEnabled, r.getEnabled());
            side(e, before, FhzgReconMetrics::setBeforeReliabilityComment, FhzgReconMetrics::setAfterReliabilityComment, r.getComment());
        }

        MetricsDTO.HealthDTO h = m.getHealth();
        if (h != null) {
            side(e, before, FhzgReconMetrics::setBeforeHealthScore, FhzgReconMetrics::setAfterHealthScore, h.getScore());
            side(e, before, FhzgReconMetrics::setBeforeHealthBase, FhzgReconMetrics::setAfterHealthBase, h.getBase());
            side(e, before, FhzgReconMetrics::setBeforeHealthPenalty, FhzgReconMetrics::setAfterHealthPenalty, h.getPenalty());
            ScoreItemsDTO w = h.getWeights();
            if (w != null) {
                side(e, before, FhzgReconMetrics::setBeforeHealthWeightSafe, FhzgReconMetrics::setAfterHealthWeightSafe, w.getSafe());
                side(e, before, FhzgReconMetrics::setBeforeHealthWeightVoltage, FhzgReconMetrics::setAfterHealthWeightVoltage, w.getVoltage());
                side(e, before, FhzgReconMetrics::setBeforeHealthWeightLoss, FhzgReconMetrics::setAfterHealthWeightLoss, w.getLoss());
                side(e, before, FhzgReconMetrics::setBeforeHealthWeightBalance, FhzgReconMetrics::setAfterHealthWeightBalance, w.getBalance());
                side(e, before, FhzgReconMetrics::setBeforeHealthWeightRisk, FhzgReconMetrics::setAfterHealthWeightRisk, w.getRisk());
            }
            ScoreItemsDTO c = h.getComponents();
            if (c != null) {
                side(e, before, FhzgReconMetrics::setBeforeHealthComponentSafe, FhzgReconMetrics::setAfterHealthComponentSafe, c.getSafe());
                side(e, before, FhzgReconMetrics::setBeforeHealthComponentVoltage, FhzgReconMetrics::setAfterHealthComponentVoltage, c.getVoltage());
                side(e, before, FhzgReconMetrics::setBeforeHealthComponentLoss, FhzgReconMetrics::setAfterHealthComponentLoss, c.getLoss());
                side(e, before, FhzgReconMetrics::setBeforeHealthComponentBalance, FhzgReconMetrics::setAfterHealthComponentBalance, c.getBalance());
                side(e, before, FhzgReconMetrics::setBeforeHealthComponentRisk, FhzgReconMetrics::setAfterHealthComponentRisk, c.getRisk());
            }
        }
    }

    /** before 时调 beforeSetter, 否则调 afterSetter; val 为 null 跳过. */
    private <T> void side(FhzgReconMetrics e, boolean before,
                          BiConsumer<FhzgReconMetrics, T> beforeSetter,
                          BiConsumer<FhzgReconMetrics, T> afterSetter, T val) {
        if (val == null) return;
        (before ? beforeSetter : afterSetter).accept(e, val);
    }

    // ==================== 类型转换工具 ====================

    /** ISO8601 字符串(含时区) -> LocalDateTime(取本地壁钟时间); 无法解析返回 null. */
    private LocalDateTime parseTime(String s) {
        if (s == null || s.trim().isEmpty()) return null;
        try {
            return OffsetDateTime.parse(s, ISO_OFFSET).toLocalDateTime();
        } catch (Exception e) {
            try {
                return LocalDateTime.parse(s, DateTimeFormatter.ISO_LOCAL_DATE_TIME);
            } catch (Exception e2) {
                return null;
            }
        }
    }

    /** JsonNode -> JSON 字符串; null 或 null 节点返回 null. */
    private String toJson(JsonNode node) {
        if (node == null || node.isNull()) return null;
        try {
            return OM.writeValueAsString(node);
        } catch (Exception e) {
            return null;
        }
    }
}