package org.example.Utils;

import java.util.Arrays;
import java.util.Locale;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public final class SqlSafetyGuard {
    public static final String RULE_VERSION = "readonly-select-v3-configurable";

    private static final Pattern QUALIFIED_IDENTIFIER =
            Pattern.compile("[A-Za-z_][A-Za-z0-9_]*(\\.[A-Za-z_][A-Za-z0-9_]*)*");
    private static final Pattern SQL_TERMINATOR_OR_COMMENT =
            Pattern.compile("(?s)(;|--|/\\*|\\*/)");
    private static final Pattern READ_ONLY_DANGEROUS_KEYWORDS =
            Pattern.compile("(?i)\\b(insert|update|delete|drop|alter|truncate|merge|exec|execute|call|create|grant|revoke|replace|into|outfile|load_file|xp_cmdshell|shutdown|waitfor|sleep|benchmark)\\b");
    private static final Pattern CONDITION_DANGEROUS_KEYWORDS =
            Pattern.compile("(?i)\\b(insert|update|delete|drop|alter|truncate|merge|exec|execute|call|create|grant|revoke|replace|union|into|outfile|load_file|xp_cmdshell|shutdown|waitfor|sleep|benchmark)\\b");

    private SqlSafetyGuard() {
    }

    public static boolean isSafeIdentifier(String value) {
        return value != null && QUALIFIED_IDENTIFIER.matcher(value.trim()).matches();
    }

    public static String requireSafeIdentifier(String value) {
        String trimmed = value == null ? "" : value.trim();
        if (!isSafeIdentifier(trimmed)) {
            throw new IllegalArgumentException("Unsafe SQL identifier: " + trimmed);
        }
        return trimmed;
    }

    public static String requireAllowedIdentifier(String value, String... allowedValues) {
        String trimmed = requireSafeIdentifier(value);
        Set<String> allowed = Arrays.stream(allowedValues)
                .map(v -> v.toLowerCase(Locale.ROOT))
                .collect(Collectors.toSet());
        if (!allowed.contains(trimmed.toLowerCase(Locale.ROOT))) {
            throw new IllegalArgumentException("SQL identifier is not allowed: " + trimmed);
        }
        return trimmed;
    }

    public static boolean isSafeReadOnlySelect(String sql) {
        if (sql == null) {
            return false;
        }

        String trimmed = sanitizeReadOnlySelectForExecution(sql);
        if (trimmed == null) {
            return false;
        }
        trimmed = trimmed.trim();
        if (trimmed.isEmpty()) {
            return false;
        }

        String normalized = trimmed.toLowerCase(Locale.ROOT);
        return startsWithReadOnlyQueryKeyword(normalized)
                && !containsDangerousReadOnlyQueryToken(trimmed);
    }

    public static String requireSafeReadOnlySelect(String sql) {
        String strippedSql = sanitizeReadOnlySelectForExecution(sql);
        if (strippedSql == null || !isSafeReadOnlySelect(strippedSql)) {
            throw new IllegalArgumentException("Only read-only SELECT SQL is allowed");
        }
        return strippedSql.trim();
    }

    public static String sanitizeReadOnlySelectForExecution(String sql) {
        return stripSqlComments(sql);
    }

    public static boolean isSafeConditionFragment(String fragment) {
        if (fragment == null || fragment.trim().isEmpty()) {
            return true;
        }
        return !containsDangerousConditionToken(fragment);
    }

    public static String requireSafeConditionFragment(String fragment) {
        String safeFragment = fragment == null ? "" : fragment.trim();
        if (!isSafeConditionFragment(safeFragment)) {
            throw new IllegalArgumentException("Unsafe SQL condition fragment");
        }
        return safeFragment;
    }

    private static boolean containsDangerousConditionToken(String sql) {
        return SQL_TERMINATOR_OR_COMMENT.matcher(sql).find()
                || CONDITION_DANGEROUS_KEYWORDS.matcher(sql).find();
    }

    private static boolean startsWithReadOnlyQueryKeyword(String normalizedSql) {
        return startsWithKeywordAndWhitespace(normalizedSql, "select")
                || startsWithKeywordAndWhitespace(normalizedSql, "with");
    }

    private static boolean startsWithKeywordAndWhitespace(String normalizedSql, String keyword) {
        return normalizedSql.length() > keyword.length()
                && normalizedSql.startsWith(keyword)
                && Character.isWhitespace(normalizedSql.charAt(keyword.length()));
    }

    private static boolean containsDangerousReadOnlyQueryToken(String sql) {
        return containsStatementTerminatorOutsideString(sql)
                || READ_ONLY_DANGEROUS_KEYWORDS.matcher(maskStringLiterals(sql)).find();
    }

    private static boolean containsStatementTerminatorOutsideString(String sql) {
        boolean inString = false;
        for (int i = 0; i < sql.length(); i++) {
            char current = sql.charAt(i);
            if (inString) {
                if (current == '\'') {
                    if (i + 1 < sql.length() && sql.charAt(i + 1) == '\'') {
                        i++;
                    } else {
                        inString = false;
                    }
                }
                continue;
            }

            if (current == '\'') {
                inString = true;
            } else if (current == ';') {
                return true;
            }
        }
        return inString;
    }

    private static String maskStringLiterals(String sql) {
        StringBuilder masked = new StringBuilder(sql.length());
        boolean inString = false;
        for (int i = 0; i < sql.length(); i++) {
            char current = sql.charAt(i);
            if (inString) {
                if (current == '\'') {
                    masked.append(' ');
                    if (i + 1 < sql.length() && sql.charAt(i + 1) == '\'') {
                        masked.append(' ');
                        i++;
                    } else {
                        inString = false;
                    }
                } else if (current == '\r' || current == '\n') {
                    masked.append(current);
                } else {
                    masked.append(' ');
                }
                continue;
            }

            if (current == '\'') {
                inString = true;
                masked.append(' ');
            } else {
                masked.append(current);
            }
        }
        return masked.toString();
    }

    private static String stripSqlComments(String sql) {
        if (sql == null) {
            return null;
        }

        StringBuilder stripped = new StringBuilder(sql.length());
        boolean inString = false;
        for (int i = 0; i < sql.length(); i++) {
            char current = sql.charAt(i);

            if (inString) {
                stripped.append(current);
                if (current == '\'') {
                    if (i + 1 < sql.length() && sql.charAt(i + 1) == '\'') {
                        stripped.append(sql.charAt(i + 1));
                        i++;
                    } else {
                        inString = false;
                    }
                }
                continue;
            }

            if (current == '\'') {
                inString = true;
                stripped.append(current);
                continue;
            }

            if (current == '-' && i + 1 < sql.length() && sql.charAt(i + 1) == '-') {
                int legacyCommentEnd = legacyInlineDisabledColumnEnd(sql, i, stripped);
                if (legacyCommentEnd >= 0) {
                    i = legacyCommentEnd;
                    continue;
                }

                stripped.append(' ');
                i += 2;
                while (i < sql.length() && sql.charAt(i) != '\r' && sql.charAt(i) != '\n') {
                    i++;
                }
                if (i < sql.length()) {
                    stripped.append(sql.charAt(i));
                    if (sql.charAt(i) == '\r' && i + 1 < sql.length() && sql.charAt(i + 1) == '\n') {
                        stripped.append(sql.charAt(i + 1));
                        i++;
                    }
                }
                continue;
            }

            if (current == '/' && i + 1 < sql.length() && sql.charAt(i + 1) == '*') {
                stripped.append(' ');
                i += 2;
                boolean closed = false;
                while (i + 1 < sql.length()) {
                    if (sql.charAt(i) == '*' && sql.charAt(i + 1) == '/') {
                        i++;
                        closed = true;
                        break;
                    }
                    if (sql.charAt(i) == '\r' || sql.charAt(i) == '\n') {
                        stripped.append(sql.charAt(i));
                    }
                    i++;
                }
                if (!closed) {
                    return null;
                }
                continue;
            }

            stripped.append(current);
        }

        return inString ? null : stripped.toString();
    }

    private static int legacyInlineDisabledColumnEnd(String sql, int commentStart, StringBuilder stripped) {
        if (!previousNonWhitespaceIsComma(stripped)) {
            return -1;
        }

        int i = commentStart + 2;
        if (i >= sql.length() || Character.isWhitespace(sql.charAt(i))) {
            return -1;
        }

        boolean hasIdentifier = false;
        while (i < sql.length()) {
            char current = sql.charAt(i);
            if (current == ',') {
                return hasIdentifier ? i : -1;
            }
            if (current == '\r' || current == '\n') {
                return -1;
            }
            if (!isLegacyDisabledColumnChar(current)) {
                return -1;
            }
            if (!Character.isWhitespace(current)) {
                hasIdentifier = true;
            }
            i++;
        }
        return -1;
    }

    private static boolean previousNonWhitespaceIsComma(StringBuilder value) {
        for (int i = value.length() - 1; i >= 0; i--) {
            if (!Character.isWhitespace(value.charAt(i))) {
                return value.charAt(i) == ',';
            }
        }
        return false;
    }

    private static boolean isLegacyDisabledColumnChar(char value) {
        return Character.isLetterOrDigit(value)
                || Character.isWhitespace(value)
                || value == '_'
                || value == '.'
                || value == '*';
    }
}
