"""
元数据冗余检测辅助函数

提供元数据新增/更新时的疑似冗余检测逻辑，
与 business_domain 模块共享相同的比对规则。
"""

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

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 normalize_tag_inputs(tag_data: Any) -> List[int]:
    """
    将各种形式的标签输入统一为 int 列表
    支持: [1,2,3], [{"id":1},{"id":2}], 1, {"id":1}
    """
    if tag_data is None:
        return []
    if isinstance(tag_data, int):
        return [tag_data]
    if isinstance(tag_data, dict):
        tid = tag_data.get("id")
        if tid is not None:
            with contextlib.suppress(TypeError, ValueError):
                return [int(tid)]
        return []
    if isinstance(tag_data, (list, tuple)):
        result = []
        for item in tag_data:
            if isinstance(item, int):
                result.append(item)
            elif isinstance(item, dict):
                tid = item.get("id")
                if tid is not None:
                    with contextlib.suppress(TypeError, ValueError):
                        result.append(int(tid))
            else:
                with contextlib.suppress(TypeError, ValueError):
                    result.append(int(item))
        return result
    return []


def _get_meta_tag_ids(session, meta_id: int) -> List[int]:
    """获取 DataMeta 节点关联的所有标签 ID"""
    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 serialize_node_properties(node) -> Dict[str, Any]:
    """将 Neo4j 节点属性序列化为字典"""
    if node is None:
        return {}
    return dict(node)


def build_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 = normalize_tag_inputs(item.get("tag") or item.get("tag_ids") or [])
    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 get_existing_meta_snapshot(session, meta_id: int) -> Dict[str, Any]:
    """
    获取已存在的 DataMeta 节点快照
    """
    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", ""),
        "tag_ids": _get_meta_tag_ids(session, int(meta_id)),
    }


