package com.lingyue.graph.neo4j;

import com.lingyue.graph.dto.KnowledgeGraphDTO;
import com.lingyue.graph.dto.KnowledgeGraphDTO.*;
import com.lingyue.graph.entity.GraphNode;
import com.lingyue.graph.entity.GraphRelation;
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.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

/**
 * Neo4j 图服务
 * 
 * 提供高级图操作功能：
 * - 同步 PostgreSQL 数据到 Neo4j
 * - 图遍历和路径查询
 * - 图分析（中心性、社区检测等）
 * 
 * @author lingyue
 * @since 2026-01-21
 */
@Slf4j
@Service
@RequiredArgsConstructor
@ConditionalOnProperty(name = "neo4j.enabled", havingValue = "true")
public class Neo4jGraphService {
    
    private final Driver driver;
    private final Neo4jNodeRepository nodeRepository;
    private final Neo4jRelationRepository relationRepository;
    
    // 类型映射
    private static final Map<String, String> TYPE_LABELS = Map.ofEntries(
            Map.entry("person", "Person"),
            Map.entry("org", "Organization"),
            Map.entry("loc", "Location"),
            Map.entry("location", "Location"),
            Map.entry("date", "Date"),
            Map.entry("number", "Number"),
            Map.entry("money", "Money"),
            Map.entry("data", "Data"),
            Map.entry("concept", "Concept"),
            Map.entry("device", "Device"),
            Map.entry("term", "Term"),
            Map.entry("entity", "Entity")
    );
    
    private static final Map<String, String> TYPE_ICONS = Map.ofEntries(
            Map.entry("person", "👤"),
            Map.entry("org", "🏢"),
            Map.entry("loc", "📍"),
            Map.entry("location", "📍"),
            Map.entry("date", "📅"),
            Map.entry("number", "🔢"),
            Map.entry("money", "💰"),
            Map.entry("data", "📊"),
            Map.entry("concept", "💡"),
            Map.entry("device", "🔧"),
            Map.entry("term", "📝"),
            Map.entry("entity", "🏷️")
    );
    
    private static final Map<String, String> TYPE_COLORS = Map.ofEntries(
            Map.entry("person", "#1890ff"),
            Map.entry("org", "#52c41a"),
            Map.entry("loc", "#fa8c16"),
            Map.entry("location", "#fa8c16"),
            Map.entry("date", "#722ed1"),
            Map.entry("number", "#13c2c2"),
            Map.entry("money", "#52c41a"),
            Map.entry("data", "#13c2c2"),
            Map.entry("concept", "#722ed1"),
            Map.entry("device", "#eb2f96"),
            Map.entry("term", "#2f54eb"),
            Map.entry("entity", "#1890ff")
    );
    
    // ==================== 同步方法 ====================
    
    /**
     * 同步单个节点到 Neo4j
     */
    public void syncNode(GraphNode node) {
        String type = node.getType() != null ? node.getType().toLowerCase() : "entity";
        String label = TYPE_LABELS.getOrDefault(type, "Entity");
        
        Map<String, Object> properties = new HashMap<>();
        properties.put("id", node.getId());
        properties.put("name", node.getName());
        properties.put("type", type);
        properties.put("value", node.getValue());
        properties.put("documentId", node.getDocumentId());
        properties.put("userId", node.getUserId());
        properties.put("createdAt", node.getCreateTime() != null ? node.getCreateTime().toString() : null);
        
        // 添加位置信息
        if (node.getPosition() instanceof Map) {
            @SuppressWarnings("unchecked")
            Map<String, Object> pos = (Map<String, Object>) node.getPosition();
            properties.put("charStart", pos.get("charStart"));
            properties.put("charEnd", pos.get("charEnd"));
        }
        
        nodeRepository.createOrUpdateNode(node.getId(), List.of("Entity", label), properties);
        log.debug("同步节点到 Neo4j: id={}, name={}", node.getId(), node.getName());
    }
    
