package com.agent.management.service;

import com.agent.management.common.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

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

/**
 * Cypher 安全守卫：禁止写操作，只允许只读查询
 *
 * <p>规则：
 * <ul>
 *   <li>只允许 MATCH / OPTIONAL MATCH / WITH / RETURN / SHOW / PROFILE / EXPLAIN / UNWIND / USE 开头</li>
 *   <li>禁止 CREATE / MERGE / DELETE / DETACH / SET / REMOVE / DROP / RENAME / CONSTRAINT / INDEX / GRANT / REVOKE / DENY / LOAD / SUBMIT / FOREACH（in write context）等关键词</li>
 *   <li>禁止分号（防止多语句注入）</li>
 *   <li>禁止注释（// 和 /* *\/）</li>
 * </ul>
 * </p>
 *
 * <p>注意：业务查询禁止 CALL，包括子查询和过程调用；内部 Schema 探测不经过本守卫。</p>
 */
@Slf4j
@Service
public class CypherGuardService {

    private static final Set<String> ALLOWED_PREFIXES = Set.of(
            "MATCH", "OPTIONAL", "WITH", "RETURN", "SHOW",
            "PROFILE", "EXPLAIN", "UNWIND", "USE"
    );

    private static final Set<String> FORBIDDEN_KEYWORDS = Set.of(
            // 写操作
            "CREATE", "MERGE", "DELETE", "DETACH", "SET", "REMOVE",
            "DROP", "RENAME", "FOREACH",
            // Schema 操作
            "CONSTRAINT", "INDEX",
            // 权限
            "GRANT", "REVOKE", "DENY",
            // 数据加载
            "LOAD", "SUBMIT",
            // 子事务
            "IN TRANSACTIONS",
            // 过程调用中可能危险（CALL 自身允许，但禁止其内部出现写关键词——通过整体扫描覆盖）
            // DBA
            "ADMIN", "ADMINISTER", "CALL"
    );

    /** 提取 Cypher 第一个非空单词（忽略前导空白和左括号） */
    private static final Pattern FIRST_WORD = Pattern.compile("^\\s*\\(?\\s*([A-Za-z]+)");

    /** 检测分号 */
    private static final Pattern SEMICOLON = Pattern.compile(";");

    /** 检测 Cypher 注释（// 单行、/* 多行 *\/） */
    private static final Pattern COMMENT = Pattern.compile("//|/\\*|\\*/");

    /** 词边界匹配禁止关键词（IN TRANSACTIONS 含空格，单独正则覆盖） */
    private static final Pattern FORBIDDEN_PATTERN = Pattern.compile(
            "\\b(" + String.join("|",
                    "CREATE", "MERGE", "DELETE", "DETACH", "SET", "REMOVE",
                    "DROP", "RENAME", "FOREACH",
                    "CONSTRAINT", "INDEX",
                    "GRANT", "REVOKE", "DENY",
                    "LOAD", "SUBMIT",
                    "ADMIN", "ADMINISTER", "CALL"
            ) + ")\\b",
            Pattern.CASE_INSENSITIVE
    );

    /** IN TRANSACTIONS（防止 CALL { ... } IN TRANSACTIONS 写批量） */
    private static final Pattern IN_TRANSACTIONS = Pattern.compile(
            "\\bIN\\s+TRANSACTIONS\\b", Pattern.CASE_INSENSITIVE);

    /**
     * 校验 Cypher，违规则抛 BusinessException
     */
    public void validate(String cypher) {
        if (cypher == null || cypher.trim().isEmpty()) {
            throw new BusinessException("Cypher 不能为空");
        }
        String trimmed = cypher.trim();

        // 1. 检查分号
        if (SEMICOLON.matcher(trimmed).find()) {
            throw new BusinessException("禁止使用分号（防止多语句执行）");
        }
        // 2. 检查注释
        if (COMMENT.matcher(trimmed).find()) {
            throw new BusinessException("禁止使用注释（// 或 /* */）");
        }

        // 3. 首词白名单
        Matcher m = FIRST_WORD.matcher(trimmed);
        if (!m.find()) {
            throw new BusinessException("无法解析 Cypher 首词");
        }
        String firstWord = m.group(1).toUpperCase();
        if (!ALLOWED_PREFIXES.contains(firstWord)) {
            throw new BusinessException("仅允许查询语句，当前首词：" + firstWord);
        }

        // 4. 禁止关键词
        Matcher forbidden = FORBIDDEN_PATTERN.matcher(trimmed);
        if (forbidden.find()) {
            throw new BusinessException("Cypher 包含禁止的关键词：" + forbidden.group(1));
        }
        // 5. IN TRANSACTIONS
        if (IN_TRANSACTIONS.matcher(trimmed).find()) {
            throw new BusinessException("禁止使用 IN TRANSACTIONS 批量写");
        }

        log.debug("Cypher 校验通过: {}", trimmed.length() > 80 ? trimmed.substring(0, 80) + "..." : trimmed);
    }
}
