package com.njcn.gather.icd.mapping.infrastructure.parser;

import com.njcn.gather.icd.mapping.pojo.bo.icd.*;
import com.njcn.gather.icd.mapping.infrastructure.parser.generated.*;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * JAXB 生成模型读取器。
 *
 * 负责遍历 `SCL/IED/LDevice/LN0/LN/ReportControl/DataSet/DOI` 结构，
 * 并转换为模块内部使用的 ICD 领域模型。
 */
public class SclGeneratedModelReader {

    /**
     * 从 JAXB SCL 根对象中读取当前模块需要的 ICD 结构。
     */
    public IcdDocument read(SCL scl, String fileName) {
        IcdDocument document = new IcdDocument();
        document.setFileName(fileName);

        // 当前业务只处理第一个可用的非 LD0/PQLD0 逻辑设备。
        TIED targetIed = null;
        TLDevice targetDevice = null;

        if (scl.getIED() != null) {
            for (TIED ied : scl.getIED()) {
                if (ied.getAccessPoint() == null) {
                    continue;
                }
                for (TAccessPoint accessPoint : ied.getAccessPoint()) {
                    TServer server = accessPoint.getServer();
                    if (server == null || server.getLDevice() == null) {
                        continue;
                    }
                    for (TLDevice lDevice : server.getLDevice()) {
                        String inst = lDevice.getInst();
                        if (inst == null || inst.trim().isEmpty()) {
                            continue;
                        }
                        if ("LD0".equals(inst) || "PQLD0".equals(inst)) {
                            continue;
                        }
                        targetIed = ied;
                        targetDevice = lDevice;
                        break;
                    }
                    if (targetDevice != null) {
                        break;
                    }
                }
                if (targetDevice != null) {
                    break;
                }
            }
        }

        if (targetIed == null || targetDevice == null) {
            throw new IllegalArgumentException("未找到可解析的 IED/LDevice 节点");
        }

        IedNode iedNode = new IedNode();
        iedNode.setName(targetIed.getName());
        document.setPrimaryIed(iedNode);
        document.setIedName(targetIed.getName());
        document.setLdInst(targetDevice.getInst());
        document.setLdPrefix(extractLdPrefix(targetDevice.getInst()));

        LogicalDeviceNode logicalDeviceNode = new LogicalDeviceNode();
        logicalDeviceNode.setInst(targetDevice.getInst());
        logicalDeviceNode.setLdName(targetDevice.getLdName());
        document.setLogicalDevice(logicalDeviceNode);

        LN0 ln0 = targetDevice.getLN0();
        if (ln0 != null) {
            readReportAndDataSetFromAnyLn(ln0, document);
            document.getLogicalNodes().add(readLogicalNode(ln0, true));
        }
        if (targetDevice.getLN() != null) {
            for (TLN ln : targetDevice.getLN()) {
                document.getLogicalNodes().add(readLogicalNode(ln, false));
            }
        }
        // `icdcout` 优先取自模板链路 `DataTypeTemplates -> LNodeType -> DO -> DOType -> DA.count`，
        // 只有模板未提供时才退回到 `DataSet/FCDA` 反查，保持与原 C# 行为一致。
        syncDoiSequenceCount(scl, document);

        return document;
    }

    /**
     * 从 LN0 或 LN 中读取 DataSet 与 ReportControl 信息。
     */
    private void readReportAndDataSetFromAnyLn(TAnyLN anyLn, IcdDocument document) {
        //List<FcdaNode> allFcdas = new ArrayList<FcdaNode>();
        if (anyLn.getDataSet() != null) {
            for (TDataSet dataSet : anyLn.getDataSet()) {
                DataSetNode dataSetNode = new DataSetNode();
                dataSetNode.setName(dataSet.getName());
                if (dataSet.getFCDA() != null) {
                    for (TFCDA fcda : dataSet.getFCDA()) {
                        FcdaNode node = toFcdaNode(fcda);
                        dataSetNode.getFcdas().add(node);
                        //allFcdas.add(node);
                    }
                }
                document.getDataSets().put(dataSetNode.getName(), dataSetNode);
            }
        }
        for (DataSetNode dataSet : document.getDataSets().values()) {
            for (FcdaNode fcda : dataSet.getFcdas()) {
                fcda.setSequenceCount(SclTraversalSupport.calculateSequenceCount(dataSet.getFcdas(), fcda));
            }
        }

        if (anyLn.getReportControl() != null) {
            for (TReportControl reportControl : anyLn.getReportControl()) {
                ReportControlNode report = new ReportControlNode();
                report.setName(reportControl.getName());
                report.setRptId(reportControl.getRptID());
                report.setBuffered(reportControl.isBuffered());
                report.setDataSetName(reportControl.getDatSet());
                report.setConfRev(String.valueOf(reportControl.getConfRev()));
                report.setTrgOps(readTrgOps(reportControl));
                document.getReportControls().add(report);
            }
        }
    }