    /**
     * 批量同步节点
     */
    public void syncNodes(List<GraphNode> nodes) {
        List<Map<String, Object>> nodeDataList = nodes.stream()
                .map(node -> {
                    String type = node.getType() != null ? node.getType().toLowerCase() : "entity";
                    String label = TYPE_LABELS.getOrDefault(type, "Entity");
                    
                    Map<String, Object> data = new HashMap<>();
                    data.put("id", node.getId());
                    data.put("labels", List.of("Entity", label));
                    
                    Map<String, Object> props = new HashMap<>();
                    props.put("name", node.getName());
                    props.put("type", type);
                    props.put("value", node.getValue());
                    props.put("documentId", node.getDocumentId());
                    props.put("userId", node.getUserId());
                    data.put("properties", props);
                    
                    return data;
                })
                .collect(Collectors.toList());
        
        nodeRepository.batchCreateNodes(nodeDataList);
        log.info("批量同步节点到 Neo4j: count={}", nodes.size());
    }
    
    /**
     * 同步单个关系到 Neo4j
     */
    public void syncRelation(GraphRelation relation) {
        Map<String, Object> properties = new HashMap<>();
        properties.put("id", relation.getId());
        properties.put("relationType", relation.getRelationType());
        properties.put("actionType", relation.getActionType());
        properties.put("orderIndex", relation.getOrderIndex());
        
        String relType = relation.getRelationType() != null ? relation.getRelationType() : "RELATED";
        
        relationRepository.createRelation(
                relation.getFromNodeId(),
                relation.getToNodeId(),
                relType,
                properties
        );
        
        log.debug("同步关系到 Neo4j: from={}, to={}, type={}", 
                relation.getFromNodeId(), relation.getToNodeId(), relType);
    }
    
    /**
     * 批量同步关系
     */
    public void syncRelations(List<GraphRelation> relations) {
        List<Map<String, Object>> relDataList = relations.stream()
                .map(rel -> {
                    Map<String, Object> data = new HashMap<>();
                    data.put("fromId", rel.getFromNodeId());
                    data.put("toId", rel.getToNodeId());
                    data.put("type", rel.getRelationType() != null ? rel.getRelationType() : "RELATED");
                    
                    Map<String, Object> props = new HashMap<>();
                    props.put("id", rel.getId());
                    props.put("actionType", rel.getActionType());
                    props.put("orderIndex", rel.getOrderIndex());
                    data.put("properties", props);
                    
                    return data;
                })
                .collect(Collectors.toList());
        
        relationRepository.batchCreateRelations(relDataList);
        log.info("批量同步关系到 Neo4j: count={}", relations.size());
    }
    
    // ==================== 图查询方法 ====================
    