def is_exact_match(new_meta: Dict[str, Any], cand: Dict[str, Any]) -> bool:
    """
    严格比对：name_zh, name_en, data_type, tag_ids 全部相同
    """
    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 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,
    exclude_id: Optional[int] = None,
    limit: int = 20,
) -> List[Dict[str, Any]]:
    """
    根据 name_zh 或 name_en 查找可能重复的 DataMeta 节点

    Args:
        session: Neo4j session
        name_zh: 中文名
        name_en: 英文名
        exclude_id: 排除的节点ID（用于更新场景，排除自身）
        limit: 最大返回数量
    """
    name_zh = _norm_str(name_zh)
    name_en = _norm_str(name_en)
    if not name_zh and not name_en:
        return []

    if exclude_id is not None:
        cypher = """
        MATCH (m:DataMeta)
        WHERE (($name_zh <> '' AND m.name_zh = $name_zh)
           OR ($name_en <> '' AND m.name_en = $name_en))
          AND id(m) <> $exclude_id
        RETURN id(m) as id, m as m
        LIMIT $limit
        """
        params = {
            "name_zh": name_zh,
            "name_en": name_en,
            "exclude_id": int(exclude_id),
            "limit": int(limit),
        }
    else:
        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
        """
        params = {"name_zh": name_zh, "name_en": name_en, "limit": int(limit)}

    result = session.run(cypher, params)
    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", ""),
                "tag_ids": _get_meta_tag_ids(session, meta_id),
            }
        )
    return candidates


def write_redundancy_review_record(
    new_meta: Dict[str, Any],
    candidates: List[Dict[str, Any]],
    source: str = "api",
) -> None:
    """
    写入疑似冗余审核记录到 PostgreSQL

    Args:
        new_meta: 新元数据快照
        candidates: 疑似重复的候选元数据列表
        source: 来源标识（api / ddl）
    """
    candidates_payload = []
    for cand in candidates:
        candidates_payload.append(
            {
                "candidate_meta_id": cand.get("id"),
                "snapshot": cand,
                "diff_fields": diff_fields(new_meta, cand),
            }
        )

    review = MetadataReviewRecord()
    review.record_type = "redundancy"
    review.source = source
    review.business_domain_id = 0  # 单独新增元数据时无业务领域关联
    review.new_meta = new_meta
    review.candidates = candidates_payload
    review.old_meta = None
    review.status = "pending"
    review.created_at = now_china_naive()
    review.updated_at = now_china_naive()
    db.session.add(review)
    db.session.commit()
    logger.info(f"已创建疑似冗余审核记录: new_meta.name_zh={new_meta.get('name_zh')}")


def write_redundancy_review_record_with_new_id(
    new_meta: Dict[str, Any],
    candidates: List[Dict[str, Any]],
    source: str = "api",
) -> None:
    """
    写入疑似冗余审核记录到 PostgreSQL（新元数据已创建，包含ID）

    与 write_redundancy_review_record 的区别：
    - 此函数用于新元数据节点已创建后的场景
    - new_meta 中包含已创建的节点 ID

    Args:
        new_meta: 新元数据快照（包含已创建的节点ID）
        candidates: 疑似重复的候选元数据列表
        source: 来源标识（api / ddl）
    """
    candidates_payload = []
    for cand in candidates:
        candidates_payload.append(
            {
                "candidate_meta_id": cand.get("id"),
                "snapshot": cand,
                "diff_fields": diff_fields(new_meta, cand),
            }
        )

    review = MetadataReviewRecord()
    review.record_type = "redundancy"
    review.source = source
    review.business_domain_id = 0  # 单独新增元数据时无业务领域关联
    review.new_meta = new_meta  # 包含新创建的节点ID
    review.candidates = candidates_payload
    review.old_meta = None
    review.status = "pending"
    review.created_at = now_china_naive()
    review.updated_at = now_china_naive()
    db.session.add(review)
    db.session.commit()
    logger.info(
        f"已创建疑似冗余审核记录（新节点已创建）: "
        f"new_meta_id={new_meta.get('id')}, name_zh={new_meta.get('name_zh')}"
    )


def check_redundancy_for_add(
    name_zh: str,
    name_en: str,
    data_type: str,
    tag_ids: List[int],
) -> Dict[str, Any]:
    """
    新增元数据时的冗余检测

    注意：此函数只进行检测，不创建审核记录。
    调用方应根据返回结果决定是否创建节点和审核记录。

    Returns:
        {
            "has_exact_match": bool,      # 是否有完全匹配
            "exact_match_id": int|None,   # 完全匹配的节点ID
            "has_candidates": bool,       # 是否有疑似重复
            "candidates": list,           # 疑似重复候选列表
        }
    """
    new_meta = {
        "name_zh": _norm_str(name_zh),
        "name_en": _norm_str(name_en),
        "data_type": _norm_data_type(data_type),
        "tag_ids": sorted(set(tag_ids)),
    }

    with neo4j_driver.get_session() as session:
        candidates = find_candidate_metas(
            session,
            name_zh=new_meta["name_zh"],
            name_en=new_meta["name_en"],
        )

        if not candidates:
            return {
                "has_exact_match": False,
                "exact_match_id": None,
                "has_candidates": False,
                "candidates": [],
            }

        # 检查是否有完全匹配
        for cand in candidates:
            if is_exact_match(new_meta, cand):
                return {
                    "has_exact_match": True,
                    "exact_match_id": cand.get("id"),
                    "has_candidates": True,
                    "candidates": candidates,
                }

        # 有疑似重复但无完全匹配，返回候选列表
        return {
            "has_exact_match": False,
            "exact_match_id": None,
            "has_candidates": True,
            "candidates": candidates,
        }


def check_redundancy_for_update(
    node_id: int,
    name_zh: str,
    name_en: str,
    data_type: str,
    tag_ids: List[int],
) -> Dict[str, Any]:
    """
    更新元数据时的冗余检测（排除自身）

    Returns:
        {
            "has_exact_match": bool,      # 是否有完全匹配（与其他节点）
            "exact_match_id": int|None,   # 完全匹配的节点ID
            "has_candidates": bool,       # 是否有疑似重复
            "candidates": list,           # 疑似重复候选列表
            "review_created": bool,       # 是否已创建审核记录
        }
    """
    new_meta = {
        "name_zh": _norm_str(name_zh),
        "name_en": _norm_str(name_en),
        "data_type": _norm_data_type(data_type),
        "tag_ids": sorted(set(tag_ids)),
    }

    with neo4j_driver.get_session() as session:
        candidates = find_candidate_metas(
            session,
            name_zh=new_meta["name_zh"],
            name_en=new_meta["name_en"],
            exclude_id=node_id,  # 排除自身
        )

        if not candidates:
            return {
                "has_exact_match": False,
                "exact_match_id": None,
                "has_candidates": False,
                "candidates": [],
                "review_created": False,
            }

        # 检查是否有完全匹配
        for cand in candidates:
            if is_exact_match(new_meta, cand):
                return {
                    "has_exact_match": True,
                    "exact_match_id": cand.get("id"),
                    "has_candidates": True,
                    "candidates": candidates,
                    "review_created": False,
                }

        # 有疑似重复但无完全匹配，写入审核记录
        write_redundancy_review_record(new_meta, candidates, source="api")
        return {
            "has_exact_match": False,
            "exact_match_id": None,
            "has_candidates": True,
            "candidates": candidates,
            "review_created": True,
        }
