package com.lingyue.graph.neo4j;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.neo4j.driver.Driver;
import org.neo4j.driver.Result;
import org.neo4j.driver.Session;
import org.neo4j.driver.Value;
import org.neo4j.driver.types.Node;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Repository;

import java.util.*;

/**
 * Neo4j 节点仓库
 * 
 * 提供图节点的 CRUD 操作
 * 
 * @author lingyue
 * @since 2026-01-21
 */
@Slf4j
@Repository
@RequiredArgsConstructor
@ConditionalOnProperty(name = "neo4j.enabled", havingValue = "true")
public class Neo4jNodeRepository {
    
    private final Driver driver;
    
    /**
     * 创建或更新节点
     * 
     * @param nodeId 节点ID
     * @param labels 节点标签列表（如 Entity, Person, Concept）
     * @param properties 节点属性
     * @return 创建的节点ID
     */
    public String createOrUpdateNode(String nodeId, List<String> labels, Map<String, Object> properties) {
        String labelString = String.join(":", labels);
        
        // 使用 MERGE 确保幂等性
        String cypher = String.format(
                "MERGE (n:%s {id: $id}) " +
                "SET n += $props " +
                "RETURN n.id AS id",
                labelString
        );
        
        Map<String, Object> params = new HashMap<>();
        params.put("id", nodeId);
        params.put("props", properties);
        
        try (Session session = driver.session()) {
            Result result = session.run(cypher, params);
            if (result.hasNext()) {
                return result.next().get("id").asString();
            }
        } catch (Exception e) {
            log.error("创建节点失败: nodeId={}, error={}", nodeId, e.getMessage());
            throw new RuntimeException("Neo4j 创建节点失败", e);
        }
        
        return nodeId;
    }
    
    /**
     * 批量创建节点（使用 UNWIND 提高性能）
     * 
     * @param nodes 节点列表，每个节点包含 id, labels, properties
     */
    public void batchCreateNodes(List<Map<String, Object>> nodes) {
        if (nodes == null || nodes.isEmpty()) {
            return;
        }
        
        String cypher = 
                "UNWIND $nodes AS node " +
                "CALL apoc.merge.node(node.labels, {id: node.id}, node.properties) YIELD node AS n " +
                "RETURN count(n) AS count";
        
        // 如果没有 APOC 插件，使用简单版本
        String simpleCypher = 
                "UNWIND $nodes AS node " +
                "MERGE (n:Entity {id: node.id}) " +
                "SET n += node.properties " +
                "RETURN count(n) AS count";
        
        try (Session session = driver.session()) {
            try {
                Result result = session.run(cypher, Map.of("nodes", nodes));
                log.info("批量创建节点完成: count={}", result.single().get("count").asInt());
            } catch (Exception e) {
                // 回退到简单版本
                log.warn("APOC 不可用，使用简单模式");
                Result result = session.run(simpleCypher, Map.of("nodes", nodes));
                log.info("批量创建节点完成: count={}", result.single().get("count").asInt());
            }
        }
    }
    
    /**
     * 根据ID查找节点
     */
    public Map<String, Object> findById(String nodeId) {
        String cypher = "MATCH (n {id: $id}) RETURN n, labels(n) AS labels";
        
        try (Session session = driver.session()) {
            Result result = session.run(cypher, Map.of("id", nodeId));
            if (result.hasNext()) {
                org.neo4j.driver.Record record = result.next();
                Node node = record.get("n").asNode();
                List<String> labels = record.get("labels").asList(Value::asString);
                
                Map<String, Object> nodeMap = new HashMap<>(node.asMap());
                nodeMap.put("_labels", labels);
                return nodeMap;
            }
        }
        
        return null;
    }
    
    /**
     * 根据文档ID查找所有节点
     */
    public List<Map<String, Object>> findByDocumentId(String documentId) {
        String cypher = 
                "MATCH (n) WHERE n.documentId = $documentId " +
                "RETURN n, labels(n) AS labels";
        
        List<Map<String, Object>> nodes = new ArrayList<>();
        
        try (Session session = driver.session()) {
            Result result = session.run(cypher, Map.of("documentId", documentId));
            while (result.hasNext()) {
                org.neo4j.driver.Record record = result.next();
                Node node = record.get("n").asNode();
                List<String> labels = record.get("labels").asList(Value::asString);
                
                Map<String, Object> nodeMap = new HashMap<>(node.asMap());
                nodeMap.put("_labels", labels);
                nodes.add(nodeMap);
            }
        }
        
        return nodes;
    }
    