    /**
     * 获取文档的完整知识图谱（从 Neo4j）
     */
    public KnowledgeGraphDTO getDocumentGraph(String documentId) {
        List<Map<String, Object>> nodes = nodeRepository.findByDocumentId(documentId);
        if (nodes.isEmpty()) {
            return null;
        }
        
        List<Map<String, Object>> relations = relationRepository.findByDocumentId(documentId);
        
        // 转换节点
        List<NodeDTO> nodeDTOs = nodes.stream()
                .map(this::convertToNodeDTO)
                .collect(Collectors.toList());
        
        // 计算关联数量
        Map<String, Integer> relationCounts = new HashMap<>();
        for (Map<String, Object> rel : relations) {
            String fromId = (String) rel.get("fromId");
            String toId = (String) rel.get("toId");
            relationCounts.merge(fromId, 1, Integer::sum);
            relationCounts.merge(toId, 1, Integer::sum);
        }
        
        // 更新节点的关联数量和大小
        for (NodeDTO node : nodeDTOs) {
            int count = relationCounts.getOrDefault(node.getId(), 0);
            node.setRelationCount(count);
            node.setSize(40 + Math.min(count * 5, 30));
        }
        
        // 转换边
        List<EdgeDTO> edgeDTOs = relations.stream()
                .map(rel -> EdgeDTO.builder()
                        .id((String) rel.get("id"))
                        .source((String) rel.get("fromId"))
                        .sourceName((String) rel.get("fromName"))
                        .target((String) rel.get("toId"))
                        .targetName((String) rel.get("toName"))
                        .relationType((String) rel.get("type"))
                        .label((String) rel.get("type"))
                        .weight(1.0)
                        .build())
                .collect(Collectors.toList());
        
        // 统计
        GraphStats stats = GraphStats.builder()
                .totalNodes(nodeDTOs.size())
                .totalEdges(edgeDTOs.size())
                .nodesByType(nodes.stream()
                        .collect(Collectors.groupingBy(
                                n -> (String) n.getOrDefault("type", "other"),
                                Collectors.collectingAndThen(Collectors.counting(), Long::intValue))))
                .edgesByType(relations.stream()
                        .collect(Collectors.groupingBy(
                                r -> (String) r.getOrDefault("type", "RELATED"),
                                Collectors.collectingAndThen(Collectors.counting(), Long::intValue))))
                .build();
        
        return KnowledgeGraphDTO.builder()
                .documentId(documentId)
                .title("标记要素关系图谱")
                .nodes(nodeDTOs)
                .edges(edgeDTOs)
                .stats(stats)
                .build();
    }
    
    /**
     * 查找两节点之间的最短路径
     */
    public List<Map<String, Object>> findShortestPath(String fromNodeId, String toNodeId, int maxDepth) {
        String cypher = 
                "MATCH path = shortestPath((a {id: $fromId})-[*1.." + maxDepth + "]-(b {id: $toId})) " +
                "RETURN [n IN nodes(path) | {id: n.id, name: n.name, type: n.type}] AS nodes, " +
                "       [r IN relationships(path) | {type: type(r)}] AS relations";
        
        List<Map<String, Object>> result = new ArrayList<>();
        
        try (Session session = driver.session()) {
            Result queryResult = session.run(cypher, Map.of("fromId", fromNodeId, "toId", toNodeId));
            if (queryResult.hasNext()) {
                var record = queryResult.next();
                result = record.get("nodes").asList(v -> v.asMap());
            }
        }
        
        return result;
    }
    
    /**
     * 获取节点的 N 跳邻居
     */
    public KnowledgeGraphDTO getNeighborhood(String nodeId, int depth) {
        String cypher = 
                "MATCH path = (center {id: $nodeId})-[*1.." + depth + "]-(neighbor) " +
                "WITH center, neighbor, relationships(path) AS rels " +
                "RETURN DISTINCT neighbor, labels(neighbor) AS labels, " +
                "       [r IN rels | {type: type(r), from: startNode(r).id, to: endNode(r).id}] AS relInfo";
        
        Set<String> nodeIds = new HashSet<>();
        nodeIds.add(nodeId);
        
        List<NodeDTO> nodes = new ArrayList<>();
        List<EdgeDTO> edges = new ArrayList<>();
        Set<String> edgeKeys = new HashSet<>();
        
        // 添加中心节点
        Map<String, Object> centerNode = nodeRepository.findById(nodeId);
        if (centerNode != null) {
            nodes.add(convertToNodeDTO(centerNode));
        }
        
        try (Session session = driver.session()) {
            Result result = session.run(cypher, Map.of("nodeId", nodeId));
            
            while (result.hasNext()) {
                var record = result.next();
                var neighbor = record.get("neighbor").asNode();
                var labels = record.get("labels").asList(v -> v.asString());
                var relInfo = record.get("relInfo").asList(v -> v.asMap());
                
                String neighborId = neighbor.get("id").asString();
                
                if (!nodeIds.contains(neighborId)) {
                    nodeIds.add(neighborId);
                    
                    Map<String, Object> nodeMap = new HashMap<>(neighbor.asMap());
                    nodeMap.put("_labels", labels);
                    nodes.add(convertToNodeDTO(nodeMap));
                }
                
                // 处理关系
                for (var rel : relInfo) {
                    String from = (String) rel.get("from");
                    String to = (String) rel.get("to");
                    String type = (String) rel.get("type");
                    String edgeKey = from + "-" + to + "-" + type;
                    
                    if (!edgeKeys.contains(edgeKey)) {
                        edgeKeys.add(edgeKey);
                        edges.add(EdgeDTO.builder()
                                .id(edgeKey)
                                .source(from)
                                .target(to)
                                .relationType(type)
                                .label(type)
                                .build());
                    }
                }
            }
        }
        
        return KnowledgeGraphDTO.builder()
                .title("邻域图谱")
                .nodes(nodes)
                .edges(edges)
                .stats(GraphStats.builder()
                        .totalNodes(nodes.size())
                        .totalEdges(edges.size())
                        .build())
                .build();
    }
    
