"""
===========================================
操作图谱检索模块 - operation_graph_search
===========================================
功能：针对"操作"的固定图谱检索接口
     检索路径：舰艇 -> 系统 -> 子系统 -> 设备 -> 操作使用 -> 操作程序 -> 操作项目
===========================================
"""

import json
import os
import logging
import time
from typing import Any, Dict, List, Optional

from dotenv import load_dotenv
from neo4j import AsyncGraphDatabase, GraphDatabase

load_dotenv()

logger = logging.getLogger(__name__)

NEO4J_CONFIG = {
    "uri": os.getenv("NEO4J_URI", "bolt://192.168.0.46:57687"),
    "user": os.getenv("NEO4J_USER", "neo4j"),
    "password": os.getenv("NEO4J_PASSWORD", "zdht123@"),
}


class EmbeddingWrapper:
    """嵌入模型包装器"""
    def embed_documents(self, texts: List[str]) -> List[List[float]]:
        """批量获取嵌入向量（一次性处理所有文本）"""
        from modelsAPI.model_api import OpenaiAPI
        
        if not texts:
            return []
        
        try:
            result = OpenaiAPI.batch_embeddings(texts)
            if result and isinstance(result, list):
                embedding_list = [item.get('embedding') if isinstance(item, dict) else item for item in result]
                if embedding_list and len(embedding_list) == len(texts):
                    return embedding_list
                else:
                    logger.error(f"EmbeddingWrapper: 批量向量化返回数量不匹配，期望: {len(texts)}, 实际: {len(embedding_list) if embedding_list else 0}")
                    return [None] * len(texts)
            else:
                logger.error(f"EmbeddingWrapper: 批量向量化返回格式异常")
                return [None] * len(texts)
        except Exception as e:
            logger.error(f"EmbeddingWrapper: 批量向量化失败: {e}", exc_info=True)
            return [None] * len(texts)


async def _match_node_with_retrieval(
    session,
    label: str,
    node_name: str,
    top_k: int = 10
) -> Optional[Dict[str, Any]]:
    """
    使用 NodeRetrieval 匹配节点
    """
    from graph_search.node_retrieval import NodeRetrieval
    
    uri = NEO4J_CONFIG.get("uri")
    user = NEO4J_CONFIG.get("user")
    password = NEO4J_CONFIG.get("password")
    
    sync_driver = GraphDatabase.driver(uri, auth=(user, password))
    
    try:
        embeddings = EmbeddingWrapper()
        node_retrieval = NodeRetrieval(sync_driver, embeddings)
        
        route_res = [{"label": label, "entity": node_name}]
        retrieved_nodes = await node_retrieval.retrieve_nodes(route_res, top_k=top_k)
        
        if not retrieved_nodes or label not in retrieved_nodes:
            logger.warning(f"NodeRetrieval 未检索到 {label} 节点: {node_name}")
            return None
        
        candidates = retrieved_nodes[label]
        if not candidates:
            return None
        
        logger.info(f"NodeRetrieval 检索到 {len(candidates)} 个 {label} 节点候选")
        
        for i, node in enumerate(candidates):
            node_data = node.get("node", {})
            candidate_name = node_data.get("名称") or node_data.get("name") or node.get("name", "")
            logger.info(f"  候选 {i+1}: 名称={candidate_name}, score={node.get('score', 0.0)}")
        
        best = candidates[0]
        node_data = best.get("node", {})
        best_name = node_data.get("名称") or node_data.get("name") or best.get("name", "")
        
        query = f"""
        MATCH (n:{label})
        WHERE n.名称 = $name OR n.name = $name
        RETURN elementId(n) AS id, n.名称 AS name, labels(n) AS labels, properties(n) AS props
        LIMIT 1
        """
        result = await session.run(query, name=best_name)
        record = await result.single()
        
        if record:
            return {
                "id": record.get("id"),
                "name": record.get("name"),
                "labels": record.get("labels"),
                "props": record.get("props", {}),
                "score": best.get("score", 0.0)
            }
        
        return None
    finally:
        sync_driver.close()