    /**
     * 将 ReportControl.TrgOps 转成后续模板使用的触发编码。
     */
    private String readTrgOps(TReportControl reportControl) {
        if (reportControl.getTrgOps() == null) {
            return null;
        }
        boolean dchg = Boolean.TRUE.equals(reportControl.getTrgOps().isDchg());
        boolean qchg = Boolean.TRUE.equals(reportControl.getTrgOps().isQchg());
        boolean period = Boolean.TRUE.equals(reportControl.getTrgOps().isPeriod());
        if (period) {
            return "40";
        }
        if (dchg || qchg) {
            return "96";
        }
        return null;
    }

    /**
     * 读取逻辑节点。
     *
     * `TAnyLN` 只暴露通用能力，`prefix / lnClass / inst` 实际定义在 `TLN / TLN0` 子类上，
     * 因此这里需要按具体类型分别读取。
     */
    private LnNode readLogicalNode(TAnyLN anyLn, boolean isLn0) {
        LnNode node = new LnNode();
        node.setLn0(isLn0);

        // `lnType` 定义在 `TAnyLN` 上，可直接读取。
        node.setLnType(anyLn.getLnType());

        // `prefix / lnClass / inst` 需要根据具体子类读取。
        node.setPrefix(resolveLnPrefix(anyLn));
        node.setLnClass(resolveLnClass(anyLn));
        node.setLnInst(resolveLnInst(anyLn));

        // `DOI` 定义在 `TAnyLN` 上，可直接读取。
        if (anyLn.getDOI() != null) {
            for (TDOI doi : anyLn.getDOI()) {
                DoiNode doiNode = new DoiNode();
                doiNode.setName(doi.getName());
                doiNode.setIx(doi.getIx());
                doiNode.setLnClass(node.getLnClass());
                doiNode.setLnInst(node.getLnInst());

                if (doi.getSDIOrDAI() != null) {
                    for (TUnNaming child : doi.getSDIOrDAI()) {
                        DoiElementNode childNode = readUnNamingNode(child);
                        if (childNode != null) {
                            doiNode.getChildren().add(childNode);
                        }
                    }
                }

                node.getDoiList().add(doiNode);
            }
        }

        return node;
    }
    /**
     * 解析逻辑节点的 `prefix`。
     *
     * `TLN` 有 `prefix` 字段，`TLN0` 在当前生成模型中没有单独字段，统一返回空字符串。
     */
    private String resolveLnPrefix(TAnyLN anyLn) {
        if (anyLn instanceof TLN) {
            return ((TLN) anyLn).getPrefix();
        }
        return "";
    }

    /**
     * 解析逻辑节点的 `lnClass`。
     *
     * 当前生成模型中，`TLN` 和 `TLN0` 都返回 `List<String>`，这里只取首个值。
     */
    private String resolveLnClass(TAnyLN anyLn) {
        if (anyLn instanceof TLN) {
            return first(((TLN) anyLn).getLnClass());
        }
        if (anyLn instanceof TLN0) {
            return first(((TLN0) anyLn).getLnClass());
        }
        return null;
    }

    /**
     * 解析逻辑节点的 `inst`。
     *
     * 当前生成模型中，`TLN` 和 `TLN0` 都提供了该字段。
     */
    private String resolveLnInst(TAnyLN anyLn) {
        if (anyLn instanceof TLN) {
            return ((TLN) anyLn).getInst();
        }
        if (anyLn instanceof TLN0) {
            return ((TLN0) anyLn).getInst();
        }
        return null;
    }

