"""
Business Domain 核心业务逻辑模块
提供对 Neo4j 图数据库中 BusinessDomain 节点的操作功能
"""

import logging
from typing import Any, Dict, List, Optional, Tuple

from app.core.common.identifiers import ensure_governance_uid

from app import db
from app.core.common.timezone_utils import now_china_naive
from app.models.metadata_review import MetadataReviewRecord
from app.services.neo4j_driver import neo4j_driver

logger = logging.getLogger("app")


def _norm_str(value: Any) -> str:
    if value is None:
        return ""
    return str(value).strip()


def _norm_data_type(value: Any) -> str:
    # 统一大小写与空白，便于严格比较
    s = _norm_str(value)
    s = " ".join(s.split())
    return s.lower()


def _extract_tag_ids_from_item(item: Dict[str, Any]) -> List[int]:
    # 兼容可能的字段：tag / tag_ids / tagIdList
    tag_data = item.get("tag") or item.get("tag_ids") or item.get("tagIdList") or []
    return normalize_tag_inputs(tag_data)


def _get_meta_tag_ids(session, meta_id: int) -> List[int]:
    cypher = """
    MATCH (m:DataMeta)-[:LABEL]->(t:DataLabel)
    WHERE id(m) = $meta_id
    RETURN collect(id(t)) as tag_ids
    """
    record = session.run(cypher, {"meta_id": int(meta_id)}).single()
    tag_ids = record["tag_ids"] if record and "tag_ids" in record else []
    tag_ids = [int(t) for t in (tag_ids or []) if t is not None]
    tag_ids.sort()
    return tag_ids


def _get_meta_snapshot(session, meta_id: int) -> Dict[str, Any]:
    cypher = """
    MATCH (m:DataMeta)
    WHERE id(m) = $meta_id
    RETURN m
    """
    record = session.run(cypher, {"meta_id": int(meta_id)}).single()
    if not record or not record.get("m"):
        return {"id": int(meta_id)}
    m_node = record["m"]
    props = serialize_node_properties(m_node)
    return {
        "id": int(meta_id),
        "name_zh": props.get("name_zh", ""),
        "name_en": props.get("name_en", ""),
        "data_type": props.get("data_type", ""),
        "status": props.get("status", True),
        "tag_ids": _get_meta_tag_ids(session, int(meta_id)),
    }


def _build_new_meta_snapshot(item: Dict[str, Any]) -> Dict[str, Any]:
    name_zh = _norm_str(item.get("name_zh"))
    name_en = _norm_str(item.get("name_en"))
    data_type = _norm_data_type(item.get("data_type", "varchar(255)"))
    tag_ids = _extract_tag_ids_from_item(item)
    tag_ids_sorted = sorted({int(t) for t in tag_ids if t is not None})
    return {
        "name_zh": name_zh,
        "name_en": name_en,
        "data_type": data_type,
        "tag_ids": tag_ids_sorted,
    }


def _is_exact_match(new_meta: Dict[str, Any], cand: Dict[str, Any]) -> bool:
    return (
        _norm_str(new_meta.get("name_zh")) == _norm_str(cand.get("name_zh"))
        and _norm_str(new_meta.get("name_en")) == _norm_str(cand.get("name_en"))
        and _norm_data_type(new_meta.get("data_type"))
        == _norm_data_type(cand.get("data_type"))
        and sorted(new_meta.get("tag_ids") or []) == sorted(cand.get("tag_ids") or [])
    )


def _match_name_and_type(new_meta: Dict[str, Any], cand: Dict[str, Any]) -> bool:
    """
    检查 name_zh、name_en 和 data_type 是否都匹配
    """
    return (
        _norm_str(new_meta.get("name_zh")) == _norm_str(cand.get("name_zh"))
        and _norm_str(new_meta.get("name_en")) == _norm_str(cand.get("name_en"))
        and _norm_data_type(new_meta.get("data_type"))
        == _norm_data_type(cand.get("data_type"))
    )


def _match_names_only(new_meta: Dict[str, Any], cand: Dict[str, Any]) -> bool:
    """
    检查 name_zh 和 name_en 是否都匹配（data_type 不匹配）
    """
    return (
        _norm_str(new_meta.get("name_zh")) == _norm_str(cand.get("name_zh"))
        and _norm_str(new_meta.get("name_en")) == _norm_str(cand.get("name_en"))
        and _norm_data_type(new_meta.get("data_type"))
        != _norm_data_type(cand.get("data_type"))
    )


def _match_partial_name(new_meta: Dict[str, Any], cand: Dict[str, Any]) -> bool:
    """
    检查是否只有 name_zh 或 name_en 其中一个匹配
    """
    name_zh_match = _norm_str(new_meta.get("name_zh")) == _norm_str(cand.get("name_zh"))
    name_en_match = _norm_str(new_meta.get("name_en")) == _norm_str(cand.get("name_en"))

    # 只有一个匹配（异或）
    return (name_zh_match or name_en_match) and not (name_zh_match and name_en_match)


def _diff_fields(new_meta: Dict[str, Any], cand: Dict[str, Any]) -> List[str]:
    diffs: List[str] = []
    if _norm_str(new_meta.get("name_zh")) != _norm_str(cand.get("name_zh")):
        diffs.append("name_zh")
    if _norm_str(new_meta.get("name_en")) != _norm_str(cand.get("name_en")):
        diffs.append("name_en")
    if _norm_data_type(new_meta.get("data_type")) != _norm_data_type(
        cand.get("data_type")
    ):
        diffs.append("data_type")
    if sorted(new_meta.get("tag_ids") or []) != sorted(cand.get("tag_ids") or []):
        diffs.append("tag_ids")
    return diffs


def _find_candidate_metas(
    session,
    name_zh: str,
    name_en: str,
    limit: int = 20,
) -> List[Dict[str, Any]]:
    name_zh = _norm_str(name_zh)
    name_en = _norm_str(name_en)
    if not name_zh and not name_en:
        return []

    cypher = """
    MATCH (m:DataMeta)
    WHERE ($name_zh <> '' AND m.name_zh = $name_zh)
       OR ($name_en <> '' AND m.name_en = $name_en)
    RETURN id(m) as id, m as m
    LIMIT $limit
    """
    result = session.run(
        cypher,
        {"name_zh": name_zh, "name_en": name_en, "limit": int(limit)},
    )
    candidates: List[Dict[str, Any]] = []
    for record in result:
        meta_id = int(record["id"])
        m_node = record.get("m")
        props = serialize_node_properties(m_node) if m_node else {}
        candidates.append(
            {
                "id": meta_id,
                "name_zh": props.get("name_zh", ""),
                "name_en": props.get("name_en", ""),
                "data_type": props.get("data_type", ""),
                "status": props.get("status", True),
                "tag_ids": _get_meta_tag_ids(session, meta_id),
            }
        )
    return candidates


def _write_review_record(
    record_type: str,
    business_domain_id: Optional[int],
    new_meta: Dict[str, Any],
    candidates: Optional[List[Dict[str, Any]]] = None,
    old_meta: Optional[Dict[str, Any]] = None,
    source: str = "ddl",
) -> None:
    review = MetadataReviewRecord()
    review.record_type = record_type
    review.source = source
    review.business_domain_id = business_domain_id
    review.new_meta = new_meta
    review.candidates = candidates or []
    review.old_meta = old_meta
    review.status = "pending"
    review.created_at = now_china_naive()
    review.updated_at = now_china_naive()
    db.session.add(review)


