from typing import Any

from yuxi.utils import logger

from .base import BaseNeo4jAdapter, GraphAdapter, GraphMetadata


class LightRAGGraphAdapter(GraphAdapter):
    """LightRAG 图谱适配器 (LightRAG Graph Adapter)"""

    def __init__(self, config: dict[str, Any] = None):
        super().__init__(config)

        # 使用公共的 Neo4j 适配器，而不是 GraphDatabase
        self._db = BaseNeo4jAdapter()

        # 从配置中获取 kb_id
        self.kb_id = self.config.get("kb_id")

    def _get_metadata(self) -> GraphMetadata:
        """获取 LightRAG 图谱元数据"""
        return GraphMetadata(
            graph_type="lightrag",
            id_field="element_id",
            name_field="entity_id",  # LightRAG 使用 entity_id 存储实体名称
            supports_embedding=False,
            supports_threshold=False,
        )

    async def query_nodes(self, keyword: str, **kwargs) -> dict[str, Any]:
        """查询节点 (Query nodes)"""
        kb_id = kwargs.get("kb_id") or self.kb_id
        limit = kwargs.get("max_nodes", kwargs.get("limit", 50))
        max_depth = kwargs.get("max_depth", 1)  # 默认为 1，以返回边

        # 如果 keyword 为 *，强制 max_depth=1 至少
        if keyword == "*":
            max_depth = max(max_depth, 1)

        query = self._build_cypher_query(keyword, kb_id, limit, max_depth)

        try:
            with self._db.driver.session() as session:
                result = session.run(query, keyword=keyword, kb_id=kb_id, limit=limit)
                return self._process_query_result(result, limit=limit)
        except Exception as e:
            logger.error(f"Neo4j query failed: {e}")
            return {"nodes": [], "edges": []}

    async def get_labels(self) -> list[str]:
        """获取所有标签 (Get all labels)"""
        query = "CALL db.labels()"
        try:
            with self._db.driver.session() as session:
                result = session.run(query)
                return [record["label"] for record in result if not record["label"].startswith("kb_")]
        except Exception as e:
            logger.error(f"Failed to get labels: {e}")
            return []

    async def get_stats(self, **kwargs) -> dict[str, Any]:
        """获取统计信息 (Get statistics)"""
        kb_id = kwargs.get("kb_id") or self.kb_id

        # 安全检查
        if kb_id and not all(c.isalnum() or c == "_" for c in kb_id):
            logger.warning(f"Invalid kb_id format: {kb_id}")
            return {"total_nodes": 0, "total_edges": 0, "entity_types": []}

        if not kb_id:
            # 如果没有 kb_id，可能返回全局统计或空
            return {"total_nodes": 0, "total_edges": 0, "entity_types": []}

        try:
            # 统计节点和边
            query = f"""
            MATCH (n:`{kb_id}`)
            WITH count(n) as node_count
            OPTIONAL MATCH (n:`{kb_id}`)-[r]->(m:`{kb_id}`)
            RETURN node_count, count(r) as edge_count
            """

            # 统计标签分布
            label_query = f"""
            MATCH (n:`{kb_id}`)
            UNWIND labels(n) as label
            WITH label, count(*) as count
            WHERE label <> 'Entity' AND NOT label STARTS WITH 'kb_'
            RETURN label, count
            ORDER BY count DESC
            """

            with self._db.driver.session() as session:
                stats = session.run(query).single()
                label_stats = session.run(label_query)

                entity_types_list = [{"type": record["label"], "count": record["count"]} for record in label_stats]

                return {
                    "total_nodes": stats["node_count"],
                    "total_edges": stats["edge_count"],
                    "entity_types": entity_types_list,
                }
        except Exception as e:
            logger.error(f"Failed to get stats: {e}")
            return {"total_nodes": 0, "total_edges": 0, "entity_types": []}

    def normalize_node(self, raw_node: Any) -> dict[str, Any]:
        """标准化节点格式 (Normalize node format)"""
        if hasattr(raw_node, "element_id"):  # neo4j.graph.Node
            node_id = raw_node.element_id
            labels = list(raw_node.labels)
            properties = dict(raw_node.items())
        elif isinstance(raw_node, dict):
            node_id = raw_node.get("id") or raw_node.get("element_id")
            labels = raw_node.get("labels", [])
            properties = raw_node.get("properties", {})
            if not properties:
                properties = {k: v for k, v in raw_node.items() if k not in ["id", "element_id", "labels"]}
        else:
            return {}

        # 优先使用 entity_id 字段作为 name (LightRAG 风格)，如果不存在则使用 name 字段
        # 很多时候 entity_id 存储了实体名称
        name = properties.get("entity_id", properties.get("name", "Unknown"))

        # 过滤掉 kb_ 开头的标签
        filtered_labels = [label for label in labels if not label.startswith("kb_")]

        # 提取实体类型
        entity_type = "Entity"
        for label in filtered_labels:
            if label != "Entity":
                entity_type = label
                break

        return self._create_standard_node(
            node_id=node_id,
            name=name,
            entity_type=entity_type,
            labels=filtered_labels,
            properties=properties,
            source="neo4j",
        )

    def normalize_edge(self, raw_edge: Any) -> dict[str, Any]:
        """标准化边格式 (Normalize edge format)"""
        logger.info(raw_edge._properties)
        if hasattr(raw_edge, "element_id"):  # neo4j.graph.Relationship
            edge_id = raw_edge.element_id
            edge_type = raw_edge._properties["keywords"] or raw_edge.type
            start_node_id = raw_edge.start_node.element_id if hasattr(raw_edge.start_node, "element_id") else None
            end_node_id = raw_edge.end_node.element_id if hasattr(raw_edge.end_node, "element_id") else None
            properties = dict(raw_edge.items())
        elif isinstance(raw_edge, dict):
            edge_id = raw_edge.get("id")
            edge_type = raw_edge.get("type")
            start_node_id = raw_edge.get("source_id")
            end_node_id = raw_edge.get("target_id")
            properties = raw_edge.get("properties", {})
        else:
            return {}

        return self._create_standard_edge(
            edge_id=edge_id, source_id=start_node_id, target_id=end_node_id, edge_type=edge_type, properties=properties
        )

    def _build_cypher_query(self, keyword: str, kb_id: str = None, limit: int = 50, max_depth: int = 0) -> str:
        """构建 Cypher 查询"""
        # 安全性检查：kb_id 只能包含字母、数字和下划线
        if kb_id:
            if not all(c.isalnum() or c == "_" for c in kb_id):
                logger.warning(f"Invalid kb_id: {kb_id}")
                kb_id = None

        where_clauses = []

        # 确定 MATCH 子句
        if kb_id:
            # 如果提供了 kb_id，直接匹配该标签
            # 这样即使节点没有 Entity 标签也能匹配到
            match_clause = f"MATCH (n:`{kb_id}`)"
        else:
            match_clause = "MATCH (n:Entity)"

        if keyword and keyword != "*":
            # 兼容 LightRAG 格式 (entity_id) 和普通格式 (name)
            where_clauses.append(
                "(toLower(n.name) CONTAINS toLower($keyword) OR toLower(n.entity_id) CONTAINS toLower($keyword))"
            )

        where_str = " AND ".join(where_clauses)
        if where_str:
            where_str = "WHERE " + where_str

        # 如果 max_depth > 0，我们需要扩展查询
        # 为了避免查询过于复杂，我们使用两步法：
        # 1. 找到种子节点
        # 2. 找到这些节点及其周围的关系

        # 步骤 1: 找到种子节点
        # 如果 keyword 是 * 且有 kb_id，我们使用采样逻辑进行随机采样
        # 但这里 query_nodes 主要是为了搜索

        if max_depth > 0:
            # 如果需要扩展，返回子图
            query = f"""
            {match_clause}
            {where_str}
            WITH n LIMIT {limit}

            // 收集种子节点
            WITH collect(n) as seeds

            // 扩展 1 跳 (如果 max_depth >= 1)
            UNWIND seeds as s
            OPTIONAL MATCH (s)-[r1]-(m1)
            // 确保 m1 也在同一个 KB 中 (如果指定了 kb_id)
            {f"WHERE m1:`{kb_id}`" if kb_id else ""}

            WITH seeds, collect(DISTINCT {{h: s, r: r1, t: m1}}) as hop1

            // 扩展 2 跳 (如果 max_depth >= 2)
            // 这里为了简化，只做 1 跳扩展，或者如果需要 2 跳，可以在这里添加
            // 考虑到性能，通常只做 1 跳或者只找种子节点内部的关系

            // 重新整理返回结果
            UNWIND hop1 as triple
            RETURN triple.h as h, triple.r as r, triple.t as t
            LIMIT {limit * 10}
            """

            # 简化版扩展查询：只返回种子节点及其直接连接的边（如果另一端也在 seeds 中，或者不限制）
            # 下面这个查询返回种子节点以及它们之间的关系，加上它们的一跳邻居

            query = f"""
            {match_clause}
            {where_str}
            WITH n LIMIT {limit}

            // 扩展查询：获取 n 和它的邻居
            OPTIONAL MATCH (n)-[r]-(m)
            {f"WHERE m:`{kb_id}`" if kb_id else ""}

            RETURN n, r, m
            """
        else:
            # 仅返回节点
            query = f"""
            {match_clause}
            {where_str}
            RETURN n
            LIMIT {limit}
            """

        return query

    def _build_subgraph_query(self, limit: int, kb_id: str = None) -> str:
        """构建子图查询"""
        # 安全性检查
        if kb_id:
            if not all(c.isalnum() or c == "_" for c in kb_id):
                kb_id = None

        if kb_id:
            match_clause = f"MATCH (n:`{kb_id}`)"
        else:
            match_clause = "MATCH (n:Entity)"

        query = f"""
        {match_clause}
        WITH n LIMIT {limit}
        WITH collect(n) as nodes
        UNWIND nodes as n
        UNWIND nodes as m
        OPTIONAL MATCH (n)-[r]-(m)
        WHERE elementId(n) < elementId(m)
        RETURN n, r, m
        """

        return query

    def _process_query_result(self, result, limit: int = None) -> dict[str, list]:
        """处理查询结果，并限制节点数量不超过 limit"""
        nodes = []
        edges = []
        node_ids = set()
        edge_ids = set()

        for record in result:
            # 检查是否已达到节点限制
            if limit is not None and len(node_ids) >= limit:
                break

            for key in record.keys():
                val = record[key]
                if val is None:
                    continue

                if hasattr(val, "element_id") and hasattr(val, "labels"):  # Node
                    if val.element_id not in node_ids:
                        # 再次检查限制
                        if limit is not None and len(node_ids) >= limit:
                            break
                        nodes.append(self.normalize_node(val))
                        node_ids.add(val.element_id)
                elif hasattr(val, "element_id") and hasattr(val, "start_node"):  # Relationship
                    if val.element_id not in edge_ids:
                        edges.append(self.normalize_edge(val))
                        edge_ids.add(val.element_id)
                elif isinstance(val, list):
                    for item in val:
                        if hasattr(item, "element_id") and hasattr(item, "labels"):
                            if item.element_id not in node_ids:
                                if limit is not None and len(node_ids) >= limit:
                                    break
                                nodes.append(self.normalize_node(item))
                                node_ids.add(item.element_id)

        # 过滤掉引用不存在节点的边
        valid_edges = []
        for edge in edges:
            source_id = edge.get("source_id")
            target_id = edge.get("target_id")
            if source_id in node_ids and target_id in node_ids:
                valid_edges.append(edge)

        return {"nodes": nodes, "edges": valid_edges}