    /**
     * 递归读取 DOI 下的 SDI/DAI 混合子节点。
     */
    private DoiElementNode readUnNamingNode(TUnNaming source) {
        if (source == null) {
            return null;
        }
        if (source instanceof TSDI) {
            TSDI sdi = (TSDI) source;
            DoiElementNode node = new DoiElementNode();
            node.setKind("SDI");
            node.setName(sdi.getName());
            node.setIx(sdi.getIx());
            if (sdi.getSDIOrDAI() != null) {
                for (TUnNaming child : sdi.getSDIOrDAI()) {
                    DoiElementNode childNode = readUnNamingNode(child);
                    if (childNode != null) {
                        node.getChildren().add(childNode);
                    }
                }
            }
            return node;
        }
        if (source instanceof TDAI) {
            TDAI dai = (TDAI) source;
            DoiElementNode node = new DoiElementNode();
            node.setKind("DAI");
            node.setName(dai.getName());
            node.setIx(dai.getIx());
            if (dai.getVal() != null) {
                for (TVal val : dai.getVal()) {
                    if (val.getValue() != null) {
                        node.getValues().add(val.getValue());
                    }
                }
            }
            return node;
        }
        return null;
    }

    /**
     * 将 JAXB FCDA 节点转换为内部 FCDA 模型。
     */
    private FcdaNode toFcdaNode(TFCDA fcda) {
        FcdaNode node = new FcdaNode();
        node.setLdInst(fcda.getLdInst());
        node.setPrefix(fcda.getPrefix());
        node.setLnClass(first(fcda.getLnClass()));
        node.setLnInst(fcda.getLnInst());
        node.setDoName(fcda.getDoName());
        node.setDaName(fcda.getDaName());
        node.setFc(fcda.getFc());
        node.setIx(fcda.getIx());
        return node;
    }

    /**
     * 读取 JAXB 列表型字段的首个值。
     */
    private String first(List<String> values) {
        return values == null || values.isEmpty() ? null : values.get(0);
    }

