package com.zsElectric.boot.common.util;

import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Pattern;

/**
 * 安全防护工具类
 * <p>
 * 提供 XSS 攻击和 SQL 注入防护的核心检测方法
 *
 * @author zsElectric
 */
@Slf4j
public class SecurityUtils {

    /**
     * SQL 注入检测是否使用严格模式
     * 默认为宽松模式以减少误判
     */
    private static volatile boolean sqlStrictMode = false;

    /**
     * 设置 SQL 注入检测模式
     * 
     * @param strictMode true 为严格模式，false 为宽松模式
     */
    public static void setSqlStrictMode(boolean strictMode) {
        sqlStrictMode = strictMode;
    }

    /**
     * XSS 攻击检测正则表达式
     */
    private static final Pattern[] XSS_PATTERNS = {
            // Script 标签
            Pattern.compile("<script[^>]*?>.*?</script>", Pattern.CASE_INSENSITIVE | Pattern.DOTALL),
            Pattern.compile("<script[^>]*?>", Pattern.CASE_INSENSITIVE),
            Pattern.compile("</script>", Pattern.CASE_INSENSITIVE),
            // JavaScript 事件
            Pattern.compile("javascript:", Pattern.CASE_INSENSITIVE),
            Pattern.compile("onerror\\s*=", Pattern.CASE_INSENSITIVE),
            Pattern.compile("onload\\s*=", Pattern.CASE_INSENSITIVE),
            Pattern.compile("onclick\\s*=", Pattern.CASE_INSENSITIVE),
            Pattern.compile("onmouseover\\s*=", Pattern.CASE_INSENSITIVE),
            Pattern.compile("onfocus\\s*=", Pattern.CASE_INSENSITIVE),
            Pattern.compile("onblur\\s*=", Pattern.CASE_INSENSITIVE),
            Pattern.compile("onsubmit\\s*=", Pattern.CASE_INSENSITIVE),
            Pattern.compile("onreset\\s*=", Pattern.CASE_INSENSITIVE),
            Pattern.compile("onselect\\s*=", Pattern.CASE_INSENSITIVE),
            Pattern.compile("onchange\\s*=", Pattern.CASE_INSENSITIVE),
            // iframe 标签
            Pattern.compile("<iframe[^>]*?>.*?</iframe>", Pattern.CASE_INSENSITIVE | Pattern.DOTALL),
            Pattern.compile("<iframe[^>]*?>", Pattern.CASE_INSENSITIVE),
            // embed、object 标签
            Pattern.compile("<embed[^>]*?>", Pattern.CASE_INSENSITIVE),
            Pattern.compile("<object[^>]*?>", Pattern.CASE_INSENSITIVE),
            // eval、expression
            Pattern.compile("eval\\s*\\(", Pattern.CASE_INSENSITIVE),
            Pattern.compile("expression\\s*\\(", Pattern.CASE_INSENSITIVE),
            // vbscript
            Pattern.compile("vbscript:", Pattern.CASE_INSENSITIVE),
            // img 标签的 src
            Pattern.compile("<img[^>]+src[\\s]*=[\\s]*['\"]?javascript:", Pattern.CASE_INSENSITIVE),
            // style 中的 expression
            Pattern.compile("style\\s*=.*expression", Pattern.CASE_INSENSITIVE),
            // base64 编码的脚本
            Pattern.compile("data:text/html;base64", Pattern.CASE_INSENSITIVE),
            // SVG
            Pattern.compile("<svg[^>]*?>.*?</svg>", Pattern.CASE_INSENSITIVE | Pattern.DOTALL),
            // Meta 标签
            Pattern.compile("<meta[^>]*?>", Pattern.CASE_INSENSITIVE),
            // Link 标签
            Pattern.compile("<link[^>]*?>", Pattern.CASE_INSENSITIVE)
    };

    /**
     * SQL 注入危险关键词
     */
    private static final Set<String> SQL_KEYWORDS = new HashSet<>(Arrays.asList(
            // DML 语句
            "select", "insert", "update", "delete",
            // DDL 语句
            "drop", "create", "alter", "truncate",
            // DCL 语句
            "grant", "revoke",
            // 联合查询
            "union", "join",
            // 系统函数和存储过程
            "exec", "execute", "xp_cmdshell", "sp_executesql",
            // 信息获取
            "information_schema", "mysql.user", "sys.",
            // 条件判断
            "case", "when", "then", "else", "end",
            // 其他危险操作
            "declare", "cast", "convert", "char", "chr",
            "concat", "load_file", "into outfile", "into dumpfile",
            "benchmark", "sleep", "waitfor", "delay",
            // 子查询
            "exists", "any", "all", "some"
    ));

