import json
from pathlib import Path
from typing import List, Dict, Any, Set

from pilot.job.impl.base_job import BaseJob

class EditJsonDataBase(BaseJob):

    output_file_path = ''

    def propagate_occurs_from_defined(self,
                                      nodes: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
        """
        把所有已经定义好的 occurs（非 None）向下传播。
        - 先找出 `occurs` 已有值的节点列表（可能不止一个根）。
        - 对每个已定义节点遍历它的完整子树，把子树中原本为 None 的 `occurs`
          替换成该祖先的值。
        - 使用显式栈遍历，避免递归深度限制；同时检测环路防止死循环。
        - 原地修改 `nodes` 并返回同一列表（保持顺序）。
        """
        # ---------------------------------------------------------
        # 1️⃣ 建立 name → node 的映射，方便快速定位父/子
        # ---------------------------------------------------------
        by_name: Dict[str, Dict[str, Any]] = {
            n["cobol_field_name"]: n for n in nodes
        }

        # ---------------------------------------------------------
        # 2️⃣ 建立 parent → [children] 的映射
        # ---------------------------------------------------------
        children: Dict[str, List[Dict[str, Any]]] = {}
        for n in nodes:
            p = n["parent_name"]
            children.setdefault(p, []).append(n)  # 即使是 Root 也放进去，后面会统一处理

        # ---------------------------------------------------------
        # 3️⃣ 收集所有已经定义了 occurs（非 None）的节点
        # ---------------------------------------------------------
        defined_nodes: List[Dict[str, Any]] = [
            n for n in nodes if n["occurs"] is not None
        ]

        # ---------------------------------------------------------
        # 4️⃣ 为防止环路导致无限遍历，先做一次环路检测
        # ---------------------------------------------------------
        self._detect_cycle(by_name, children)  # 若有环会抛 OccursPropagationError

        # ---------------------------------------------------------
        # 5️⃣ 用集合记录已经被写入的节点，避免同一子树被多次遍历
        # ---------------------------------------------------------
        filled: Set[str] = set()  # 存放已经处理过的 cobol_field_name

        for src in defined_nodes:
            src_name = src["cobol_field_name"]
            src_occurs = src["occurs"]

            # 若该节点已经在 filled 中，说明它是之前某个祖先的子树，
            # 其值已经被正确传播，无需再次遍历。
            if src_name in filled:
                continue

            # 用显式栈进行深度优先遍历（DFS）
            stack: List[Dict[str, Any]] = [src]

            while stack:
                node = stack.pop()
                name = node["cobol_field_name"]

                # 已经处理过的节点直接跳过（防止同层不同祖先交叉时重复写入）
                if name in filled:
                    continue

                # 只在当前节点的 occurs 为 None 时才写入 src_occurs
                if node["occurs"] is None:
                    node["occurs"] = src_occurs

                # 标记为已处理
                filled.add(name)

                # 把子节点压栈继续向下传播（无论子节点是否已经有值，都要继续遍历，
                # 因为它们的后代可能仍是 None）
                for child in children.get(name, []):
                    stack.append(child)

        return nodes

    # -----------------------------------------------------------------
    # 6️⃣ 环路检测（DFS + 三色标记），若出现环则抛异常
    # -----------------------------------------------------------------
    @staticmethod
    def _detect_cycle(by_name: Dict[str, Dict[str, Any]],
                      children: Dict[str, List[Dict[str, Any]]]) -> None:
        """
        检查 parent_name → cobol_field_name 的有向图是否为 DAG。
        若检测到环路，抛出 OccursPropagationError 并把环路路径打印出来。
        """
        WHITE, GRAY, BLACK = 0, 1, 2
        color: Dict[str, int] = {k: WHITE for k in by_name}
        path_stack: List[str] = []

        def dfs(node_name: str):
            color[node_name] = GRAY
            path_stack.append(node_name)

            for child in children.get(node_name, []):
                cname = child["cobol_field_name"]
                if color[cname] == WHITE:
                    dfs(cname)
                elif color[cname] == GRAY:  # 回边 → 环路
                    idx = path_stack.index(cname)
                    cycle = path_stack[idx:] + [cname]

            path_stack.pop()
            color[node_name] = BLACK

        for name in by_name:
            if color[name] == WHITE:
                dfs(name)

    def main(self):

        with Path(self.file_path).open("r", encoding="utf-8") as f:
            # json.load 会把整个文件解析成 Python 对象，这里应该是 list[dict]
            raw_data = json.load(f)

        edited_data = self.propagate_occurs_from_defined(raw_data)

        cobol_name = Path(self.file_path).parent.name

        # delete_params_info = {
        #     'cobol_name': cobol_name
        # }
        # delete_sql_info('delete_all_parameter_info', delete_params_info)

        # for rec in edited_data:
        #     parent_name = rec.get('parent_name')
        #
        #     if not parent_name:
        #         parent_name = 'Root'
        #     select_params_info = {
        #         'cobol_name': cobol_name,
        #         'parameter_name': rec.get('cobol_field_name'),
        #         'parameter_parent_name': parent_name
        #     }
        #     all_parameter_result = select_sql_info('select_all_parameter_info_by_key', select_params_info)
        #
        #     if len(all_parameter_result) == 0:
        #         parameter_key_name = rec.get('cobol_field_name').split('-')[0]
        #         insert_params_info = {
        #             'cobol_name': cobol_name,
        #             'parameter_name': rec.get('cobol_field_name'),
        #             'parameter_style': rec.get('type'),
        #             'parameter_value': rec.get('value'),
        #             'parameter_key_name': parameter_key_name,
        #             'parameter_parent_name': parent_name,
        #             'parameter_occurs': rec.get('occurs')
        #         }
        #
        #         insert_sql_info('insert_all_parameter_info', insert_params_info)

        output_path = Path(self.file_path).with_suffix('.json')  # 想要的输出文件名


        with output_path.open("w", encoding="utf-8") as f:
            json.dump(edited_data, f, ensure_ascii=False, indent=4)

        self.output_file_path = str(output_path)

    def run(self):

        self.main()

        super().run()