package com.agent.management.rag.capability;

import com.agent.management.rag.bridge.RagAiBridgeClient;
import com.agent.management.rag.model.RagSourceType;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

@Service
@RequiredArgsConstructor
public class RagGovernanceService {
    private final RagCapabilityProfileService profiles;
    private final RagAiBridgeClient bridge;
    private final RagSemanticCatalogService semanticCatalog;
    private final GraphBusinessSubgraphSelector subgraphs;

    public Map<String,Object> suggest(RagSourceType type, String sourceId, String description) {
        RagCapabilityProfile profile = profiles.refresh(type, sourceId);
        String schema;
        if (type == RagSourceType.STRUCTURED_DATA) {
            List<String> tables = semanticCatalog.rank(description, profile, "table:", 12);
            schema = profile.sqlSchema().toDdl(tables);
        } else {
            schema = subgraphs.select(description, profile, profile.graphSchema(), 16, 20).toPromptText();
        }
        Map<String,Object> request = new LinkedHashMap<>();
        request.put("sourceType", type.name()); request.put("schemaText", schema);
        request.put("sourceDescription", description == null ? "" : description); request.put("maxExamples", 8);
        Map<String,Object> suggestion;
        try {
            suggestion = normalize(type, bridge.suggestGovernance(request), profile);
            suggestion.put("generator", "LLM");
        } catch (Exception error) {
            suggestion = fallback(profile, description);
            suggestion.put("generator", "SEMANTIC_FALLBACK");
            suggestion.put("warning", "模型建议不可用，已按语义目录生成可审核草稿：" + shortMessage(error));
        }
        suggestion.put("profileVersion", profile.version()); suggestion.put("sourceType", type.name());
        suggestion.put("sourceId", sourceId);
        return suggestion;
    }

    private Map<String,Object> normalize(RagSourceType type, Map<String,Object> raw, RagCapabilityProfile profile) {
        Map<String,Object> config = new LinkedHashMap<>();
        config.put("retrievalMode", "AUTO_GENERATE");
        config.put("generationRules", rules(raw.get("businessRules"), raw.get("rules")));
        if (type == RagSourceType.STRUCTURED_DATA) {
            config.put("allowTextToSql", true);
            var actualTables = new java.util.HashSet<>(profile.sqlSchema().tables().stream().map(SqlSchemaSnapshot.TableSchema::name).toList());
            config.put("allowedTables", strings(raw.containsKey("allowedTables") ? raw.get("allowedTables") : raw.get("tableWhitelist"))
                    .stream().filter(actualTables::contains).toList());
            config.put("maxRows", 100);
        } else {
            config.put("allowTextToCypher", true);
            List<String> labels = strings(raw.get("allowedLabels")).stream().filter(profile.graphSchema().labelNames()::contains).toList();
            List<String> relationships = strings(raw.get("allowedRelationships")).stream().filter(profile.graphSchema().relationshipTypeNames()::contains).toList();
            config.put("allowedLabels", labels);
            config.put("allowedRelationships", relationships);
            config.put("allowedProperties", validProperties(raw.get("allowedProperties"), profile, labels, relationships));
            config.put("maxDepth", 3);
        }
        Map<String,Object> result = new LinkedHashMap<>();
        result.put("summary", String.valueOf(raw.getOrDefault("summary", "基于实际 Schema 生成的授权草稿")));
        result.put("suggestedConfig", config);
        result.put("examples", raw.get("examples") instanceof List<?> list ? list : List.of());
        return result;
    }

    private static List<String> rules(Object primary, Object secondary) {
        List<String> values = strings(primary);
        return values.isEmpty() ? strings(secondary) : values;
    }

    private static List<String> strings(Object value) {
        if (value instanceof List<?> list) return list.stream().map(String::valueOf).map(String::trim).filter(item -> !item.isEmpty()).toList();
        if (value == null) return List.of();
        return java.util.Arrays.stream(String.valueOf(value).split("[\\r\\n;；]+"))
                .map(String::trim).filter(item -> !item.isEmpty()).toList();
    }