async def operation_graph_search(
    ship_number: str,
    device_name: str,
    operation_item: str,
) -> Dict[str, Any]:
    """
    操作图谱检索接口
    
    Args:
        ship_number: 舰艇舷号
        device_name: 设备名称
        operation_item: 操作项目名称
        
    Returns:
        Dict: 检索结果，包含 success, xxxx.graph 等字段
    """
    start_time = time.time()
    
    if not ship_number or not str(ship_number).strip():
        return {"success": False, "error": "舷号不能为空"}
    if not device_name or not str(device_name).strip():
        return {"success": False, "error": "设备名称不能为空"}
    if not operation_item or not str(operation_item).strip():
        return {"success": False, "error": "操作项目名称不能为空"}

    uri = NEO4J_CONFIG.get("uri")
    user = NEO4J_CONFIG.get("user")
    password = NEO4J_CONFIG.get("password")
    if not uri or not user or not password:
        return {"success": False, "error": "Neo4j 配置不完整，请检查 NEO4J_CONFIG"}

    try:
        async with AsyncGraphDatabase.driver(uri, auth=(user, password)) as driver:
            async with driver.session() as session:
                ship_key = str(ship_number).strip()
                device_key = str(device_name).strip()
                operation_key = str(operation_item).strip()

                # ========== 1) 匹配舰艇节点 ==========
                ship_query = """
                MATCH (s:舰艇)
                WHERE s.舷号 = $ship_key OR s.舷号 CONTAINS $ship_key OR $ship_key CONTAINS s.舷号
                RETURN elementId(s) AS id, s.名称 AS name, s.舷号 AS ship_number, labels(s) AS labels, properties(s) AS props
                LIMIT 1
                """
                result = await session.run(ship_query, ship_key=ship_key)
                ship_record = await result.single()
                
                if not ship_record:
                    return {"success": False, "error": "在图谱中未找到对应舷号的舰艇节点"}
                
                ship_id = ship_record.get("id")
                ship_name = ship_record.get("name") or ship_record.get("ship_number")
                logger.info(f"匹配到舰艇节点: {ship_name}, id={ship_id}")

                # ========== 2) 匹配设备节点 ==========
                device_query = """
                MATCH (ship:舰艇)
                WHERE elementId(ship) = $ship_id
                MATCH (ship)-[:包含*0..8]->(d:设备)
                WHERE d.名称 CONTAINS $device_key OR $device_key CONTAINS d.名称
                   OR d.name CONTAINS $device_key OR $device_key CONTAINS d.name
                RETURN elementId(d) AS id, d.名称 AS name, labels(d) AS labels, properties(d) AS props
                LIMIT 1
                """
                result = await session.run(device_query, ship_id=ship_id, device_key=device_key)
                device_record = await result.single()
                
                if not device_record:
                    device_node = await _match_node_with_retrieval(session, "设备", device_key)
                    if device_node:
                        check_query = """
                        MATCH (ship:舰艇)
                        WHERE elementId(ship) = $ship_id
                        MATCH (ship)-[:包含*0..8]->(d:设备)
                        WHERE elementId(d) = $device_id
                        RETURN true AS in_range
                        LIMIT 1
                        """
                        check_result = await session.run(check_query, ship_id=ship_id, device_id=device_node["id"])
                        check_record = await check_result.single()
                        if check_record:
                            device_record = device_node
                    
                    if not device_record:
                        return {"success": False, "error": "在该舰艇范围内未找到匹配的设备节点"}
                
                device_id = device_record.get("id")
                device_name_found = device_record.get("name")
                logger.info(f"匹配到设备节点: {device_name_found}, id={device_id}")

                # ========== 3) 匹配操作项目节点 ==========
                op_item_query = """
                MATCH (d:设备)
                WHERE elementId(d) = $device_id
                MATCH (d)-[:包含*0..8]-(real_d:设备)
                MATCH (real_d)-[:设备支持操作操作使用]-(op_use:操作使用)
                      -[:使用]-(op_proc:操作程序)
                      -[:操作时使用]-(op_item:操作项目)
                WHERE op_item.名称 CONTAINS $op_key OR $op_key CONTAINS op_item.名称
                RETURN elementId(op_item) AS id, op_item.名称 AS name, labels(op_item) AS labels, properties(op_item) AS props
                LIMIT 1
                """
                result = await session.run(op_item_query, device_id=device_id, op_key=operation_key)
                op_item_record = await result.single()
                
                if not op_item_record:
                    logger.info(f"Cypher查询未找到操作项目，尝试 NodeRetrieval...")
                    op_item_node = await _match_node_with_retrieval(session, "操作项目", operation_key)
                    if op_item_node:
                        logger.info(f"NodeRetrieval 找到操作项目节点: id={op_item_node.get('id')}, name={op_item_node.get('name')}")
                        check_query = """
                        MATCH (d:设备)
                        WHERE elementId(d) = $device_id
                        MATCH (d)-[:包含*0..8]-(real_d:设备)
                        MATCH (real_d)-[:支持操作]-(op_use:操作使用)
                              -[:使用]-(op_proc:操作程序)
                              -[:操作时使用]-(op_item:操作项目)
                        WHERE elementId(op_item) = $op_item_id
                        RETURN true AS in_range
                        LIMIT 1
                        """
                        check_result = await session.run(check_query, device_id=device_id, op_item_id=op_item_node["id"])
                        check_record = await check_result.single()
                        if check_record:
                            logger.info(f"操作项目节点在设备路径下验证通过")
                            op_item_record = op_item_node
                        else:
                            logger.warning(f"操作项目节点不在设备路径下，尝试直接使用...")
                            op_item_record = op_item_node
                    
                    if not op_item_record:
                        return {"success": False, "error": "未找到匹配的操作项目节点"}
                
                op_item_id = op_item_record.get("id")
                op_item_name = op_item_record.get("name")
                logger.info(f"匹配到操作项目节点: {op_item_name}, id={op_item_id}")

                # ========== 4) 查找经过这三个节点的链路 ==========
                exclude_keys = {
                    "last_updated",
                    "created_at",
                    "knowledge_source",
                    "fulltext",
                    "embedding",
                }

                path_query = """
                MATCH (ship:舰艇)
                WHERE elementId(ship) = $ship_id
                MATCH (device:设备)
                WHERE elementId(device) = $device_id
                MATCH (op_item:操作项目)
                WHERE elementId(op_item) = $op_item_id
                
                MATCH path1 = (ship)-[:包含*0..8]->(device)
                MATCH path2 = (device)-[:包含*0..8]-(real_d:设备)
                      -[:支持操作]->(op_use:操作使用)
                      -[:使用]->(op_proc:操作程序)
                      -[:操作时使用]->(op_item)
                
                WITH path1, path2
                WITH nodes(path1) + nodes(path2) AS all_nodes,
                     relationships(path1) + relationships(path2) AS all_rels
                RETURN all_nodes, all_rels
                LIMIT 1
                """
                result = await session.run(path_query, ship_id=ship_id, device_id=device_id, op_item_id=op_item_id)
                path_record = await result.single()
                
                if not path_record:
                    return {"success": False, "error": "未找到连接这三个节点的路径"}

                # ========== 5) 格式化输出 ==========
                all_nodes = path_record.get("all_nodes", [])
                all_rels = path_record.get("all_rels", [])
                
                nodes_g: List[Dict[str, Any]] = []
                node_id_set = set()
                
                for node in all_nodes:
                    if node is None:
                        continue
                    nid = node.element_id if hasattr(node, 'element_id') else node.id
                    if nid in node_id_set:
                        continue
                    node_id_set.add(nid)
                    
                    props = dict(node) if hasattr(node, '__iter__') else {}
                    props_filtered = {k: v for k, v in props.items() if k not in exclude_keys}
                    
                    name = props.get("名称") or props.get("name") or str(nid)
                    labels = list(node.labels) if hasattr(node, 'labels') else []
                    
                    nodes_g.append({
                        "id": nid,
                        "name": name,
                        "labels": labels,
                        "properties": props_filtered,
                    })
                
                links_g: List[Dict[str, Any]] = []
                for rel in all_rels:
                    if rel is None:
                        continue
                    src = rel.start_node.element_id if hasattr(rel.start_node, 'element_id') else rel.start_node.id
                    tgt = rel.end_node.element_id if hasattr(rel.end_node, 'element_id') else rel.end_node.id
                    
                    if src not in node_id_set or tgt not in node_id_set:
                        continue
                    
                    links_g.append({
                        "id": rel.element_id if hasattr(rel, 'element_id') else rel.id,
                        "label": rel.type if hasattr(rel, 'type') else str(rel.type),
                        "source": src,
                        "target": tgt,
                    })

                # ========== 6) 生成 results 文本 ==========
                op_item_props = op_item_record.get("props", {})
                op_info = {k: v for k, v in op_item_props.items() if k not in exclude_keys and v}
                results_text = json.dumps(op_info, ensure_ascii=False) if op_info else ""

                elapsed_ms = (time.time() - start_time) * 1000
                logger.info(f"[时间统计] 操作图谱检索完成，耗时: {elapsed_ms:.2f}ms")

                graph_item = {"nodes": nodes_g, "links": links_g, "results": results_text}
                return {"success": True, "xxxx.graph": [graph_item]}

    except Exception as e:
        logger.error(f"操作图谱检索失败: {e}", exc_info=True)
        return {"success": False, "error": f"Neo4j 查询失败: {str(e)}"}