def _create_new_meta_and_link(
    session,
    domain_id: int,
    new_meta: Dict[str, Any],
    alias_name_zh: Optional[str] = None,
    alias_name_en: Optional[str] = None,
) -> int:
    """
    创建新的 DataMeta 节点并建立 BusinessDomain-[:INCLUDES]->DataMeta 关系。
    总是创建新节点，不检查是否已存在。
    """
    from app.core.meta_data import get_formatted_time

    meta_create = """
    CREATE (m:DataMeta {
        uid: $uid,
        name_zh: $name_zh,
        name_en: $name_en,
        create_time: $create_time,
        data_type: $data_type,
        status: true
    })
    RETURN m
    """
    meta_properties = {
        "name_zh": _norm_str(new_meta.get("name_zh")),
        "name_en": _norm_str(new_meta.get("name_en")),
        "create_time": get_formatted_time(),
        "data_type": _norm_data_type(new_meta.get("data_type") or "varchar(255)"),
    }
    ensure_governance_uid(meta_properties)
    meta_result = session.run(
        meta_create,
        meta_properties,
    ).single()
    if not meta_result or not meta_result.get("m"):
        raise ValueError("创建 DataMeta 失败")
    meta_id = int(meta_result["m"].id)

    # 标签关系（若提供 tag_ids）
    tag_ids = sorted({int(t) for t in (new_meta.get("tag_ids") or []) if t is not None})
    if tag_ids:
        tag_rel = """
        MATCH (m:DataMeta)
        WHERE id(m) = $meta_id
        WITH m
        UNWIND $tag_ids AS tid
        MATCH (t:DataLabel) WHERE id(t) = tid
        MERGE (m)-[:LABEL]->(t)
        """
        session.run(tag_rel, {"meta_id": meta_id, "tag_ids": tag_ids})

    # 建立 INCLUDES 关系（可选写入别名信息）
    if alias_name_zh or alias_name_en:
        rel_cypher = """
        MATCH (n:BusinessDomain), (m:DataMeta)
        WHERE id(n) = $domain_id AND id(m) = $meta_id
        MERGE (n)-[r:INCLUDES]->(m)
        SET r.alias_name_zh = $alias_name_zh,
            r.alias_name_en = $alias_name_en
        RETURN r
        """
        session.run(
            rel_cypher,
            {
                "domain_id": int(domain_id),
                "meta_id": meta_id,
                "alias_name_zh": _norm_str(alias_name_zh),
                "alias_name_en": _norm_str(alias_name_en),
            },
        )
    else:
        rel_cypher = """
        MATCH (n:BusinessDomain), (m:DataMeta)
        WHERE id(n) = $domain_id AND id(m) = $meta_id
        MERGE (n)-[:INCLUDES]->(m)
        """
        session.run(rel_cypher, {"domain_id": int(domain_id), "meta_id": meta_id})

    return meta_id


def _link_existing_meta(
    session,
    domain_id: int,
    meta_id: int,
    alias_name_zh: Optional[str] = None,
    alias_name_en: Optional[str] = None,
) -> None:
    """
    将已存在的 DataMeta 节点关联到 BusinessDomain。
    """
    # 建立 INCLUDES 关系（可选写入别名信息）
    if alias_name_zh or alias_name_en:
        rel_cypher = """
        MATCH (n:BusinessDomain), (m:DataMeta)
        WHERE id(n) = $domain_id AND id(m) = $meta_id
        MERGE (n)-[r:INCLUDES]->(m)
        SET r.alias_name_zh = $alias_name_zh,
            r.alias_name_en = $alias_name_en
        RETURN r
        """
        session.run(
            rel_cypher,
            {
                "domain_id": int(domain_id),
                "meta_id": int(meta_id),
                "alias_name_zh": _norm_str(alias_name_zh),
                "alias_name_en": _norm_str(alias_name_en),
            },
        )
    else:
        rel_cypher = """
        MATCH (n:BusinessDomain), (m:DataMeta)
        WHERE id(n) = $domain_id AND id(m) = $meta_id
        MERGE (n)-[:INCLUDES]->(m)
        """
        session.run(rel_cypher, {"domain_id": int(domain_id), "meta_id": int(meta_id)})


def _create_meta_if_absent_and_link(
    session,
    domain_id: int,
    new_meta: Dict[str, Any],
    alias_name_zh: Optional[str] = None,
    alias_name_en: Optional[str] = None,
) -> int:
    """
    创建 DataMeta（若不存在）并建立 BusinessDomain-[:INCLUDES]->DataMeta 关系。
    这里使用 MERGE 但不在 ON MATCH 覆盖 data_type，避免静默覆盖。
    """
    from app.core.meta_data import get_formatted_time

    meta_merge = """
    MERGE (m:DataMeta {name_zh: $name_zh})
    ON CREATE SET m.uid = $uid,
                m.name_en = $name_en,
                m.create_time = $create_time,
                m.data_type = $data_type,
                m.status = true
    ON MATCH SET m.uid = coalesce(m.uid, $uid)
    RETURN m
    """
    meta_properties = {
        "name_zh": _norm_str(new_meta.get("name_zh")),
        "name_en": _norm_str(new_meta.get("name_en")),
        "create_time": get_formatted_time(),
        "data_type": _norm_data_type(new_meta.get("data_type") or "varchar(255)"),
    }
    ensure_governance_uid(meta_properties)
    meta_result = session.run(
        meta_merge,
        meta_properties,
    ).single()
    if not meta_result or not meta_result.get("m"):
        raise ValueError("创建/获取 DataMeta 失败")
    meta_id = int(meta_result["m"].id)

    # 标签关系（若提供 tag_ids）
    tag_ids = sorted({int(t) for t in (new_meta.get("tag_ids") or []) if t is not None})
    if tag_ids:
        tag_rel = """
        MATCH (m:DataMeta)
        WHERE id(m) = $meta_id
        WITH m
        UNWIND $tag_ids AS tid
        MATCH (t:DataLabel) WHERE id(t) = tid
        MERGE (m)-[:LABEL]->(t)
        """
        session.run(tag_rel, {"meta_id": meta_id, "tag_ids": tag_ids})

    # 建立 INCLUDES 关系（可选写入别名信息）
    if alias_name_zh or alias_name_en:
        rel_cypher = """
        MATCH (n:BusinessDomain), (m:DataMeta)
        WHERE id(n) = $domain_id AND id(m) = $meta_id
        MERGE (n)-[r:INCLUDES]->(m)
        SET r.alias_name_zh = $alias_name_zh,
            r.alias_name_en = $alias_name_en
        RETURN r
        """
        session.run(
            rel_cypher,
            {
                "domain_id": int(domain_id),
                "meta_id": meta_id,
                "alias_name_zh": _norm_str(alias_name_zh),
                "alias_name_en": _norm_str(alias_name_en),
            },
        )
    else:
        rel_cypher = """
        MATCH (n:BusinessDomain), (m:DataMeta)
        WHERE id(n) = $domain_id AND id(m) = $meta_id
        MERGE (n)-[:INCLUDES]->(m)
        """
        session.run(rel_cypher, {"domain_id": int(domain_id), "meta_id": meta_id})

    return meta_id


def serialize_neo4j_object(obj):
    """
    将Neo4j对象转换为可JSON序列化的格式

    Args:
        obj: Neo4j节点或属性值

    Returns:
        序列化后的对象
    """
    if hasattr(obj, "year"):  # DateTime对象
        if hasattr(obj, "strftime"):
            return obj.strftime("%Y-%m-%d %H:%M:%S")
        return str(obj)
    elif hasattr(obj, "__dict__"):  # 复杂对象
        return str(obj)
    else:
        return obj


def serialize_node_properties(node):
    """
    将Neo4j节点属性序列化为可JSON化的字典

    Args:
        node: Neo4j节点对象

    Returns:
        dict: 序列化后的属性字典
    """
    properties = {}
    for key, value in dict(node).items():
        properties[key] = serialize_neo4j_object(value)
    return properties