    /**
     * 根据用户ID查找所有节点
     */
    public List<Map<String, Object>> findByUserId(String userId, int limit) {
        String cypher = 
                "MATCH (n) WHERE n.userId = $userId " +
                "RETURN n, labels(n) AS labels " +
                "LIMIT $limit";
        
        List<Map<String, Object>> nodes = new ArrayList<>();
        
        try (Session session = driver.session()) {
            Result result = session.run(cypher, Map.of("userId", userId, "limit", limit));
            while (result.hasNext()) {
                org.neo4j.driver.Record record = result.next();
                Node node = record.get("n").asNode();
                List<String> labels = record.get("labels").asList(Value::asString);
                
                Map<String, Object> nodeMap = new HashMap<>(node.asMap());
                nodeMap.put("_labels", labels);
                nodes.add(nodeMap);
            }
        }
        
        return nodes;
    }
    
    /**
     * 按类型查找节点
     */
    public List<Map<String, Object>> findByType(String documentId, String type) {
        String cypher = 
                "MATCH (n) WHERE n.documentId = $documentId AND n.type = $type " +
                "RETURN n, labels(n) AS labels";
        
        List<Map<String, Object>> nodes = new ArrayList<>();
        
        try (Session session = driver.session()) {
            Result result = session.run(cypher, Map.of("documentId", documentId, "type", type));
            while (result.hasNext()) {
                org.neo4j.driver.Record record = result.next();
                Node node = record.get("n").asNode();
                List<String> labels = record.get("labels").asList(Value::asString);
                
                Map<String, Object> nodeMap = new HashMap<>(node.asMap());
                nodeMap.put("_labels", labels);
                nodes.add(nodeMap);
            }
        }
        
        return nodes;
    }
    
    /**
     * 搜索节点（按名称模糊匹配）
     */
    public List<Map<String, Object>> searchByName(String keyword, String documentId, int limit) {
        String cypher;
        Map<String, Object> params = new HashMap<>();
        params.put("keyword", "(?i).*" + keyword + ".*");
        params.put("limit", limit);
        
        if (documentId != null && !documentId.isEmpty()) {
            cypher = 
                    "MATCH (n) WHERE n.documentId = $documentId AND n.name =~ $keyword " +
                    "RETURN n, labels(n) AS labels " +
                    "LIMIT $limit";
            params.put("documentId", documentId);
        } else {
            cypher = 
                    "MATCH (n) WHERE n.name =~ $keyword " +
                    "RETURN n, labels(n) AS labels " +
                    "LIMIT $limit";
        }
        
        List<Map<String, Object>> nodes = new ArrayList<>();
        
        try (Session session = driver.session()) {
            Result result = session.run(cypher, params);
            while (result.hasNext()) {
                org.neo4j.driver.Record record = result.next();
                Node node = record.get("n").asNode();
                List<String> labels = record.get("labels").asList(Value::asString);
                
                Map<String, Object> nodeMap = new HashMap<>(node.asMap());
                nodeMap.put("_labels", labels);
                nodes.add(nodeMap);
            }
        }
        
        return nodes;
    }
    
    /**
     * 删除节点
     */
    public void deleteById(String nodeId) {
        String cypher = "MATCH (n {id: $id}) DETACH DELETE n";
        
        try (Session session = driver.session()) {
            session.run(cypher, Map.of("id", nodeId));
            log.info("删除节点: nodeId={}", nodeId);
        }
    }
    
    /**
     * 删除文档的所有节点和关系
     */
    public void deleteByDocumentId(String documentId) {
        String cypher = "MATCH (n) WHERE n.documentId = $documentId DETACH DELETE n";
        
        try (Session session = driver.session()) {
            Result result = session.run(cypher, Map.of("documentId", documentId));
            log.info("删除文档节点: documentId={}", documentId);
        }
    }
    
    /**
     * 获取节点的所有关联节点
     */
    public List<Map<String, Object>> findRelatedNodes(String nodeId) {
        String cypher = 
                "MATCH (n {id: $id})-[r]-(m) " +
                "RETURN m, labels(m) AS labels, type(r) AS relationType, " +
                "       CASE WHEN startNode(r) = n THEN 'outgoing' ELSE 'incoming' END AS direction";
        
        List<Map<String, Object>> nodes = new ArrayList<>();
        
        try (Session session = driver.session()) {
            Result result = session.run(cypher, Map.of("id", nodeId));
            while (result.hasNext()) {
                org.neo4j.driver.Record record = result.next();
                Node node = record.get("m").asNode();
                List<String> labels = record.get("labels").asList(Value::asString);
                String relationType = record.get("relationType").asString();
                String direction = record.get("direction").asString();
                
                Map<String, Object> nodeMap = new HashMap<>(node.asMap());
                nodeMap.put("_labels", labels);
                nodeMap.put("_relationType", relationType);
                nodeMap.put("_direction", direction);
                nodes.add(nodeMap);
            }
        }
        
        return nodes;
    }
    
    /**
     * 统计节点数量
     */
    public long countByDocumentId(String documentId) {
        String cypher = "MATCH (n) WHERE n.documentId = $documentId RETURN count(n) AS count";
        
        try (Session session = driver.session()) {
            Result result = session.run(cypher, Map.of("documentId", documentId));
            if (result.hasNext()) {
                return result.next().get("count").asLong();
            }
        }
        
        return 0;
    }
}