    /**
     * SQL 注入检测正则表达式
     */
    private static final Pattern[] SQL_INJECTION_PATTERNS = {
            // SQL 注释
            Pattern.compile("('.+--)|(--)|(;)|(\\|{2})"),
            // SQL 函数调用
            Pattern.compile("\\bexec(ute)?\\s*\\(", Pattern.CASE_INSENSITIVE),
            // union 查询
            Pattern.compile("\\bunion\\b.*\\bselect\\b", Pattern.CASE_INSENSITIVE),
            // 多语句
            Pattern.compile(";.*?(select|insert|update|delete|drop|create|alter)", Pattern.CASE_INSENSITIVE),
            // 16 进制编码
            Pattern.compile("0x[0-9a-f]+", Pattern.CASE_INSENSITIVE),
            // 字符串拼接
            Pattern.compile("\\bconcat\\s*\\(", Pattern.CASE_INSENSITIVE),
            // sleep 函数
            Pattern.compile("\\bsleep\\s*\\(", Pattern.CASE_INSENSITIVE),
            // benchmark 函数
            Pattern.compile("\\bbenckmark\\s*\\(", Pattern.CASE_INSENSITIVE),
            // waitfor delay
            Pattern.compile("\\bwaitfor\\s+\\bdelay\\b", Pattern.CASE_INSENSITIVE),
            // 子查询
            Pattern.compile("\\bsubstr\\s*\\(", Pattern.CASE_INSENSITIVE),
            Pattern.compile("\\bsubstring\\s*\\(", Pattern.CASE_INSENSITIVE)
    };

    /**
     * 检测 XSS 攻击
     *
     * @param value 待检测的字符串
     * @return 如果检测到 XSS 攻击返回 true，否则返回 false
     */
    public static boolean containsXss(String value) {
        if (StrUtil.isBlank(value)) {
            return false;
        }

        // 解码 URL 编码
        String decodedValue = urlDecode(value);

        // 使用正则表达式检测
        for (Pattern pattern : XSS_PATTERNS) {
            if (pattern.matcher(decodedValue).find()) {
                log.warn("检测到 XSS 攻击，匹配模式: {}, 内容: {}", pattern.pattern(), value);
                return true;
            }
        }

        return false;
    }

    /**
     * 检测 SQL 注入
     *
     * @param value 待检测的字符串
     * @return 如果检测到 SQL 注入返回 true，否则返回 false
     */
    public static boolean containsSqlInjection(String value) {
        if (StrUtil.isBlank(value)) {
            return false;
        }

        String lowerValue = value.toLowerCase();

        // 检查注释符号（更严格的检测）
        if (lowerValue.contains("--") || lowerValue.contains("/*") || lowerValue.contains("*/") || lowerValue.contains("#")) {
            // 检查是否是真正的注释而不是普通文本
            if (lowerValue.matches(".*\\s(--|#).*") || lowerValue.contains("/*") || lowerValue.contains("*/")) {
                log.warn("检测到 SQL 注入注释符号: {}, 内容: {}", "--/#/*", value);
                return true;
            }
        }

        // 检查危险关键词（使用更精确的匹配规则）
        for (String keyword : SQL_KEYWORDS) {
            // 使用单词边界进行匹配，避免误判（例如："selection" 不应匹配 "select"）
            // 同时确保关键词前后不是字母数字字符
            String pattern = "([^a-zA-Z0-9]|^)" + keyword + "([^a-zA-Z0-9]|$)";
            if (Pattern.compile(pattern, Pattern.CASE_INSENSITIVE).matcher(lowerValue).find()) {
                // 进一步检查是否为真实攻击而非正常文本
                // 例如："select" 在 "selected" 中是正常文本，但在 "select * from" 中可能是攻击
                if (isRealSqlInjection(keyword, lowerValue)) {
                    log.warn("检测到 SQL 注入关键词: {}, 内容: {}", keyword, value);
                    return true;
                }
            }
        }

        // 使用正则表达式检测
        for (Pattern pattern : SQL_INJECTION_PATTERNS) {
            if (pattern.matcher(value).find()) {
                log.warn("检测到 SQL 注入，匹配模式: {}, 内容: {}", pattern.pattern(), value);
                return true;
            }
        }

        return false;
    }

    /**
     * 清理 XSS 攻击内容（转义特殊字符）
     *
     * @param value 待清理的字符串
     * @return 清理后的字符串
     */
    public static String cleanXss(String value) {
        if (StrUtil.isBlank(value)) {
            return value;
        }

        // HTML 实体编码
        value = value.replace("&", "&amp;")
                .replace("<", "&lt;")
                .replace(">", "&gt;")
                .replace("\"", "&quot;")
                .replace("'", "&#x27;")
                .replace("/", "&#x2F;");

        return value;
    }

    /**
     * URL 解码（支持多次编码）
     *
     * @param value 待解码的字符串
     * @return 解码后的字符串
     */
    private static String urlDecode(String value) {
        String decoded = value;
        try {
            // 最多解码 3 次，防止多重编码绕过
            for (int i = 0; i < 3; i++) {
                String temp = java.net.URLDecoder.decode(decoded, "UTF-8");
                if (temp.equals(decoded)) {
                    break;
                }
                decoded = temp;
            }
        } catch (Exception e) {
            log.warn("URL 解码失败: {}", value, e);
        }
        return decoded;
    }

