package com.njcn.gather.icd.mapping.component;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.njcn.gather.icd.mapping.pojo.bo.mapping.DataSetGroupItem;
import com.njcn.gather.icd.mapping.pojo.bo.mapping.DoiItem;
import com.njcn.gather.icd.mapping.pojo.bo.mapping.InstItem;
import com.njcn.gather.icd.mapping.pojo.bo.mapping.MappingDocument;
import com.njcn.gather.icd.mapping.pojo.bo.mapping.ReportMapItem;
import com.njcn.gather.icd.mapping.pojo.bo.mapping.SdiItem;
import com.njcn.gather.icd.mapping.pojo.param.IcdConsistencyCheckRequest;
import com.njcn.gather.icd.mapping.pojo.vo.IcdConsistencyCheckResponse;
import com.njcn.gather.icd.mapping.pojo.vo.IcdConsistencyIssue;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * ICD 映射 JSON 一致性校验服务。
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class IcdConsistencyCheckService {

    private static final String PASS_MESSAGE = "符合";
    private static final String NOT_PASS_MESSAGE = "不符合";
    private static final String ISSUE_FILE_NAME = "icd-consistency-issues.json";
    private static final List<String> REQUIRED_REPORT_DESCS = Arrays.asList("统计数据", "波动闪变", "实时数据", "暂态事件");
    private static final List<String> REQUIRED_LN_CLASSES = Arrays.asList("MMXU", "MSQI", "MHAI", "MFLK");
    private static final Set<String> EXCLUDED_DOI_DESCS = new HashSet<String>(Arrays.asList(
            "电压扰动事件启动", "电压暂降事件启动", "电压暂升事件启动", "电压中断事件启动",
            "电压暂降启动定值", "电压暂升启动定值", "电压中断启动定值"));

    private final FileStorageService fileStorageService;
    private final ObjectMapper objectMapper = buildMapper();

    public IcdConsistencyCheckResponse check(IcdConsistencyCheckRequest request) {
        if (request == null) {
            throw new IllegalArgumentException("ICD 一致性校验请求不能为空");
        }
        MappingDocument checked = parseMapping(request.getCheckedJson(), "待校验 JSON");
        MappingDocument standard = parseMapping(request.getStandardJson(), "标准 JSON");

        List<IcdConsistencyIssue> issues = new ArrayList<IcdConsistencyIssue>();
        validateSelfFormat(checked, "待校验映射", issues);
        boolean corrected = applySelfMappingRules(checked, standard);
        corrected = removeDuplicateDescDoiItems(checked, standard, issues) || corrected;
        validateConsistency(checked, standard, issues);

        IcdConsistencyCheckResponse response = new IcdConsistencyCheckResponse();
        response.setResult(issues.isEmpty() ? 1 : 0);
        response.setMessage(issues.isEmpty() ? PASS_MESSAGE : NOT_PASS_MESSAGE);
        response.getIssues().addAll(issues);
        if (!issues.isEmpty()) {
            response.setIssuesJson(toJson(issues));
            if (request.isSaveToDisk()) {
                fileStorageService.save(ISSUE_FILE_NAME, response.getIssuesJson(), request.getOutputDir());
            }
        }
        if (corrected) {
            response.setCorrectedJson(toJson(checked));
        }
        return response;
    }

    private MappingDocument parseMapping(String json, String label) {
        if (isBlank(json)) {
            throw new IllegalArgumentException(label + "不能为空");
        }
        try {
            return objectMapper.readValue(json, MappingDocument.class);
        } catch (Exception ex) {
            throw new IllegalArgumentException(label + "解析失败：" + ex.getMessage(), ex);
        }
    }

    private void validateSelfFormat(MappingDocument document, String label, List<IcdConsistencyIssue> issues) {
        if (document == null) {
            addIssue(issues, "自身格式校验", label, label + "不能为空", null, null, false);
            return;
        }
        requireText(document.getIed(), "IED", issues);
        requireText(document.getLd(), "LD", issues);
        requireText(document.getDataType(), "DataType", issues);
        requireText(document.getUnit(), "unit", issues);

        validateRequiredReportDescs(document, issues);
        validateRequiredLnClasses(document, issues);
        validateDoiDuplicateNames(document, issues);
        validateNestedLists(document, issues);
    }

    private void requireText(String value, String path, List<IcdConsistencyIssue> issues) {
        if (isBlank(value)) {
            addIssue(issues, "自身格式校验", path, path + "不能为空", null, value, false);
        }
    }

    private void validateRequiredReportDescs(MappingDocument document, List<IcdConsistencyIssue> issues) {
        Set<String> actualDescs = new HashSet<String>();
        if (document.getReportMap() != null) {
            for (ReportMapItem item : document.getReportMap()) {
                actualDescs.add(trimToEmpty(item.getDesc()));
            }
        }
        for (String desc : REQUIRED_REPORT_DESCS) {
            if (!actualDescs.contains(desc)) {
                addIssue(issues, "自身格式校验", "ReportMap.desc", "ReportMap 最少需要包含一条" + desc, desc, null, false);
            }
        }
    }

    private void validateRequiredLnClasses(MappingDocument document, List<IcdConsistencyIssue> issues) {
        Set<String> actualLnClasses = new HashSet<String>();
        if (document.getDataSetList() != null) {
            for (DataSetGroupItem item : document.getDataSetList()) {
                actualLnClasses.add(trimToEmpty(item.getLnClass()));
            }
        }
        for (String lnClass : REQUIRED_LN_CLASSES) {
            if (!actualLnClasses.contains(lnClass)) {
                addIssue(issues, "自身格式校验", "DataSetList.lnClass", "DataSetList 最少需要包含一组 lnClass=" + lnClass, lnClass, null, false);
            }
        }
    }

    private void validateDoiDuplicateNames(MappingDocument document, List<IcdConsistencyIssue> issues) {
        if (document.getDataSetList() == null) {
            return;
        }
        for (DataSetGroupItem group : document.getDataSetList()) {
            if (group.getInstList() == null) {
                continue;
            }
            for (InstItem inst : group.getInstList()) {
                Set<String> doiKeys = new HashSet<String>();
                if (inst.getDoiList() == null) {
                    continue;
                }
                for (DoiItem doi : inst.getDoiList()) {
                    String key = buildKey(doi.getName(), doi.getDesc());
                    if (!doiKeys.add(key)) {
                        String path = buildInstPath(group, inst) + ".doiList";
                        addIssue(issues, "自身格式校验", path, "同一个 doiList 下不能存在 name+desc 都一样的指标", key, key, false);
                    }
                }
            }
        }
    }

    private void validateNestedLists(MappingDocument document, List<IcdConsistencyIssue> issues) {
        if (document.getDataSetList() == null) {
            return;
        }
        for (DataSetGroupItem group : document.getDataSetList()) {
            if (isEmpty(group.getInstList())) {
                addIssue(issues, "自身格式校验", buildGroupPath(group), "instList 不能为空：" + trimToEmpty(group.getDesc()), null, null, false);
                continue;
            }
            for (InstItem inst : group.getInstList()) {
                String instPath = buildInstPath(group, inst);
                if (isEmpty(inst.getDoiList())) {
                    addIssue(issues, "自身格式校验", instPath, "doiList 不能为空：" + joinDesc(group.getDesc(), inst.getDesc()), null, null, false);
                    continue;
                }
                for (DoiItem doi : inst.getDoiList()) {
                    if (isExcludedDoiDesc(doi.getDesc())) {
                        continue;
                    }
                    if (isEmpty(doi.getSdiList())) {
                        addIssue(issues, "自身格式校验", instPath + ".doiList[" + buildKey(doi.getName(), doi.getDesc()) + "]",
                                "typeList 不能为空：" + joinDesc(group.getDesc(), inst.getDesc(), doi.getDesc()), null, null, false);
                        continue;
                    }
                    for (SdiItem sdi : doi.getSdiList()) {
                        if (isEmpty(sdi.getTypeList())) {
                            addIssue(issues, "自身格式校验", instPath + ".doiList[" + buildKey(doi.getName(), doi.getDesc()) + "].sdiList[" + buildKey(sdi.getName(), sdi.getDesc()) + "]",
                                    "typeList 不能为空：" + joinDesc(group.getDesc(), inst.getDesc(), doi.getDesc(), sdi.getDesc()), null, null, false);
                        }
                    }
                }
            }
        }
    }

    private void validateConsistency(MappingDocument checked, MappingDocument standard, List<IcdConsistencyIssue> issues) {
        compareValue(standard.getIed(), checked.getIed(), "基础字段", "IED", issues, false);
        compareValue(standard.getLd(), checked.getLd(), "基础字段", "LD", issues, false);
        compareValue(standard.getDataType(), checked.getDataType(), "基础字段", "DataType", issues, false);
        compareValue(standard.getUnit(), checked.getUnit(), "基础字段", "unit", issues, false);
        validateReportMapConsistency(checked, standard, issues);
        validateDataSetConsistency(checked, standard, issues);
    }

    private boolean applySelfMappingRules(MappingDocument checked, MappingDocument standard) {
        if (checked.getReportMap() == null) {
            return false;
        }
        Map<String, ReportMapItem> standardMap = indexReportMap(standard);
        boolean hasRtFre = false;
        boolean corrected = false;
        for (ReportMapItem item : checked.getReportMap()) {
            if ("实时数据".equals(trimToEmpty(item.getDesc())) && containsRtFre(item.getRptId())) {
                hasRtFre = true;
                ReportMapItem standardItem = standardMap.get(buildReportKey(item));
                boolean needRuleCorrection = standardItem == null
                        || !equalsValue(String.valueOf(standardItem.getReportCount()), String.valueOf(item.getReportCount()))
                        || !equalsValue(standardItem.getFlickerFlag(), item.getFlickerFlag());
                if (!needRuleCorrection) {
                    continue;
                }
                if (!"1".equals(trimToEmpty(item.getFlickerFlag()))) {
                    item.setFlickerFlag("1");
                    corrected = true;
                }
                if (item.getReportCount() != 0) {
                    item.setReportCount(0);
                    corrected = true;
                }
            }
        }
        if (!hasRtFre) {
            return false;
        }
        for (ReportMapItem item : checked.getReportMap()) {
            if ("统计数据".equals(trimToEmpty(item.getDesc()))) {
                ReportMapItem standardItem = standardMap.get(buildReportKey(item));
                if (standardItem != null && equalsValue(String.valueOf(standardItem.getReportCount()), String.valueOf(item.getReportCount()))) {
                    continue;
                }
                int adjustedCount = item.getReportCount() - 1;
                if (item.getReportCount() != adjustedCount) {
                    item.setReportCount(adjustedCount);
                    corrected = true;
                }
            }
        }
        return corrected;
    }

    private boolean removeDuplicateDescDoiItems(MappingDocument checked, MappingDocument standard, List<IcdConsistencyIssue> issues) {
        if (checked.getDataSetList() == null) {
            return false;
        }
        Map<String, Set<String>> standardDoiKeys = indexStandardDoiKeysByInst(standard);
        boolean corrected = false;
        for (DataSetGroupItem group : checked.getDataSetList()) {
            if (group.getInstList() == null) {
                continue;
            }
            for (InstItem inst : group.getInstList()) {
                if (inst.getDoiList() == null) {
                    continue;
                }
                Map<String, List<DoiItem>> doiItemsByDesc = new HashMap<String, List<DoiItem>>();
                for (DoiItem doi : inst.getDoiList()) {
                    String desc = trimToEmpty(doi.getDesc());
                    if (!doiItemsByDesc.containsKey(desc)) {
                        doiItemsByDesc.put(desc, new ArrayList<DoiItem>());
                    }
                    doiItemsByDesc.get(desc).add(doi);
                }
                String instPath = buildInstPath(group, inst);
                Set<String> currentStandardKeys = standardDoiKeys.get(buildInstKey(group, inst));
                for (Map.Entry<String, List<DoiItem>> entry : doiItemsByDesc.entrySet()) {
                    if (entry.getValue().size() <= 1) {
                        continue;
                    }
                    DoiItem retained = chooseRetainedDoi(entry.getValue(), currentStandardKeys);
                    String message = "同一个 doiList 下存在 desc 相同的指标，已仅保留 " + buildKey(retained.getDesc(), retained.getName())
                            + "；重复组合：" + describeDoiStandardMatches(entry.getValue(), currentStandardKeys);
                    addIssue(issues, "映射规则", instPath + ".doiList[desc=" + entry.getKey() + "]", message,
                            null, describeDoiKeys(entry.getValue()), true);
                    removeDuplicatedDoiItems(inst.getDoiList(), entry.getValue(), retained);
                    corrected = true;
                }
            }
        }
        return corrected;
    }

    private void validateReportMapConsistency(MappingDocument checked, MappingDocument standard, List<IcdConsistencyIssue> issues) {
        Map<String, ReportMapItem> checkedMap = new HashMap<String, ReportMapItem>();
        if (checked.getReportMap() != null) {
            for (ReportMapItem item : checked.getReportMap()) {
                checkedMap.put(buildReportKey(item), item);
            }
        }
        if (standard.getReportMap() == null) {
            return;
        }
        for (ReportMapItem standardItem : standard.getReportMap()) {
            String key = buildReportKey(standardItem);
            ReportMapItem checkedItem = checkedMap.get(key);
            if (checkedItem == null) {
                addIssue(issues, "一致性校验", "ReportMap[" + key + "]", "标准映射中的 ReportMap 对象在待校验映射中不存在", key, null, false);
                continue;
            }
            compareValue(String.valueOf(standardItem.getReportCount()), String.valueOf(checkedItem.getReportCount()), "ReportMap", "ReportMap[" + key + "].reportCount", issues, false);
            compareValue(standardItem.getBuffered(), checkedItem.getBuffered(), "ReportMap", "ReportMap[" + key + "].buffered", issues, false);
            compareValue(standardItem.getInst(), checkedItem.getInst(), "ReportMap", "ReportMap[" + key + "].inst", issues, false);
            compareValue(standardItem.getFlickerFlag(), checkedItem.getFlickerFlag(), "ReportMap", "ReportMap[" + key + "].FlickerFlag", issues, false);
            compareValue(standardItem.getSelect(), checkedItem.getSelect(), "ReportMap", "ReportMap[" + key + "].Select", issues, false);
            compareValue(standardItem.getTrgOps(), checkedItem.getTrgOps(), "ReportMap", "ReportMap[" + key + "].TrgOps", issues, false);
        }
    }

    private void validateDataSetConsistency(MappingDocument checked, MappingDocument standard, List<IcdConsistencyIssue> issues) {
        Map<String, DataSetGroupItem> checkedGroups = indexGroups(checked);
        if (standard.getDataSetList() == null) {
            return;
        }
        for (DataSetGroupItem standardGroup : standard.getDataSetList()) {
            String groupKey = buildKey(standardGroup.getLnClass(), standardGroup.getDesc());
            DataSetGroupItem checkedGroup = checkedGroups.get(groupKey);
            if (checkedGroup == null) {
                addIssue(issues, "一致性校验", "DataSetList[" + groupKey + "]", "标准映射中的 lnClass+desc 在待校验映射中不存在", groupKey, null, false);
                continue;
            }
            validateInstConsistency(checkedGroup, standardGroup, groupKey, issues);
        }
    }

    private void validateInstConsistency(DataSetGroupItem checkedGroup, DataSetGroupItem standardGroup, String groupKey, List<IcdConsistencyIssue> issues) {
        Map<String, InstItem> checkedInstMap = indexInst(checkedGroup);
        if (standardGroup.getInstList() == null) {
            return;
        }
        for (InstItem standardInst : standardGroup.getInstList()) {
            String instKey = buildKey(standardInst.getInst(), standardInst.getDesc());
            InstItem checkedInst = checkedInstMap.get(instKey);
            if (checkedInst == null) {
                addIssue(issues, "一致性校验", "DataSetList[" + groupKey + "].instList[" + instKey + "]",
                        "标准映射中的 inst+desc 在待校验映射中不存在", instKey, null, false);
                continue;
            }
            validateDoiConsistency(checkedInst, standardInst, groupKey, instKey, issues);
        }
    }

    private void validateDoiConsistency(InstItem checkedInst, InstItem standardInst, String groupKey, String instKey, List<IcdConsistencyIssue> issues) {
        Map<String, DoiItem> checkedDoiMap = indexDoi(checkedInst);
        if (standardInst.getDoiList() == null) {
            return;
        }
        for (DoiItem standardDoi : standardInst.getDoiList()) {
            if (isExcludedDoiDesc(standardDoi.getDesc())) {
                continue;
            }
            String doiKey = buildKey(standardDoi.getName(), standardDoi.getDesc());
            DoiItem checkedDoi = checkedDoiMap.get(doiKey);
            String path = "DataSetList[" + groupKey + "].instList[" + instKey + "].doiList[" + doiKey + "]";
            if (checkedDoi == null) {
                addIssue(issues, "一致性校验", path, "标准映射中的 name+desc 在待校验映射中不存在", doiKey, null, false);
                continue;
            }
            compareDoiFields(checkedDoi, standardDoi, path, issues);
        }
    }

    private void compareDoiFields(DoiItem checked, DoiItem standard, String path, List<IcdConsistencyIssue> issues) {
        compareValue(String.valueOf(standard.getStart()), String.valueOf(checked.getStart()), "doiList", path + ".start", issues, false);
        compareValue(String.valueOf(standard.getEnd()), String.valueOf(checked.getEnd()), "doiList", path + ".end", issues, false);
        compareValue(standard.getUnit(), checked.getUnit(), "doiList", path + ".unit", issues, false);
        compareValue(String.valueOf(standard.getCoefficient()), String.valueOf(checked.getCoefficient()), "doiList", path + ".coefficient", issues, false);
        compareValue(String.valueOf(standard.getBaseflag()), String.valueOf(checked.getBaseflag()), "doiList", path + ".baseflag", issues, false);
        compareValue(String.valueOf(standard.getBasecount()), String.valueOf(checked.getBasecount()), "doiList", path + ".basecount", issues, false);
        compareValue(String.valueOf(standard.getIcdcout()), String.valueOf(checked.getIcdcout()), "doiList", path + ".icdcout", issues, false);
    }

    private void compareValue(String standardValue, String checkedValue, String scope, String path,
                              List<IcdConsistencyIssue> issues, boolean corrected) {
        if (!equalsValue(standardValue, checkedValue)) {
            addIssue(issues, scope, path, path + " 与标准映射不一致", standardValue, checkedValue, corrected);
        }
    }

    private void addIssue(List<IcdConsistencyIssue> issues, String scope, String path, String message,
                          String standardValue, String checkedValue, boolean corrected) {
        IcdConsistencyIssue issue = new IcdConsistencyIssue();
        issue.setScope(scope);
        issue.setPath(path);
        issue.setMessage(message);
        issue.setStandardValue(standardValue);
        issue.setCheckedValue(checkedValue);
        issue.setCorrected(corrected);
        issues.add(issue);
    }

    private Map<String, ReportMapItem> indexReportMap(MappingDocument document) {
        Map<String, ReportMapItem> result = new HashMap<String, ReportMapItem>();
        if (document.getReportMap() == null) {
            return result;
        }
        for (ReportMapItem item : document.getReportMap()) {
            result.put(buildReportKey(item), item);
        }
        return result;
    }

    private Map<String, DataSetGroupItem> indexGroups(MappingDocument document) {
        Map<String, DataSetGroupItem> result = new HashMap<String, DataSetGroupItem>();
        if (document.getDataSetList() == null) {
            return result;
        }
        for (DataSetGroupItem item : document.getDataSetList()) {
            result.put(buildKey(item.getLnClass(), item.getDesc()), item);
        }
        return result;
    }

    private Map<String, InstItem> indexInst(DataSetGroupItem group) {
        Map<String, InstItem> result = new HashMap<String, InstItem>();
        if (group.getInstList() == null) {
            return result;
        }
        for (InstItem item : group.getInstList()) {
            result.put(buildKey(item.getInst(), item.getDesc()), item);
        }
        return result;
    }

    private Map<String, DoiItem> indexDoi(InstItem inst) {
        Map<String, DoiItem> result = new HashMap<String, DoiItem>();
        if (inst.getDoiList() == null) {
            return result;
        }
        for (DoiItem item : inst.getDoiList()) {
            result.put(buildKey(item.getName(), item.getDesc()), item);
        }
        return result;
    }

    private Map<String, Set<String>> indexStandardDoiKeysByInst(MappingDocument standard) {
        Map<String, Set<String>> result = new HashMap<String, Set<String>>();
        if (standard.getDataSetList() == null) {
            return result;
        }
        for (DataSetGroupItem group : standard.getDataSetList()) {
            if (group.getInstList() == null) {
                continue;
            }
            for (InstItem inst : group.getInstList()) {
                Set<String> doiKeys = new HashSet<String>();
                if (inst.getDoiList() != null) {
                    for (DoiItem doi : inst.getDoiList()) {
                        doiKeys.add(buildKey(doi.getDesc(), doi.getName()));
                    }
                }
                result.put(buildInstKey(group, inst), doiKeys);
            }
        }
        return result;
    }

    private String buildReportKey(ReportMapItem item) {
        return buildKey(item.getDesc(), item.getRptId(), item.getName());
    }

    private String buildInstKey(DataSetGroupItem group, InstItem inst) {
        return buildKey(group.getLnClass(), group.getDesc(), inst.getInst(), inst.getDesc());
    }

    private String buildGroupPath(DataSetGroupItem group) {
        return "DataSetList[" + buildKey(group.getLnClass(), group.getDesc()) + "]";
    }

    private String buildInstPath(DataSetGroupItem group, InstItem inst) {
        return buildGroupPath(group) + ".instList[" + buildKey(inst.getInst(), inst.getDesc()) + "]";
    }

    private String buildKey(String... values) {
        List<String> parts = new ArrayList<String>();
        for (String value : values) {
            parts.add(trimToEmpty(value));
        }
        return String.join("+", parts);
    }

    private DoiItem chooseRetainedDoi(List<DoiItem> doiItems, Set<String> standardDoiKeys) {
        if (standardDoiKeys != null) {
            for (DoiItem item : doiItems) {
                if (standardDoiKeys.contains(buildKey(item.getDesc(), item.getName()))) {
                    return item;
                }
            }
        }
        return doiItems.get(0);
    }

    private void removeDuplicatedDoiItems(List<DoiItem> allDoiItems, List<DoiItem> duplicatedItems, DoiItem retained) {
        Iterator<DoiItem> iterator = allDoiItems.iterator();
        while (iterator.hasNext()) {
            DoiItem item = iterator.next();
            if (duplicatedItems.contains(item) && item != retained) {
                iterator.remove();
            }
        }
    }

    private String describeDoiStandardMatches(List<DoiItem> doiItems, Set<String> standardDoiKeys) {
        List<String> values = new ArrayList<String>();
        for (DoiItem item : doiItems) {
            String key = buildKey(item.getDesc(), item.getName());
            boolean existsInStandard = standardDoiKeys != null && standardDoiKeys.contains(key);
            values.add(key + (existsInStandard ? " 在标准映射中存在" : " 不在标准映射中"));
        }
        return String.join("，", values);
    }

    private String describeDoiKeys(List<DoiItem> doiItems) {
        List<String> values = new ArrayList<String>();
        for (DoiItem item : doiItems) {
            values.add(buildKey(item.getDesc(), item.getName()));
        }
        return String.join("，", values);
    }

    private boolean equalsValue(String left, String right) {
        return trimToEmpty(left).equals(trimToEmpty(right));
    }

    private boolean containsRtFre(String value) {
        return trimToEmpty(value).toLowerCase().contains("rtfre");
    }

    private boolean isExcludedDoiDesc(String desc) {
        return EXCLUDED_DOI_DESCS.contains(trimToEmpty(desc));
    }

    private boolean isBlank(String value) {
        return value == null || value.trim().isEmpty();
    }

    private String trimToEmpty(String value) {
        return value == null ? "" : value.trim();
    }

    private boolean isEmpty(List<?> list) {
        return list == null || list.isEmpty();
    }

    private String joinDesc(String... descs) {
        List<String> values = new ArrayList<String>();
        for (String desc : descs) {
            if (!isBlank(desc)) {
                values.add(desc.trim());
            }
        }
        return String.join(" / ", values);
    }

    private String toJson(Object value) {
        try {
            return objectMapper.writeValueAsString(value);
        } catch (Exception ex) {
            throw new IllegalArgumentException("ICD 一致性校验结果序列化失败：" + ex.getMessage(), ex);
        }
    }

    private static ObjectMapper buildMapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        mapper.enable(SerializationFeature.INDENT_OUTPUT);
        return mapper;
    }
}