    /**
     * 获取高度中心性节点（PageRank）
     * 需要 Graph Data Science 插件
     */
    public List<Map<String, Object>> getTopNodesByPageRank(String documentId, int limit) {
        // 简化版：按关系数量排序
        String cypher = 
                "MATCH (n {documentId: $documentId})-[r]-() " +
                "WITH n, count(r) AS degree " +
                "ORDER BY degree DESC " +
                "LIMIT $limit " +
                "RETURN n.id AS id, n.name AS name, n.type AS type, degree";
        
        List<Map<String, Object>> result = new ArrayList<>();
        
        try (Session session = driver.session()) {
            Result queryResult = session.run(cypher, Map.of("documentId", documentId, "limit", limit));
            while (queryResult.hasNext()) {
                var record = queryResult.next();
                result.add(Map.of(
                        "id", record.get("id").asString(),
                        "name", record.get("name").asString(),
                        "type", record.get("type").asString(),
                        "degree", record.get("degree").asInt()
                ));
            }
        }
        
        return result;
    }
    
    // ==================== 辅助方法 ====================
    
    private NodeDTO convertToNodeDTO(Map<String, Object> nodeMap) {
        String type = (String) nodeMap.getOrDefault("type", "entity");
        if (type == null) type = "entity";
        type = type.toLowerCase();
        
        return NodeDTO.builder()
                .id((String) nodeMap.get("id"))
                .name((String) nodeMap.get("name"))
                .type(type)
                .icon(TYPE_ICONS.getOrDefault(type, "📌"))
                .color(TYPE_COLORS.getOrDefault(type, "#8c8c8c"))
                .size(40)
                .value((String) nodeMap.get("value"))
                .documentIds(nodeMap.get("documentId") != null 
                        ? List.of((String) nodeMap.get("documentId")) 
                        : Collections.emptyList())
                .build();
    }
    
    /**
     * 删除文档的所有图数据
     */
    public void deleteDocumentGraph(String documentId) {
        nodeRepository.deleteByDocumentId(documentId);
        log.info("删除文档图数据: documentId={}", documentId);
    }
    
    /**
     * 初始化图数据库索引
     */
    public void initializeIndexes() {
        String[] indexes = {
                "CREATE INDEX node_id IF NOT EXISTS FOR (n:Entity) ON (n.id)",
                "CREATE INDEX node_document IF NOT EXISTS FOR (n:Entity) ON (n.documentId)",
                "CREATE INDEX node_user IF NOT EXISTS FOR (n:Entity) ON (n.userId)",
                "CREATE INDEX node_name IF NOT EXISTS FOR (n:Entity) ON (n.name)"
        };
        
        try (Session session = driver.session()) {
            for (String index : indexes) {
                try {
                    session.run(index);
                    log.info("创建索引成功: {}", index);
                } catch (Exception e) {
                    log.warn("创建索引失败（可能已存在）: {}", e.getMessage());
                }
            }
        }
    }
}
