package com.agent.management.rag.capability;

import com.agent.management.rag.memory.RagExampleMemoryService;
import com.agent.management.rag.model.RagSourceType;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.Locale;

@Service
@RequiredArgsConstructor
public class GraphOnboardingAssessmentService {
    private final RagCapabilityProfileService profiles;
    private final RagExampleMemoryService examples;
    private final com.agent.management.service.GraphSourceService graphSources;

    public Map<String,Object> assess(String sourceId) {
        RagCapabilityProfile profile = profiles.get(RagSourceType.GRAPH, sourceId);
        var schema = profile.graphSchema();
        Set<String> connected = new LinkedHashSet<>();
        schema.relationships().forEach(rel -> { connected.addAll(rel.startLabels()); connected.addAll(rel.endLabels()); });
        List<String> orphanLabels = schema.labelNames().stream().filter(label -> !connected.contains(label)).toList();
        long properties = schema.nodes().stream().mapToLong(node -> node.properties().size()).sum()
                + schema.relationships().stream().mapToLong(rel -> rel.properties().size()).sum();
        long identifierLabels = schema.nodes().stream().filter(node -> node.properties().keySet().stream()
                .anyMatch(GraphOnboardingAssessmentService::isIdentifierProperty)).count();
        long scoreRelationships = schema.relationships().stream().filter(rel -> rel.properties().keySet().stream()
                .anyMatch(GraphOnboardingAssessmentService::isScoreProperty)).count();
        long vectorized = profile.semanticCatalog().stream().filter(item -> !item.vector().isEmpty()).count();
        long verifiedExamples = examples.list(RagSourceType.GRAPH, sourceId).stream().filter(item -> item.isVerified()).count();
        Map<String,Object> metadata = graphSources.discoverMetadata(Long.valueOf(sourceId));
        int score = 0;
        if (!schema.nodes().isEmpty()) score += 20;
        if (!schema.relationships().isEmpty()) score += 20;
        if (properties > 0) score += 15;
        if (identifierLabels > 0) score += 10;
        if (scoreRelationships > 0) score += 5;
        if (vectorized == profile.semanticCatalog().size() && vectorized > 0) score += 20;
        if (orphanLabels.size() <= Math.max(1, schema.nodes().size() / 10)) score += 10;
        score += (int)Math.min(15, verifiedExamples * 3);
        List<String> recommendations = new ArrayList<>();
        if (schema.nodes().isEmpty()) recommendations.add("未发现 Label，请检查连接权限或图数据");
        if (schema.relationships().isEmpty()) recommendations.add("未发现 Relationship，Text-to-Cypher 只能完成单节点查询");
        if (!orphanLabels.isEmpty()) recommendations.add("审核孤立 Label，确认是否缺少关系或应排除：" + String.join(", ", orphanLabels));
        if (properties == 0) recommendations.add("缺少 Property 元数据，实体过滤和属性问答准确率会受限");
        if (identifierLabels == 0) recommendations.add("缺少 id/code 等实体标识属性，陌生问题中的实体名称难以可靠落图");
        if (scoreRelationships == 0) recommendations.add("未发现 score/rank/priority 等关系指标属性，排序/评分类图查询只能交给 Text2Cypher 尝试");
        if (vectorized < profile.semanticCatalog().size()) recommendations.add("语义目录未完整向量化，请检查 Embedding Bridge");
        if (verifiedExamples < 5) recommendations.add("建议为查找、过滤、聚合、路径、多跳五类问法各审核至少一条 Few-shot");
        Map<String,Object> result = new LinkedHashMap<>();
        result.put("readinessScore", Math.min(100, score)); result.put("profileVersion", profile.version());
        result.put("labels", schema.nodes().size()); result.put("relationshipPatterns", schema.relationships().size());
        result.put("relationshipTypes", schema.relationshipTypeNames().size()); result.put("properties", properties);
        result.put("identifierLabels", identifierLabels);
        result.put("scoreRelationships", scoreRelationships);
        result.put("supportedQueryModes", supportedQueryModes(schema, identifierLabels, scoreRelationships));
        result.put("orphanLabels", orphanLabels); result.put("semanticEntries", profile.semanticCatalog().size());
        result.put("vectorizedEntries", vectorized); result.put("verifiedExamples", verifiedExamples);
        result.put("recommendations", recommendations);
        result.put("constraints", metadata.get("constraints")); result.put("indexes", metadata.get("indexes"));
        result.put("labelCounts", metadata.get("labelCounts"));
        result.put("nodeCount", metadata.get("nodeCount"));
        result.put("relationshipCount", metadata.get("relationshipCount"));
        return result;
    }

    private static boolean isIdentifierProperty(String property) {
        String name = property.toLowerCase(Locale.ROOT);
        return name.equals("id") || name.equals("code") || name.equals("identifier")
                || name.endsWith("_id") || name.endsWith("_code");
    }

    private static boolean isScoreProperty(String property) {
        String name = property.toLowerCase(Locale.ROOT);
        return name.equals("score") || name.endsWith("_score") || name.contains("rank")
                || name.contains("priority") || name.contains("rating")
                || name.equals("weight") || name.equals("level");
    }

    private static List<String> supportedQueryModes(com.agent.management.rag.graph.GraphSchemaSnapshot schema,
                                                    long identifierLabels, long scoreRelationships) {
        List<String> result = new ArrayList<>();
        if (identifierLabels > 0) result.add("ENTITY_NEIGHBORHOOD");
        if (!schema.relationships().isEmpty()) result.add("COUNT_RELATED");
        if (schema.nodes().stream().anyMatch(node -> !node.properties().isEmpty())) result.add("PROPERTY_FILTERED_EVIDENCE");
        if (scoreRelationships > 0) result.add("RELATION_RANKING");
        return result;
    }
}