def normalize_tag_inputs(tag_data):
    """
    将传入的标签数据统一为去重后的标签ID列表

    支持的输入格式:
        - 单个ID（int/str数字）
        - ID列表
        - 标签字典或字典列表，需包含id字段
    """
    tag_ids = []

    if tag_data is None:
        return tag_ids

    items = tag_data if isinstance(tag_data, list) else [tag_data]

    for item in items:
        candidate = item.get("id") if isinstance(item, dict) else item

        if candidate is None:
            continue

        try:
            tag_id = int(candidate)
            tag_ids.append(tag_id)
        except (ValueError, TypeError):
            logger.warning(f"标签值无法转换为ID: {item}")

    # 去重并保持顺序
    unique_ids = []
    for tid in tag_ids:
        if tid not in unique_ids:
            unique_ids.append(tid)
    return unique_ids


def extract_tag_filters(tag_filter):
    """
    将筛选条件转换为可用于 Cypher 的标签ID与名称列表
    """
    tag_ids = normalize_tag_inputs(tag_filter)
    tag_names = []

    if tag_filter is None:
        return tag_ids, tag_names

    candidates = tag_filter if isinstance(tag_filter, list) else [tag_filter]
    for item in candidates:
        name = None
        if isinstance(item, dict):
            name = item.get("name_zh") or item.get("name_en")
        elif isinstance(item, str) and not item.isdigit():
            name = item

        if name:
            tag_names.append(name)

    # 去重保持顺序
    tag_names = list(dict.fromkeys(tag_names))
    return tag_ids, tag_names


def get_tags_for_domain(session, domain_id_int):
    """
    获取业务领域关联的标签信息列表
    """
    tag_cypher = """
    MATCH (n:BusinessDomain)-[:LABEL]->(t:DataLabel)
    WHERE id(n) = $domain_id
    RETURN t
    """
    tag_result = session.run(tag_cypher, {"domain_id": domain_id_int})

    tags = []
    for tag_record in tag_result:
        tag_node = serialize_node_properties(tag_record["t"])
        tags.append(
            {
                "id": tag_record["t"].id,
                "name_zh": tag_node.get("name_zh"),
                "name_en": tag_node.get("name_en"),
            }
        )

    return tags


def business_domain_list(
    page,
    page_size,
    name_en_filter=None,
    name_zh_filter=None,
    type_filter="all",
    category_filter=None,
    tag_filter=None,
):
    """
    获取业务领域列表

    Args:
        page: 当前页码
        page_size: 每页大小
        name_en_filter: 英文名称过滤条件
        name_zh_filter: 中文名称过滤条件
        type_filter: 类型过滤条件，默认'all'表示不过滤
        category_filter: 分类过滤条件
        tag_filter: 标签过滤条件

    Returns:
        tuple: (业务领域列表, 总数量)
    """
    try:
        with neo4j_driver.get_session() as session:
            params = {}
            # 构建基础过滤条件（针对BusinessDomain节点）
            domain_conditions = []

            if name_en_filter:
                domain_conditions.append("n.name_en CONTAINS $name_en_filter")
                params["name_en_filter"] = name_en_filter

            if name_zh_filter:
                domain_conditions.append("n.name_zh CONTAINS $name_zh_filter")
                params["name_zh_filter"] = name_zh_filter

            if type_filter and type_filter != "all":
                domain_conditions.append("n.type = $type_filter")
                params["type_filter"] = type_filter

            if category_filter:
                domain_conditions.append("n.category = $category_filter")
                params["category_filter"] = category_filter

            domain_where_clause = (
                "WHERE " + " AND ".join(domain_conditions) if domain_conditions else ""
            )

            # 处理标签筛选，支持ID列表或名称列表
            tag_ids, tag_names = extract_tag_filters(tag_filter)
            tag_conditions = []
            if tag_ids:
                tag_conditions.append("id(t) IN $tag_ids")
                params["tag_ids"] = tag_ids
            if tag_names:
                tag_conditions.append(
                    "(t.name_zh IN $tag_names OR t.name_en IN $tag_names)"
                )
                params["tag_names"] = tag_names
            tag_where_clause = (
                "WHERE " + " OR ".join(tag_conditions) if tag_conditions else ""
            )

            # 分页参数
            skip = (page - 1) * page_size
            params["skip"] = skip
            params["limit"] = page_size

            # 根据是否有tag_filter选择不同的查询策略
            if tag_conditions:
                count_cypher = f"""
                MATCH (n:BusinessDomain)
                {domain_where_clause}
                WITH DISTINCT n
                MATCH (n)-[:LABEL]->(t:DataLabel)
                {tag_where_clause}
                RETURN count(DISTINCT n) as count
                """

                cypher = f"""
                MATCH (n:BusinessDomain)
                {domain_where_clause}
                WITH DISTINCT n
                MATCH (n)-[:LABEL]->(t:DataLabel)
                {tag_where_clause}
                RETURN DISTINCT n
                ORDER BY n.create_time DESC
                SKIP $skip LIMIT $limit
                """
            else:
                count_cypher = f"""
                MATCH (n:BusinessDomain)
                {domain_where_clause}
                RETURN count(n) as count
                """

                cypher = f"""
                MATCH (n:BusinessDomain)
                {domain_where_clause}
                RETURN n
                ORDER BY n.create_time DESC
                SKIP $skip LIMIT $limit
                """

            # 执行计数查询
            count_result = session.run(count_cypher, params)
            count_record = count_result.single()
            total_count = count_record["count"] if count_record else 0

            # 执行分页查询
            result = session.run(cypher, params)

            # 格式化结果
            domains = []
            for record in result:
                node = serialize_node_properties(record["n"])
                node["id"] = record["n"].id

                # 查询关联的标签（返回列表）
                tags = get_tags_for_domain(session, node["id"])
                node["tag"] = tags

                domains.append(node)

            logger.info(f"成功获取业务领域列表，共 {total_count} 条记录")
            return domains, total_count

    except Exception as e:
        logger.error(f"获取业务领域列表失败: {str(e)}")
        return [], 0


def get_business_domain_by_id(domain_id):
    """
    根据ID获取业务领域详情

    Args:
        domain_id: 业务领域节点ID

    Returns:
        dict: 业务领域详情，如果不存在返回None
    """
    try:
        with neo4j_driver.get_session() as session:
            # 确保domain_id为整数
            try:
                domain_id_int = int(domain_id)
            except (ValueError, TypeError):
                logger.error(f"业务领域ID不是有效的整数: {domain_id}")
                return None

            # 查询业务领域节点
            cypher = """
            MATCH (n:BusinessDomain)
            WHERE id(n) = $domain_id
            RETURN n
            """
            result = session.run(cypher, {"domain_id": domain_id_int})
            record = result.single()

            if not record:
                logger.error(f"未找到业务领域，ID: {domain_id_int}")
                return None

            # 构建返回数据
            domain_data = serialize_node_properties(record["n"])
            domain_data["id"] = record["n"].id

            # 查询关联的标签，返回列表
            tags = get_tags_for_domain(session, domain_id_int)
            domain_data["tag"] = tags

            # 查询关联的数据源（COME_FROM关系）
            data_source_cypher = """
            MATCH (n:BusinessDomain)-[r:COME_FROM]->(ds:DataSource)
            WHERE id(n) = $domain_id
            RETURN ds
            """
            data_source_result = session.run(
                data_source_cypher, {"domain_id": domain_id_int}
            )
            data_source_record = data_source_result.single()

            # 设置数据源信息
            if data_source_record:
                domain_data["data_source"] = data_source_record["ds"].id
                logger.info(f"找到关联的数据源，ID: {data_source_record['ds'].id}")
            else:
                domain_data["data_source"] = None

            # 查询关联的元数据
            meta_cypher = """
            MATCH (n:BusinessDomain)-[:INCLUDES]->(m)
            WHERE id(n) = $domain_id
            AND (m:DataMeta OR m:Metadata)
            RETURN m
            """
            meta_result = session.run(meta_cypher, {"domain_id": domain_id_int})

            parsed_data = []
            for meta_record in meta_result:
                meta = serialize_node_properties(meta_record["m"])
                meta_data = {
                    "id": meta_record["m"].id,
                    "name_zh": meta.get("name_zh"),
                    "name_en": meta.get("name_en"),
                    "data_type": meta.get("data_type"),
                    "data_standard": {"name_zh": None, "id": None},
                }
                parsed_data.append(meta_data)

            domain_data["parsed_data"] = parsed_data

            # 确保所有必需字段都有默认值
            required_fields = {
                "leader": "",
                "organization": "",
                "name_zh": "",
                "name_en": "",
                "data_sensitivity": "",
                "storage_location": "/",
                "create_time": "",
                "update_time": "",
                "type": "",
                "category": "",
                "url": "",
                "frequency": "",
                "status": True,
                "keywords": [],
                "describe": "",
            }

            for field, default_value in required_fields.items():
                if field not in domain_data or domain_data[field] is None:
                    domain_data[field] = default_value

            logger.info(f"成功获取业务领域详情，ID: {domain_id_int}")
            return domain_data

    except Exception as e:
        logger.error(f"获取业务领域详情失败: {str(e)}")
        return None