    /**
     * 从 LD 实例名中提取非数字前缀，例如 PQM1 -> PQM。
     */
    private String extractLdPrefix(String ldInst) {
        if (ldInst == null) {
            return null;
        }
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < ldInst.length(); i++) {
            char c = ldInst.charAt(i);
            if (Character.isDigit(c)) {
                break;
            }
            builder.append(c);
        }
        return builder.length() == 0 ? ldInst : builder.toString();
    }

    /**
     * 回填 DOI 的 `sequenceCount`。
     *
     * 优先按模板链路取值，模板缺失时再退回 `DataSet/FCDA` 反查，确保后续生成结果与原 C# 逻辑一致。
     */
    private void syncDoiSequenceCount(SCL scl, IcdDocument document) {
        if (document == null || document.getLogicalNodes() == null) {
            return;
        }

        // 先构建 `lnType + doName -> count` 缓存。
        Map<String, Integer> templateSequenceCountMap = buildTemplateSequenceCountMap(scl);

        for (LnNode lnNode : document.getLogicalNodes()) {
            if (lnNode == null || lnNode.getDoiList() == null) {
                continue;
            }

            for (DoiNode doiNode : lnNode.getDoiList()) {
                if (doiNode == null) {
                    continue;
                }

                int templateCount = 0;
                if (lnNode.getLnType() != null && doiNode.getName() != null) {
                    Integer value = templateSequenceCountMap.get(buildLnTypeDoKey(lnNode.getLnType(), doiNode.getName()));
                    templateCount = value == null ? 0 : value.intValue();
                }

                int fcdaCount = findDoiSequenceCountFromFcda(document, doiNode);

                // 优先使用模板中的 `count`，保持与原 C# 行为一致。
                doiNode.setSequenceCount(templateCount > 0 ? templateCount : fcdaCount);
            }
        }
    }

    /**
     * 构建 `lnType + doName -> 序列数量` 缓存。
     *
     * 数据来源为 `DataTypeTemplates -> LNodeType -> DO -> DOType -> DA.count`，
     * 原 C# 版本也是沿着这条链路计算 `doi.tNUM`。
     */
    private Map<String, Integer> buildTemplateSequenceCountMap(SCL scl) {
        Map<String, Integer> result = new LinkedHashMap<String, Integer>();
        if (scl == null || scl.getDataTypeTemplates() == null) {
            return result;
        }

        TDataTypeTemplates templates = scl.getDataTypeTemplates();
        if (templates.getLNodeType() == null || templates.getDOType() == null) {
            return result;
        }

        // 1. 先构建 `DOType.id -> count`。
        Map<String, Integer> doTypeCountMap = new LinkedHashMap<String, Integer>();
        for (TDOType doType : templates.getDOType()) {
            if (doType == null || doType.getId() == null) {
                continue;
            }

            int count = extractDoTypeSequenceCount(doType);
            doTypeCountMap.put(doType.getId(), count);
        }

        // 2. 再构建 `lnType + doName -> count`。
        for (TLNodeType lNodeType : templates.getLNodeType()) {
            if (lNodeType == null || lNodeType.getId() == null || lNodeType.getDO() == null) {
                continue;
            }

            for (TDO tdo : lNodeType.getDO()) {
                if (tdo == null || tdo.getName() == null || tdo.getType() == null) {
                    continue;
                }

                Integer count = doTypeCountMap.get(tdo.getType());
                if (count == null) {
                    count = 0;
                }

                result.put(buildLnTypeDoKey(lNodeType.getId(), tdo.getName()), count);
            }
        }

        return result;
    }

    /**
     * 从单个 `DOType` 中提取序列数量。
     *
     * 这里取顶层 `DA.count` 中最大的正整数，贴近原 C# 的处理方式。
     */
    private int extractDoTypeSequenceCount(TDOType doType) {
        int max = 0;
        if (doType == null || doType.getSDOOrDA() == null) {
            return 0;
        }

        for (TUnNaming item : doType.getSDOOrDA()) {
            if (!(item instanceof TDA)) {
                continue;
            }

            TDA da = (TDA) item;
            int value = parseDaCount(da);
            if (value > max) {
                max = value;
            }
        }

        return max;
    }

    /**
     * 解析 `DA.count`。
     *
     * 当前 JAXB 生成类把该字段定义为 `List<String>`，这里统一做安全转换。
     */
    private int parseDaCount(TAbstractDataAttribute dataAttribute) {
        if (dataAttribute == null || dataAttribute.getCount() == null || dataAttribute.getCount().isEmpty()) {
            return 0;
        }

        int max = 0;
        for (String raw : dataAttribute.getCount()) {
            if (raw == null) {
                continue;
            }

            String text = raw.trim();
            if (text.isEmpty()) {
                continue;
            }

            try {
                int value = Integer.parseInt(text);
                if (value > max) {
                    max = value;
                }
            } catch (NumberFormatException ignore) {
                // 非法 `count` 直接忽略，保持容错。
            }
        }
        return max;
    }

    /**
     * 从 `DataSet/FCDA` 反查 `sequenceCount`。
     *
     * 这里只在模板未提供 `count` 时作为兜底逻辑使用。
     */
    private int findDoiSequenceCountFromFcda(IcdDocument document, DoiNode doiNode) {
        int max = 0;
        if (document == null || document.getDataSets() == null || doiNode == null) {
            return 0;
        }

        for (DataSetNode dataSetNode : document.getDataSets().values()) {
            if (dataSetNode == null || dataSetNode.getFcdas() == null) {
                continue;
            }

            for (FcdaNode fcda : dataSetNode.getFcdas()) {
                if (fcda == null) {
                    continue;
                }

                boolean sameLnClass = equalsTrim(fcda.getLnClass(), doiNode.getLnClass());
                boolean sameLnInst = equalsTrim(fcda.getLnInst(), doiNode.getLnInst());
                boolean sameDoName = equalsTrim(fcda.getDoName(), doiNode.getName());

                if (sameLnClass && sameLnInst && sameDoName) {
                    if (fcda.getSequenceCount() > max) {
                        max = fcda.getSequenceCount();
                    }
                    if (fcda.getIx() != null && fcda.getIx() > max) {
                        max = fcda.getIx().intValue();
                    }
                }
            }
        }

        return max;
    }

    /**
     * 构造模板序列数量缓存使用的复合 key。
     */
    private String buildLnTypeDoKey(String lnType, String doName) {
        String left = lnType == null ? "" : lnType.trim();
        String right = doName == null ? "" : doName.trim();
        return left + "##" + right;
    }

    /**
     * 空安全的字符串比较。
     */
    private boolean equalsTrim(String left, String right) {
        if (left == null && right == null) {
            return true;
        }
        if (left == null || right == null) {
            return false;
        }
        return left.trim().equals(right.trim());
    }
}