    /**
     * 验证输入是否安全（综合检查 XSS 和 SQL 注入）
     *
     * @param value 待验证的字符串
     * @return 如果输入安全返回 true，否则返回 false
     */
    public static boolean isSafeInput(String value) {
        return !containsXss(value) && !containsSqlInjection(value);
    }

    /**
     * 判断是否为真实的SQL注入攻击
     * 
     * @param keyword 检测到的关键词
     * @param value   待检测的字符串（小写）
     * @return 如果是真实攻击返回 true，否则返回 false
     */
    private static boolean isRealSqlInjection(String keyword, String value) {
        if (!sqlStrictMode) {
            // 在宽松模式下，只对明显的攻击模式进行拦截
            switch (keyword) {
                case "select":
                    // 只有当 select 后面跟着典型的 SQL 结构时才认为是攻击
                    boolean isSelectAttack = value.matches(".*select\\s+[a-z0-9_*]+\\s+from\\s+[a-z0-9_]+.*") || 
                           value.matches(".*select\\s+\\*\\s+from\\s+[a-z0-9_]+.*");
                    if (isSelectAttack) {
                        log.debug("检测到可能的 SELECT 攻击: {}", value);
                    }
                    return isSelectAttack;
                case "insert":
                    boolean isInsertAttack = value.contains("insert into");
                    if (isInsertAttack) {
                        log.debug("检测到可能的 INSERT 攻击: {}", value);
                    }
                    return isInsertAttack;
                case "update":
                    boolean isUpdateAttack = value.contains("update ") && value.contains(" set ");
                    if (isUpdateAttack) {
                        log.debug("检测到可能的 UPDATE 攻击: {}", value);
                    }
                    return isUpdateAttack;
                case "delete":
                    boolean isDeleteAttack = value.contains("delete from");
                    if (isDeleteAttack) {
                        log.debug("检测到可能的 DELETE 攻击: {}", value);
                    }
                    return isDeleteAttack;
                case "drop":
                case "create":
                case "alter":
                case "truncate":
                    boolean isDdlAttack = value.contains(keyword + " ");
                    if (isDdlAttack) {
                        log.debug("检测到可能的 DDL 攻击 ({}): {}", keyword, value);
                    }
                    return isDdlAttack;
                case "union":
                    boolean isUnionAttack = value.contains("union select");
                    if (isUnionAttack) {
                        log.debug("检测到可能的 UNION 攻击: {}", value);
                    }
                    return isUnionAttack;
                case "exec":
                case "execute":
                    boolean isExecAttack = value.contains(keyword + "(");
                    if (isExecAttack) {
                        log.debug("检测到可能的 EXEC 攻击 ({}): {}", keyword, value);
                    }
                    return isExecAttack;
                default:
                    // 对于其他关键词，采用宽松策略，避免误判正常用户名等
                    return false;
            }
        } else {
            // 严格模式下保持原来的逻辑
            switch (keyword) {
                case "select":
                    // select 通常是攻击的一部分，后面跟着列名和 from
                    boolean isSelectAttack = value.matches(".*select\\s+[a-z0-9_*]+\\s+from\\s+[a-z0-9_]+.*") || 
                           value.matches(".*select\\s+\\*\\s+from\\s+[a-z0-9_]+.*");
                    if (isSelectAttack) {
                        log.debug("[严格模式] 检测到可能的 SELECT 攻击: {}", value);
                    }
                    return isSelectAttack;
                case "insert":
                    // insert 通常是攻击的一部分，后面跟着 into
                    boolean isInsertAttack = value.contains("insert into");
                    if (isInsertAttack) {
                        log.debug("[严格模式] 检测到可能的 INSERT 攻击: {}", value);
                    }
                    return isInsertAttack;
                case "update":
                    // update 通常是攻击的一部分，后面跟着 set
                    boolean isUpdateAttack = value.contains("update ") && value.contains(" set ");
                    if (isUpdateAttack) {
                        log.debug("[严格模式] 检测到可能的 UPDATE 攻击: {}", value);
                    }
                    return isUpdateAttack;
                case "delete":
                    // delete 通常是攻击的一部分，后面跟着 from
                    boolean isDeleteAttack = value.contains("delete from");
                    if (isDeleteAttack) {
                        log.debug("[严格模式] 检测到可能的 DELETE 攻击: {}", value);
                    }
                    return isDeleteAttack;
                case "drop":
                case "create":
                case "alter":
                case "truncate":
                    // DDL 语句通常是攻击的一部分
                    boolean isDdlAttack = value.contains(keyword + " ");
                    if (isDdlAttack) {
                        log.debug("[严格模式] 检测到可能的 DDL 攻击 ({}): {}", keyword, value);
                    }
                    return isDdlAttack;
                case "union":
                    // union 通常是攻击的一部分，后面跟着 select
                    boolean isUnionAttack = value.contains("union select");
                    if (isUnionAttack) {
                        log.debug("[严格模式] 检测到可能的 UNION 攻击: {}", value);
                    }
                    return isUnionAttack;
                default:
                    // 对于其他关键词，采用宽松策略，避免误判正常用户名等
                    return false;
            }
        }
    }
}