def delete_business_domain(domain_id):
    """
    删除业务领域节点及其关系

    Args:
        domain_id: 业务领域节点ID

    Returns:
        bool: 删除是否成功
    """
    try:
        with neo4j_driver.get_session() as session:
            # 确保domain_id为整数
            try:
                domain_id_int = int(domain_id)
            except (ValueError, TypeError):
                logger.error(f"业务领域ID不是有效的整数: {domain_id}")
                return False

            # 删除业务领域节点及其关系
            cypher = """
            MATCH (n:BusinessDomain)
            WHERE id(n) = $domain_id
            DETACH DELETE n
            """

            session.run(cypher, domain_id=domain_id_int)

            logger.info(f"成功删除业务领域，ID: {domain_id_int}")
            return True

    except Exception as e:
        logger.error(f"删除业务领域失败: {str(e)}")
        return False


def save_business_domain(data):
    """
    保存业务领域节点（新建或更新）

    Args:
        data: 包含业务领域信息的字典
            - id: 业务领域节点ID（可选，有则更新，无则新建）
            - name_zh: 中文名称（必填）
            - name_en: 英文名称（必填）
            - describe: 描述（可选）
            - type: 类型（可选）
            - category: 分类（可选）
            - tag: 标签列表（JSON数组，元素包含 id/name_zh/name_en 或 id，可选）
            - data_source: 数据源ID（可选）

    Returns:
        dict: 保存后的业务领域数据，失败时抛出异常
    """
    from app.core.meta_data import get_formatted_time

    # 如果有id，调用更新逻辑
    if data.get("id"):
        return update_business_domain(data)

    # 新建逻辑
    try:
        name_zh = data.get("name_zh")
        name_en = data.get("name_en")

        if not name_zh or not name_en:
            raise ValueError("缺少必填字段: name_zh 或 name_en")

        with neo4j_driver.get_session() as session:
            # 检查是否存在相同 name_en 的 BusinessDomain 节点
            check_duplicate_cypher = """
            MATCH (bd:BusinessDomain)
            WHERE bd.name_en = $name_en
            RETURN id(bd) as id, bd.name_zh as name_zh, bd.name_en as name_en
            LIMIT 1
            """
            duplicate_result = session.run(
                check_duplicate_cypher, {"name_en": name_en}
            ).single()

            if duplicate_result:
                logger.warning(
                    f"创建业务领域失败: 存在相同name_en的节点, "
                    f"name_en={name_en}, existing_id={duplicate_result['id']}"
                )
                return {
                    "success": False,
                    "message": "当前业务域存在相同英文名的节点，请重新编辑命名中英文后再进行提交。",
                    "existing_node": {
                        "id": duplicate_result["id"],
                        "name_zh": duplicate_result["name_zh"],
                        "name_en": duplicate_result["name_en"],
                    },
                }

            # 构建节点属性
            node_props = {
                "name_zh": name_zh,
                "name_en": name_en,
                "create_time": get_formatted_time(),
                "update_time": get_formatted_time(),
            }
            ensure_governance_uid(node_props)

            # 添加可选字段（不包含 parsed_data，它通过关系处理）
            optional_fields = [
                "describe",
                "type",
                "category",
                "leader",
                "organization",
                "status",
                "keywords",
                "data_sensitivity",
                "frequency",
                "url",
                "storage_location",
            ]
            for field in optional_fields:
                if data.get(field) is not None:
                    node_props[field] = data[field]

            # 构建CREATE语句
            props_str = ", ".join([f"{k}: ${k}" for k in node_props])
            cypher = f"""
            CREATE (n:BusinessDomain {{{props_str}}})
            RETURN n
            """
            result = session.run(cypher, node_props)  # type: ignore[arg-type]
            created_node = result.single()

            if not created_node:
                raise ValueError("创建业务领域节点失败")

            domain_id = created_node["n"].id
            logger.info(f"成功创建业务领域节点，ID: {domain_id}")

            # 处理 parsed_data：严格比对，避免静默覆盖；疑似冗余写入PG审核表
            parsed_data = data.get("parsed_data")
            if parsed_data and isinstance(parsed_data, list):
                from app.core.meta_data import get_formatted_time

                created_or_linked_count = 0
                review_count = 0

                for item in parsed_data:
                    if not isinstance(item, dict):
                        continue

                    new_meta = _build_new_meta_snapshot(item)
                    if not new_meta.get("name_zh"):
                        continue

                    # 查找候选 metadata
                    candidates = _find_candidate_metas(
                        session,
                        new_meta.get("name_zh", ""),
                        new_meta.get("name_en", ""),
                    )

                    # 场景1: name_zh、name_en、data_type 都匹配
                    name_type_match = None
                    for cand in candidates:
                        if _match_name_and_type(new_meta, cand):
                            name_type_match = cand
                            break

                    if name_type_match:
                        # 检查 status 字段
                        matched_id = name_type_match.get("id")
                        if matched_id is None:
                            logger.warning(
                                f"匹配到的metadata缺少id字段: {name_type_match}"
                            )
                            continue

                        if name_type_match.get("status", True):
                            # status=true: 直接关联已存在的 metadata
                            _link_existing_meta(
                                session=session,
                                domain_id=domain_id,
                                meta_id=int(matched_id),
                            )
                            created_or_linked_count += 1
                            logger.info(
                                f"关联已存在的metadata (status=true): "
                                f"id={matched_id}, "
                                f"name_zh={name_type_match.get('name_zh')}"
                            )
                        else:
                            # status=false: 创建新的 metadata，并写入审核表
                            new_meta_id = _create_new_meta_and_link(
                                session=session,
                                domain_id=domain_id,
                                new_meta=new_meta,
                            )
                            created_or_linked_count += 1

                            # 写入审核表
                            _write_review_record(
                                record_type="redundancy",
                                business_domain_id=domain_id,
                                new_meta={
                                    **new_meta,
                                    "id": new_meta_id,
                                },
                                candidates=[
                                    {
                                        "candidate_meta_id": name_type_match.get("id"),
                                        "snapshot": name_type_match,
                                        "diff_fields": ["status"],
                                    }
                                ],
                                old_meta=None,
                                source="ddl",
                            )
                            review_count += 1
                            logger.info(
                                f"创建新metadata (旧status=false): "
                                f"new_id={new_meta_id}, "
                                f"old_id={name_type_match.get('id')}"
                            )
                        continue

                    # 场景2: name_zh、name_en 都匹配，但 data_type 不匹配
                    names_match = None
                    for cand in candidates:
                        if _match_names_only(new_meta, cand):
                            names_match = cand
                            break

                    if names_match:
                        # 创建新的 metadata，并写入审核表
                        new_meta_id = _create_new_meta_and_link(
                            session=session,
                            domain_id=domain_id,
                            new_meta=new_meta,
                        )
                        created_or_linked_count += 1

                        # 写入审核表
                        _write_review_record(
                            record_type="redundancy",
                            business_domain_id=domain_id,
                            new_meta={
                                **new_meta,
                                "id": new_meta_id,
                            },
                            candidates=[
                                {
                                    "candidate_meta_id": names_match.get("id"),
                                    "snapshot": names_match,
                                    "diff_fields": _diff_fields(new_meta, names_match),
                                }
                            ],
                            old_meta=None,
                            source="ddl",
                        )
                        review_count += 1
                        logger.info(
                            f"创建新metadata (data_type不匹配): "
                            f"new_id={new_meta_id}, "
                            f"old_id={names_match.get('id')}"
                        )
                        continue

                    # 场景3: 只有 name_zh 或 name_en 其中一个匹配
                    partial_match = None
                    for cand in candidates:
                        if _match_partial_name(new_meta, cand):
                            partial_match = cand
                            break

                    if partial_match:
                        # 创建新的 metadata，并写入审核表
                        new_meta_id = _create_new_meta_and_link(
                            session=session,
                            domain_id=domain_id,
                            new_meta=new_meta,
                        )
                        created_or_linked_count += 1

                        # 写入审核表
                        _write_review_record(
                            record_type="redundancy",
                            business_domain_id=domain_id,
                            new_meta={
                                **new_meta,
                                "id": new_meta_id,
                            },
                            candidates=[
                                {
                                    "candidate_meta_id": partial_match.get("id"),
                                    "snapshot": partial_match,
                                    "diff_fields": _diff_fields(
                                        new_meta, partial_match
                                    ),
                                }
                            ],
                            old_meta=None,
                            source="ddl",
                        )
                        review_count += 1
                        logger.info(
                            f"创建新metadata (部分名称匹配): "
                            f"new_id={new_meta_id}, "
                            f"old_id={partial_match.get('id')}"
                        )
                        continue

                    # 场景4: 没有任何候选 metadata，直接创建新的
                    new_meta_id = _create_new_meta_and_link(
                        session=session,
                        domain_id=domain_id,
                        new_meta=new_meta,
                    )
                    created_or_linked_count += 1
                    logger.info(f"创建新metadata (无候选): new_id={new_meta_id}")

                # 提交PG审核记录
                if review_count > 0:
                    db.session.commit()
                logger.info(
                    f"parsed_data处理完成: linked/created={created_or_linked_count}, "
                    f"review_records={review_count}"
                )

            # 处理标签关系（支持多个标签）
            tag_inputs = data.get("tag")
            tag_ids = normalize_tag_inputs(tag_inputs)
            if tag_ids:
                tag_rel_cypher = """
                MATCH (n:BusinessDomain)
                WHERE id(n) = $domain_id
                WITH n
                UNWIND $tag_ids AS tid
                MATCH (t:DataLabel) WHERE id(t) = tid
                MERGE (n)-[:LABEL]->(t)
                """
                session.run(
                    tag_rel_cypher, {"domain_id": domain_id, "tag_ids": tag_ids}
                )
                logger.info(
                    f"创建业务领域标签关系: domain_id={domain_id}, tag_ids={tag_ids}"
                )

            # 处理数据源关系
            data_source_id = data.get("data_source")
            if data_source_id:
                try:
                    ds_id_int = int(data_source_id)
                    create_ds_rel_cypher = """
                    MATCH (n:BusinessDomain), (ds:DataSource)
                    WHERE id(n) = $domain_id AND id(ds) = $ds_id
                    CREATE (n)-[r:COME_FROM]->(ds)
                    RETURN r
                    """
                    session.run(
                        create_ds_rel_cypher,
                        {"domain_id": domain_id, "ds_id": ds_id_int},
                    )
                    logger.info(
                        f"创建业务领域数据源关系: "
                        f"domain_id={domain_id}, data_source_id={ds_id_int}"
                    )
                except (ValueError, TypeError):
                    logger.warning(f"数据源ID不是有效的整数: {data_source_id}")

            # 构建返回数据
            node_data = serialize_node_properties(created_node["n"])
            node_data["id"] = domain_id
            node_data["tag"] = get_tags_for_domain(session, domain_id)

            logger.info(f"成功保存业务领域，ID: {domain_id}")
            return node_data

    except Exception as e:
        logger.error(f"保存业务领域失败: {str(e)}")
        raise


