package tech.easyflow.datacenter.utils;

import tech.easyflow.common.web.exceptions.BusinessException;

import java.util.Locale;
import java.util.Set;

/**
 * 数据中心动态 SQL 的安全校验与字面量转义工具。
 */
public final class SqlInjectionUtils {

    private static final Set<String> SQL_KEYWORDS = Set.of(
            "select", "insert", "update", "delete", "drop", "alter", "create",
            "table", "where", "from", "join", "union", "truncate", "execute",
            "grant", "revoke", "commit", "rollback", "order", "group", "by",
            "having", "limit", "offset", "as", "on", "into", "values", "index",
            "key", "primary", "constraint", "references", "distinct", "case",
            "when", "then", "else", "end", "and", "or", "not", "null", "like",
            "in", "is", "exists", "between", "procedure", "function", "trigger",
            "view", "database", "schema", "column", "add", "rename", "replace",
            "show", "describe", "explain", "use", "lock", "unlock"
    );

    private SqlInjectionUtils() {
    }

    /**
     * 校验动态 SQL 中的字段或表标识符。
     *
     * @param identifier 待校验的标识符
     * @return 已校验的标识符
     * @throws BusinessException 标识符为空、过长、包含非法字符或为 SQL 关键字时抛出
     */
    public static String checkIdentifier(String identifier) {
        if (identifier == null || identifier.isEmpty()) {
            throw new BusinessException("标识符不能为空");
        }
        if (identifier.length() > 64) {
            throw new BusinessException("标识符过长");
        }
        // 检查是否只包含字母、数字和下划线
        if (!identifier.matches("^[a-zA-Z0-9_]+$")) {
            throw new BusinessException("只允许字母、数字和下划线");
        }
        if (isSqlKeyword(identifier)) {
            throw new BusinessException("非法字符");
        }
        return identifier;
    }

    /**
     * 校验并转义 SQL 字符串字面量中的表或字段备注。
     *
     * <p>Excel 表头和工作表名称可包含常见的中英文标点、单位符号和 Unicode 字符。换行和制表符会
     * 规范为单个空格；其余控制字符会被拒绝。单引号和反斜杠会被转义，避免备注内容破坏动态 DDL 语句。</p>
     *
     * @param comment 原始备注内容
     * @return 可安全拼入单引号 SQL 字符串字面量的备注内容
     * @throws BusinessException 备注过长或包含控制字符时抛出
     */
    public static String checkComment(String comment) {
        if (comment == null || comment.isEmpty()) {
            return "";
        }
        if (comment.length() > 255) {
            throw new BusinessException("注释过长");
        }
        String normalizedComment = comment.replaceAll("[\\r\\n\\t]+", " ");
        if (normalizedComment.codePoints().anyMatch(Character::isISOControl)) {
            throw new BusinessException("备注不能包含控制字符");
        }
        return normalizedComment.replace("\\", "\\\\").replace("'", "''");
    }

    /**
     * 判断指定单词是否为受限 SQL 关键字。
     *
     * @param word 待判断的单词
     * @return 是 SQL 关键字时返回 {@code true}
     */
    public static boolean isSqlKeyword(String word) {
        return word != null && SQL_KEYWORDS.contains(word.toLowerCase(Locale.ROOT));
    }
}
