package com.agent.management.rag.graph;

import org.springframework.stereotype.Component;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Component
public class GraphSchemaValidator {
    private static final Pattern NODE = Pattern.compile("\\(([A-Za-z_]\\w*)?\\s*(?::\\s*`?([A-Za-z_]\\w*)`?)?\\s*(?:\\{([^}]*)})?\\)");
    private static final Pattern RELATIONSHIP = Pattern.compile("\\[[^]]*?:\\s*`?([A-Za-z_]\\w*)`?([^]]*)]");
    private static final Pattern PROPERTY_REFERENCE = Pattern.compile("\\b([A-Za-z_]\\w*)\\s*\\.\\s*`?([A-Za-z_]\\w*)`?");
    private static final Pattern MAP_PROPERTY = Pattern.compile("(?:^|,)\\s*`?([A-Za-z_]\\w*)`?\\s*:");
    private static final Pattern VARIABLE_LENGTH = Pattern.compile("\\*\\s*(\\d*)\\s*(?:\\.\\.\\s*(\\d*))?");
    private static final Pattern LABEL_PREDICATE = Pattern.compile("\\b([A-Za-z_]\\w*)\\s*:\\s*`?([A-Za-z_]\\w*)`?");

    public void validate(String cypher, GraphSchemaSnapshot schema, int maxDepth) {
        Map<String, String> variables = new HashMap<>();
        Matcher predicateMatcher=LABEL_PREDICATE.matcher(cypher);
        while(predicateMatcher.find()) {
            if (isInsideSquareBrackets(cypher, predicateMatcher.start())) continue;
            String label=predicateMatcher.group(2);
            if(schema.node(label).isEmpty()) throw new IllegalArgumentException("Cypher uses nonexistent or unauthorized label: "+label);
            variables.putIfAbsent(predicateMatcher.group(1),label);
        }
        Matcher nodeMatcher = NODE.matcher(cypher);
        while (nodeMatcher.find()) {
            String variable = nodeMatcher.group(1);
            String label = nodeMatcher.group(2);
            if (label != null) {
                GraphSchemaSnapshot.NodeSchema node = schema.node(label)
                        .orElseThrow(() -> new IllegalArgumentException("Cypher uses nonexistent or unauthorized label: " + label));
                if (variable != null) variables.put(variable, label);
                validateMapProperties(nodeMatcher.group(3), node.properties().keySet(), "label " + label);
            } else if (variable == null || !variables.containsKey(variable)) {
                throw new IllegalArgumentException("Cypher node patterns must use an authorized label");
            }
        }

        Set<String> allowedRelationships = new HashSet<>(schema.relationshipTypeNames());
        Matcher relationshipMatcher = RELATIONSHIP.matcher(cypher);
        while (relationshipMatcher.find()) {
            String type = relationshipMatcher.group(1);
            if (!allowedRelationships.contains(type)) {
                throw new IllegalArgumentException("Cypher uses nonexistent or unauthorized relationship: " + type);
            }
            validateVariableLength(relationshipMatcher.group(2), maxDepth);
        }

        Matcher propertyMatcher = PROPERTY_REFERENCE.matcher(cypher);
        while (propertyMatcher.find()) {
            String label = variables.get(propertyMatcher.group(1));
            if (label == null) continue;
            String property = propertyMatcher.group(2);
            if (!schema.node(label).orElseThrow().properties().containsKey(property)) {
                throw new IllegalArgumentException("Cypher uses nonexistent or unauthorized property: " + label + "." + property);
            }
        }
    }

    private static void validateMapProperties(String map, Set<String> allowed, String owner) {
        if (map == null) return;
        Matcher matcher = MAP_PROPERTY.matcher(map);
        while (matcher.find()) {
            if (!allowed.contains(matcher.group(1))) {
                throw new IllegalArgumentException("Cypher uses nonexistent or unauthorized property on " + owner + ": " + matcher.group(1));
            }
        }
    }

    private static void validateVariableLength(String relationshipTail, int maxDepth) {
        Matcher matcher = VARIABLE_LENGTH.matcher(relationshipTail == null ? "" : relationshipTail);
        while (matcher.find()) {
            String lower = matcher.group(1);
            String upper = matcher.group(2);
            if (upper == null || upper.isBlank()) {
                if (lower == null || lower.isBlank()) throw new IllegalArgumentException("unbounded variable-length paths are forbidden");
                if (Integer.parseInt(lower) > maxDepth) throw new IllegalArgumentException("path depth exceeds maxDepth=" + maxDepth);
            } else if (Integer.parseInt(upper) > maxDepth) {
                throw new IllegalArgumentException("path depth exceeds maxDepth=" + maxDepth);
            }
        }
    }

    private static boolean isInsideSquareBrackets(String cypher, int offset) {
        int lastOpen = cypher.lastIndexOf('[', offset);
        if (lastOpen < 0) return false;
        int lastClose = cypher.lastIndexOf(']', offset);
        return lastOpen > lastClose;
    }
}