def update_business_domain(data):
    """
    更新业务领域节点及其关系

    Args:
        data: 包含更新信息的字典，必须包含 id 字段
            - id: 业务领域节点ID（必填）
            - name_zh: 中文名称
            - name_en: 英文名称
            - describe: 描述
            - tag: 标签列表（JSON数组，包含 id/name_zh/name_en 或 id，可选）
            - 其他属性字段...

    Returns:
        dict: 更新后的业务领域数据，失败时抛出异常
    """
    from app.core.meta_data import get_formatted_time

    try:
        domain_id = data.get("id")
        if not domain_id:
            raise ValueError("缺少业务领域ID")

        # 确保domain_id为整数
        try:
            domain_id_int = int(domain_id)
        except (ValueError, TypeError) as err:
            raise ValueError(f"业务领域ID不是有效的整数: {domain_id}") from err

        with neo4j_driver.get_session() as session:
            # 如果更新 name_en，检查是否与其他节点重复（排除自身）
            new_name_en = data.get("name_en")
            if new_name_en:
                check_duplicate_cypher = """
                MATCH (bd:BusinessDomain)
                WHERE bd.name_en = $name_en AND id(bd) <> $domain_id
                RETURN id(bd) as id, bd.name_zh as name_zh, bd.name_en as name_en
                LIMIT 1
                """
                duplicate_result = session.run(
                    check_duplicate_cypher,
                    {"name_en": new_name_en, "domain_id": domain_id_int},
                ).single()

                if duplicate_result:
                    logger.warning(
                        f"更新业务领域失败: 存在相同name_en的其他节点, "
                        f"name_en={new_name_en}, existing_id={duplicate_result['id']}, "
                        f"current_id={domain_id_int}"
                    )
                    return {
                        "success": False,
                        "message": "当前业务域存在相同英文名的节点，请重新编辑命名中英文后再进行提交。",
                        "existing_node": {
                            "id": duplicate_result["id"],
                            "name_zh": duplicate_result["name_zh"],
                            "name_en": duplicate_result["name_en"],
                        },
                    }

            # 构建更新字段（过滤掉特殊字段和 None 值）
            # parsed_data 通过 INCLUDES 关系处理，不存储为节点属性
            update_fields = {}
            excluded = ("id", "tag", "data_source", "parsed_data")
            for key, value in data.items():
                if key not in excluded and value is not None:
                    update_fields[key] = value

            # 添加更新时间
            update_fields["update_time"] = get_formatted_time()

            # 构建更新语句
            if update_fields:
                set_clause = ", ".join([f"n.{k} = ${k}" for k in update_fields])
                cypher = f"""
                MATCH (n:BusinessDomain)
                WHERE id(n) = $domain_id
                SET {set_clause}
                RETURN n
                """
                params = {"domain_id": domain_id_int}
                params.update(update_fields)
                result = session.run(cypher, params)  # type: ignore[arg-type]
            else:
                # 如果没有字段需要更新，只查询节点
                cypher = """
                MATCH (n:BusinessDomain)
                WHERE id(n) = $domain_id
                RETURN n
                """
                result = session.run(cypher, {"domain_id": domain_id_int})

            updated_node = result.single()

            if not updated_node:
                raise ValueError("业务领域不存在")

            logger.info(f"更新业务领域节点属性，ID: {domain_id_int}")

            # 处理标签关系（tag 字段存在于请求数据中时才处理）
            # 先删除旧关系，再根据新值创建关系；null/空值表示清除关系
            if "tag" in data:
                # 删除旧的标签关系
                delete_tag_cypher = """
                MATCH (n:BusinessDomain)-[r:LABEL]->()
                WHERE id(n) = $domain_id
                DELETE r
                """
                session.run(delete_tag_cypher, {"domain_id": domain_id_int})
                logger.info(f"已删除业务领域旧的标签关系，domain_id: {domain_id_int}")

                # 如果 tag 有值，创建新的标签关系
                tag_inputs = data.get("tag")
                if tag_inputs:
                    tag_ids = normalize_tag_inputs(tag_inputs)
                    if tag_ids:
                        create_tag_cypher = """
                        MATCH (n:BusinessDomain)
                        WHERE id(n) = $domain_id
                        WITH n
                        UNWIND $tag_ids AS tid
                        MATCH (t:DataLabel) WHERE id(t) = tid
                        MERGE (n)-[:LABEL]->(t)
                        """
                        session.run(
                            create_tag_cypher,
                            {"domain_id": domain_id_int, "tag_ids": tag_ids},
                        )
                        logger.info(
                            f"创建业务领域新的标签关系: "
                            f"domain_id={domain_id_int}, tag_ids={tag_ids}"
                        )

            # 处理数据源关系（data_source 字段存在于请求数据中时才处理）
            # 先删除旧关系，再根据新值创建关系；null/空值表示清除关系
            if "data_source" in data:
                # 删除旧的数据源关系
                delete_ds_cypher = """
                MATCH (n:BusinessDomain)-[r:COME_FROM]->()
                WHERE id(n) = $domain_id
                DELETE r
                """
                session.run(delete_ds_cypher, {"domain_id": domain_id_int})
                logger.info(f"已删除业务领域旧的数据源关系，domain_id: {domain_id_int}")

                # 如果 data_source 有值，创建新的数据源关系
                data_source_id = data.get("data_source")
                if data_source_id:
                    try:
                        ds_id_int = int(data_source_id)
                        create_ds_cypher = """
                        MATCH (n:BusinessDomain), (ds:DataSource)
                        WHERE id(n) = $domain_id AND id(ds) = $ds_id
                        CREATE (n)-[r:COME_FROM]->(ds)
                        RETURN r
                        """
                        session.run(
                            create_ds_cypher,
                            {"domain_id": domain_id_int, "ds_id": ds_id_int},
                        )
                        logger.info(
                            f"创建业务领域新的数据源关系: "
                            f"domain_id={domain_id_int}, "
                            f"data_source_id={ds_id_int}"
                        )
                    except (ValueError, TypeError):
                        logger.warning(f"数据源ID不是有效的整数: {data_source_id}")

            # 处理元数据关系（parsed_data）：先对比，再按规则写入审核表；不做破坏性删除
            if "parsed_data" in data:
                from app.core.meta_data import get_formatted_time

                # 当前业务领域已关联的 DataMeta
                current_meta_cypher = """
                MATCH (n:BusinessDomain)-[:INCLUDES]->(m:DataMeta)
                WHERE id(n) = $domain_id
                RETURN id(m) as id, m as m
                """
                current_result = session.run(
                    current_meta_cypher, {"domain_id": domain_id_int}
                )
                current_metas: List[Dict[str, Any]] = []
                for rec in current_result:
                    meta_id = int(rec["id"])
                    m_node = rec.get("m")
                    props = serialize_node_properties(m_node) if m_node else {}
                    current_metas.append(
                        {
                            "id": meta_id,
                            "name_zh": props.get("name_zh", ""),
                            "name_en": props.get("name_en", ""),
                            "data_type": props.get("data_type", ""),
                            "tag_ids": _get_meta_tag_ids(session, meta_id),
                        }
                    )

                # 构建便于对齐的索引（优先 name_zh，其次 name_en）
                index_by_name_zh = {
                    _norm_str(m.get("name_zh")): m
                    for m in current_metas
                    if _norm_str(m.get("name_zh"))
                }
                index_by_name_en = {
                    _norm_str(m.get("name_en")): m
                    for m in current_metas
                    if _norm_str(m.get("name_en"))
                }

                parsed_data = data.get("parsed_data") or []
                incoming_items = [it for it in parsed_data if isinstance(it, dict)]
                incoming_keys: set[Tuple[str, str]] = set()

                linked_or_created_count = 0
                review_count = 0

                # 逐条处理新解析结果
                for item in incoming_items:
                    new_meta = _build_new_meta_snapshot(item)
                    if not new_meta.get("name_zh") and not new_meta.get("name_en"):
                        continue

                    key = (
                        _norm_str(new_meta.get("name_zh")),
                        _norm_str(new_meta.get("name_en")),
                    )
                    incoming_keys.add(key)

                    existing = None
                    if _norm_str(new_meta.get("name_zh")) in index_by_name_zh:
                        existing = index_by_name_zh[_norm_str(new_meta.get("name_zh"))]
                    elif _norm_str(new_meta.get("name_en")) in index_by_name_en:
                        existing = index_by_name_en[_norm_str(new_meta.get("name_en"))]

                    if existing:
                        # 若不一致：写入 change 审核记录，并保持现有关联不变
                        if not _is_exact_match(new_meta, existing):
                            _write_review_record(
                                record_type="change",
                                business_domain_id=domain_id_int,
                                new_meta=new_meta,
                                candidates=[],
                                old_meta={
                                    "meta_id": existing.get("id"),
                                    "snapshot": existing,
                                    "diff_fields": _diff_fields(new_meta, existing),
                                },
                                source="ddl",
                            )
                            review_count += 1
                        else:
                            # 确保关系存在
                            existing_id = existing.get("id")
                            if existing_id is not None:
                                session.run(
                                    """
                                    MATCH (n:BusinessDomain), (m:DataMeta)
                                    WHERE id(n) = $domain_id AND id(m) = $meta_id
                                    MERGE (n)-[:INCLUDES]->(m)
                                    """,
                                    {
                                        "domain_id": domain_id_int,
                                        "meta_id": int(existing_id),
                                    },
                                )
                            linked_or_created_count += 1
                        continue

                    # 当前业务领域未关联该字段：走冗余检测/创建逻辑
                    candidates = _find_candidate_metas(
                        session,
                        new_meta.get("name_zh", ""),
                        new_meta.get("name_en", ""),
                    )
                    exact = None
                    for cand in candidates:
                        if _is_exact_match(new_meta, cand):
                            exact = cand
                            break

                    if exact:
                        _create_meta_if_absent_and_link(
                            session=session,
                            domain_id=domain_id_int,
                            new_meta=exact,
                        )
                        linked_or_created_count += 1
                        continue

                    if candidates:
                        candidates_payload = []
                        for cand in candidates:
                            candidates_payload.append(
                                {
                                    "candidate_meta_id": cand.get("id"),
                                    "snapshot": cand,
                                    "diff_fields": _diff_fields(new_meta, cand),
                                }
                            )
                        _write_review_record(
                            record_type="redundancy",
                            business_domain_id=domain_id_int,
                            new_meta=new_meta,
                            candidates=candidates_payload,
                            old_meta=None,
                            source="ddl",
                        )
                        review_count += 1
                        continue

                    # 无候选：创建新 DataMeta 并关联
                    _create_meta_if_absent_and_link(
                        session=session,
                        domain_id=domain_id_int,
                        new_meta={
                            **new_meta,
                            "create_time": get_formatted_time(),
                        },
                    )
                    linked_or_created_count += 1

                # 检测“缺失/删除”：当前关联的 meta 在新解析中不存在 -> 记录 change
                for existing in current_metas:
                    e_key = (
                        _norm_str(existing.get("name_zh")),
                        _norm_str(existing.get("name_en")),
                    )
                    if e_key in incoming_keys:
                        continue
                    _write_review_record(
                        record_type="change",
                        business_domain_id=domain_id_int,
                        new_meta={
                            "action": "missing_in_new",
                            "name_zh": _norm_str(existing.get("name_zh")),
                            "name_en": _norm_str(existing.get("name_en")),
                            "data_type": _norm_data_type(existing.get("data_type")),
                            "tag_ids": sorted(existing.get("tag_ids") or []),
                        },
                        candidates=[],
                        old_meta={
                            "meta_id": existing.get("id"),
                            "snapshot": existing,
                            "diff_fields": ["missing_in_new"],
                        },
                        source="ddl",
                    )
                    review_count += 1

                if review_count > 0:
                    db.session.commit()

                logger.info(
                    f"parsed_data更新处理完成: linked/created={linked_or_created_count}, "
                    f"review_records={review_count}"
                )

            # 构建返回数据
            node_data = serialize_node_properties(updated_node["n"])
            node_data["id"] = updated_node["n"].id
            node_data["tag"] = get_tags_for_domain(session, domain_id_int)

            logger.info(f"成功更新业务领域，ID: {domain_id_int}")
            return node_data

    except Exception as e:
        logger.error(f"更新业务领域失败: {str(e)}")
        raise