    private static Map<String,List<String>> validProperties(Object value, RagCapabilityProfile profile,
                                                             List<String> labels, List<String> relationships) {
        if (!(value instanceof Map<?,?> raw)) return Map.of();
        Map<String,List<String>> actual = new LinkedHashMap<>();
        profile.graphSchema().nodes().forEach(node -> actual.put(node.label(), node.properties().keySet().stream().toList()));
        profile.graphSchema().relationships().forEach(rel -> actual.put(rel.type(), rel.properties().keySet().stream().toList()));
        Map<String,List<String>> result = new LinkedHashMap<>();
        raw.forEach((owner, properties) -> {
            String name = String.valueOf(owner);
            if ((!labels.contains(name) && !relationships.contains(name)) || !actual.containsKey(name)) return;
            result.put(name, strings(properties).stream().filter(actual.get(name)::contains).toList());
        });
        return result;
    }

    private Map<String,Object> fallback(RagCapabilityProfile profile, String description) {
        Map<String,Object> result = new LinkedHashMap<>();
        Map<String,Object> config = new LinkedHashMap<>();
        config.put("retrievalMode", "AUTO_GENERATE");
        config.put("generationRules", List.of("仅生成只读查询", "只使用已审核的 Schema 元素", "结果必须限制返回规模"));
        if (profile.sourceType() == RagSourceType.STRUCTURED_DATA) {
            List<String> tables = semanticCatalog.rank(description, profile, "table:", 8);
            if (tables.isEmpty()) tables = profile.sqlSchema().tables().stream().limit(8).map(SqlSchemaSnapshot.TableSchema::name).toList();
            config.put("allowTextToSql", true); config.put("allowedTables", tables); config.put("maxRows", 100);
            result.put("examples", tables.stream().limit(4).map(table -> Map.of("question", "查询 " + table + " 的主要记录", "sql", "SELECT * FROM " + table + " LIMIT 20")).toList());
        } else {
            List<String> labels = semanticCatalog.rank(description, profile, "label:", 12);
            List<String> relationships = semanticCatalog.rank(description, profile, "relationship:", 12);
            if (labels.isEmpty()) labels = profile.graphSchema().labelNames().stream().limit(12).toList();
            List<String> selectedLabels = labels;
            if (relationships.isEmpty()) relationships = profile.graphSchema().relationships().stream()
                    .filter(rel -> rel.startLabels().stream().anyMatch(selectedLabels::contains) || rel.endLabels().stream().anyMatch(selectedLabels::contains))
                    .limit(12).map(com.agent.management.rag.graph.GraphSchemaSnapshot.RelationshipSchema::type).distinct().toList();
            var subgraph = profile.graphSchema().filter(labels, relationships, Map.of());
            Map<String,List<String>> properties = new LinkedHashMap<>();
            subgraph.nodes().forEach(node -> properties.put(node.label(), node.properties().keySet().stream().toList()));
            subgraph.relationships().forEach(rel -> properties.put(rel.type(), rel.properties().keySet().stream().toList()));
            config.put("allowTextToCypher", true); config.put("allowedLabels", subgraph.labelNames());
            config.put("allowedRelationships", subgraph.relationshipTypeNames()); config.put("allowedProperties", properties); config.put("maxDepth", 3);
            result.put("examples", subgraph.nodes().stream().limit(4).map(node -> Map.of("question", "查询 " + node.label() + " 及其关联信息", "cypher", "MATCH (n:`" + node.label() + "`) RETURN n LIMIT 20")).toList());
        }
        result.put("suggestedConfig", config);
        result.put("summary", "基于实际 Schema 与向量语义相似度生成的授权草稿，应用前需人工审核");
        return result;
    }

    private static String shortMessage(Exception error) {
        String value = error.getMessage();
        return value == null ? error.getClass().getSimpleName() : value.substring(0, Math.min(value.length(), 180));
    }
}