def business_domain_graph_all(domain_id, include_meta=True):
    """
    获取业务领域完整关系图谱

    Args:
        domain_id: 业务领域节点ID
        include_meta: 是否包含元数据节点，默认True

    Returns:
        dict: 包含 nodes 和 lines 的图谱数据
    """
    try:
        with neo4j_driver.get_session() as session:
            # 确保domain_id为整数
            try:
                domain_id_int = int(domain_id)
            except (ValueError, TypeError):
                logger.error(f"业务领域ID不是有效的整数: {domain_id}")
                return {"nodes": [], "lines": []}

            # 根据include_meta参数决定是否包含元数据节点
            if include_meta:
                cypher = """
                MATCH path = (n:BusinessDomain)-[*1..1]-(m)
                WHERE id(n) = $domain_id
                RETURN path
                """
            else:
                cypher = """
                MATCH path = (n:BusinessDomain)-[*1..1]-(m)
                WHERE id(n) = $domain_id
                AND NOT (m:DataMeta) AND NOT (m:Metadata)
                RETURN path
                """

            result = session.run(cypher, {"domain_id": domain_id_int})

            # 收集节点和关系
            nodes = {}
            lines = {}

            for record in result:
                path = record["path"]

                # 处理路径中的所有节点
                for node in path.nodes:
                    if node.id not in nodes:
                        node_dict = serialize_node_properties(node)
                        node_dict["id"] = str(node.id)
                        node_dict["node_type"] = (
                            list(node.labels)[0] if node.labels else ""
                        )
                        nodes[node.id] = node_dict

                # 处理路径中的所有关系
                for rel in path.relationships:
                    if rel.id not in lines:
                        rel_dict = {
                            "id": str(rel.id),
                            "from": str(rel.start_node.id),
                            "to": str(rel.end_node.id),
                            "text": rel.type,
                        }
                        lines[rel.id] = rel_dict

            logger.info(
                f"成功获取业务领域图谱，ID: {domain_id_int}, 节点数: {len(nodes)}"
            )
            return {"nodes": list(nodes.values()), "lines": list(lines.values())}
    except Exception as e:
        logger.error(f"获取业务领域图谱失败: {str(e)}")
        return {"nodes": [], "lines": []}


def business_domain_search_list(
    domain_id,
    page,
    page_size,
    name_en_filter=None,
    name_zh_filter=None,
    category_filter=None,
    tag_filter=None,
):
    """
    获取特定业务领域关联的元数据列表

    Args:
        domain_id: 业务领域节点ID
        page: 当前页码
        page_size: 每页大小
        name_en_filter: 英文名称过滤条件
        name_zh_filter: 中文名称过滤条件
        category_filter: 分类过滤条件
        tag_filter: 标签过滤条件

    Returns:
        tuple: (元数据列表, 总数)
    """
    try:
        with neo4j_driver.get_session() as session:
            # 确保domain_id为整数
            try:
                domain_id_int = int(domain_id)
            except (ValueError, TypeError):
                logger.error(f"业务领域ID不是有效的整数: {domain_id}")
                return [], 0

            # 基本匹配语句 - 支持DataMeta和Metadata标签
            match_clause = """
            MATCH (n:BusinessDomain)-[:INCLUDES]->(m)
            WHERE id(n) = $domain_id
            AND (m:DataMeta OR m:Metadata)
            """

            where_conditions = []

            if name_en_filter:
                where_conditions.append(f"m.name_en CONTAINS '{name_en_filter}'")

            if name_zh_filter:
                where_conditions.append(f"m.name_zh CONTAINS '{name_zh_filter}'")

            if category_filter:
                where_conditions.append(f"m.category = '{category_filter}'")

            # 标签过滤需要额外的匹配
            tag_match = ""
            if tag_filter:
                tag_match = (
                    "MATCH (m)-[:HAS_TAG]->(t:Tag) WHERE t.name_zh = $tag_filter"
                )

            where_clause = ""
            if where_conditions:
                where_clause = " AND " + " AND ".join(where_conditions)

            # 计算总数
            count_cypher = f"""
            {match_clause}{where_clause}
            {tag_match}
            RETURN count(m) as count
            """
            count_params = {"domain_id": domain_id_int}
            if tag_filter:
                count_params["tag_filter"] = tag_filter

            count_result = session.run(count_cypher, count_params)
            count_record = count_result.single()
            total_count = count_record["count"] if count_record else 0

            # 分页查询
            skip = (page - 1) * page_size
            cypher = f"""
            {match_clause}{where_clause}
            {tag_match}
            RETURN m
            ORDER BY m.name_zh
            SKIP {skip} LIMIT {page_size}
            """

            result = session.run(cypher, count_params)  # type: ignore

            # 格式化结果
            metadata_list = []
            for record in result:
                meta = serialize_node_properties(record["m"])
                meta["id"] = record["m"].id
                metadata_list.append(meta)

            logger.info(
                f"成功获取业务领域关联元数据，ID: {domain_id_int}, "
                f"元数据数量: {total_count}"
            )
            return metadata_list, total_count
    except Exception as e:
        logger.error(f"获取业务领域关联的元数据列表失败: {str(e)}")
        return [], 0


def business_domain_compose(data):
    """
    从已有业务领域中组合创建新的业务领域

    Args:
        data: 包含业务领域信息的字典
            - name_zh: 中文名称（必填）
            - name_en: 英文名称（可选，不提供则自动翻译）
            - id_list: 选中的元数据ID列表（必填）
                格式: [id1, id2, ...] 或 [{"id": id1}, {"id": id2}, ...]
            - meta_ids: 由路由层预处理后的元数据ID列表（内部使用）
            - describe: 描述（可选）
            - type: 类型（可选）
            - category: 分类（可选）
            - tag: 标签列表（JSON数组，元素包含 id/name_zh/name_en 或 id，可选）
            - data_source: 数据源ID（可选）

    Returns:
        dict: 创建后的业务领域数据
    """
    from app.core.meta_data import get_formatted_time, translate_and_parse

    try:
        name_zh = data.get("name_zh")
        if not name_zh:
            raise ValueError("缺少必填字段: name_zh")

        id_list = data.get("id_list")
        if not id_list:
            raise ValueError("缺少必填字段: id_list")

        # 获取或翻译 name_en
        name_en = data.get("name_en")
        if not name_en:
            translated = translate_and_parse(name_zh)
            name_en = translated[0] if translated else name_zh

        with neo4j_driver.get_session() as session:
            # 检查是否存在相同 name_en 的 BusinessDomain 节点
            check_duplicate_cypher = """
            MATCH (bd:BusinessDomain)
            WHERE bd.name_en = $name_en
            RETURN id(bd) as id, bd.name_zh as name_zh, bd.name_en as name_en
            LIMIT 1
            """
            duplicate_result = session.run(
                check_duplicate_cypher, {"name_en": name_en}
            ).single()

            if duplicate_result:
                logger.warning(
                    f"组合创建业务领域失败: 存在相同name_en的节点, "
                    f"name_en={name_en}, existing_id={duplicate_result['id']}"
                )
                return {
                    "success": False,
                    "message": "当前业务域存在相同英文名的节点，请重新编辑命名中英文后再进行提交。",
                    "existing_node": {
                        "id": duplicate_result["id"],
                        "name_zh": duplicate_result["name_zh"],
                        "name_en": duplicate_result["name_en"],
                    },
                }

            # 构建节点属性
            node_props = {
                "name_zh": name_zh,
                "name_en": name_en,
                "create_time": get_formatted_time(),
                "update_time": get_formatted_time(),
            }
            ensure_governance_uid(node_props)

            # 添加可选字段
            optional_fields = [
                "describe",
                "type",
                "category",
                "leader",
                "organization",
                "status",
                "keywords",
                "data_sensitivity",
                "frequency",
                "url",
                "storage_location",
            ]
            for field in optional_fields:
                if data.get(field) is not None:
                    node_props[field] = data[field]

            # 注意: parsed_data 通过 INCLUDES 关系处理，不存储为节点属性

            # 构建CREATE语句
            props_str = ", ".join([f"{k}: ${k}" for k in node_props])
            cypher = f"""
            CREATE (n:BusinessDomain {{{props_str}}})
            RETURN n
            """
            result = session.run(cypher, node_props)  # type: ignore
            created_node = result.single()

            if not created_node:
                raise ValueError("创建业务领域节点失败")

            domain_id = created_node["n"].id
            logger.info(f"成功创建业务领域节点，ID: {domain_id}")

            # 获取元数据ID列表（优先使用路由层预处理的meta_ids）
            meta_ids = data.get("meta_ids", [])

            # 如果没有预处理的meta_ids，兼容旧格式进行提取
            if not meta_ids and id_list:
                for item in id_list:
                    if isinstance(item, int):
                        meta_ids.append(item)
                    elif isinstance(item, dict) and "id" in item:
                        meta_ids.append(item["id"])

            # 创建与 DataMeta 的关系
            if meta_ids:
                meta_cypher = """
                MATCH (source:BusinessDomain), (target:DataMeta)
                WHERE id(source) = $source_id AND id(target) IN $target_ids
                MERGE (source)-[:INCLUDES]->(target)
                """
                session.run(meta_cypher, source_id=domain_id, target_ids=meta_ids)
                logger.info(
                    f"创建 BusinessDomain -> DataMeta 关系: "
                    f"domain_id={domain_id}, meta_ids={meta_ids}"
                )

            # 处理标签关系（支持多个标签）
            tag_inputs = data.get("tag")
            tag_ids = normalize_tag_inputs(tag_inputs)
            if tag_ids:
                tag_cypher = """
                MATCH (n:BusinessDomain)
                WHERE id(n) = $domain_id
                WITH n
                UNWIND $tag_ids AS tid
                MATCH (t:DataLabel) WHERE id(t) = tid
                MERGE (n)-[:LABEL]->(t)
                """
                session.run(tag_cypher, {"domain_id": domain_id, "tag_ids": tag_ids})
                logger.info(
                    f"创建业务领域标签关系: domain_id={domain_id}, tag_ids={tag_ids}"
                )

            # 处理数据源关系
            data_source_id = data.get("data_source")
            if data_source_id:
                try:
                    ds_id_int = int(data_source_id)
                    ds_cypher = """
                    MATCH (n:BusinessDomain), (ds:DataSource)
                    WHERE id(n) = $domain_id AND id(ds) = $ds_id
                    CREATE (n)-[r:COME_FROM]->(ds)
                    RETURN r
                    """
                    session.run(ds_cypher, {"domain_id": domain_id, "ds_id": ds_id_int})
                    logger.info(
                        f"创建业务领域数据源关系: "
                        f"domain_id={domain_id}, ds_id={ds_id_int}"
                    )
                except (ValueError, TypeError):
                    logger.warning(f"数据源ID不是有效的整数: {data_source_id}")

            # 构建返回数据
            node_data = serialize_node_properties(created_node["n"])
            node_data["id"] = domain_id
            node_data["tag"] = get_tags_for_domain(session, domain_id)

            logger.info(f"成功组合创建新业务领域，ID: {domain_id}")
            return node_data

    except Exception as e:
        logger.error(f"组合创建业务领域失败: {str(e)}")
        raise


def business_domain_label_list(
    page,
    page_size,
    name_en_filter=None,
    name_zh_filter=None,
    category_filter=None,
    group_filter=None,
):
    """
    获取数据标签列表（用于业务领域关联）

    Args:
        page: 当前页码
        page_size: 每页大小
        name_en_filter: 英文名称过滤条件
        name_zh_filter: 中文名称过滤条件
        category_filter: 分类过滤条件
        group_filter: 分组过滤条件

    Returns:
        tuple: (标签列表, 总数)
    """
    try:
        with neo4j_driver.get_session() as session:
            # 构建查询条件
            where_conditions = []
            params = {}

            if name_zh_filter:
                where_conditions.append("n.name_zh CONTAINS $name_zh")
                params["name_zh"] = name_zh_filter

            if name_en_filter:
                where_conditions.append("n.name_en CONTAINS $name_en")
                params["name_en"] = name_en_filter

            if category_filter:
                where_conditions.append("n.category CONTAINS $category")
                params["category"] = category_filter

            if group_filter:
                where_conditions.append("n.group CONTAINS $group")
                params["group"] = group_filter

            # 构建WHERE子句
            where_clause = ""
            if where_conditions:
                where_clause = "WHERE " + " AND ".join(where_conditions)

            # 计算分页
            skip_count = (page - 1) * page_size
            params["skip_count"] = skip_count
            params["page_size"] = page_size

            # 查询标签列表，包含关系数量统计
            cypher = f"""
            MATCH (n:DataLabel)
            {where_clause}
            WITH n, id(n) as nodeid
            OPTIONAL MATCH (n)<-[r]-()
            WITH n, nodeid, count(r) as incoming
            OPTIONAL MATCH (n)-[r]->()
            WITH n, nodeid, incoming, count(r) as outgoing
            RETURN n, nodeid, incoming + outgoing as relationship_count
            ORDER BY n.create_time DESC
            SKIP $skip_count
            LIMIT $page_size
            """

            result = session.run(cypher, params)

            # 格式化结果
            label_list = []
            for record in result:
                label_data = serialize_node_properties(record["n"])
                label_data["id"] = record["nodeid"]
                label_data["number"] = record["relationship_count"]
                # 确保关键字段存在
                if "describe" not in label_data:
                    label_data["describe"] = None
                if "scope" not in label_data:
                    label_data["scope"] = None
                label_list.append(label_data)

            # 查询总数
            count_cypher = f"""
            MATCH (n:DataLabel)
            {where_clause}
            RETURN count(n) as total
            """
            # 移除分页参数用于计数查询
            count_params = {
                k: v for k, v in params.items() if k not in ("skip_count", "page_size")
            }
            count_result = session.run(count_cypher, count_params)
            count_record = count_result.single()
            total_count = count_record["total"] if count_record else 0

            logger.info(f"成功获取标签列表，总数: {total_count}")
            return label_list, total_count

    except Exception as e:
        logger.error(f"获取标签列表失败: {str(e)}")
        return [], 0
