package com.miraclegarden.smsmessage.xposed.hook;

import java.io.File;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import java.util.regex.Pattern;

import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;

/**
 * Root / Hook / 模拟器检测通用绕过辅助。
 */
public final class RootBypassHelper {

    private static final String TAG = "notiMessageHook/RootBypass";

    private static final Pattern UNSAFE_NAME = Pattern.compile(
            ".*(root|jail|hook|frida|xposed|lsposed|emulator|simulator|debug|tamper|"
                    + "integrity|unsafe|risk|magisk|su|cheat|mock).*",
            Pattern.CASE_INSENSITIVE
    );

    private static final Pattern SAFE_NAME = Pattern.compile(
            ".*(safe|secure|valid|passed|pass|clean|trusted|normal|ok).*",
            Pattern.CASE_INSENSITIVE
    );

    private static final Set<String> ROOT_PATH_MARKERS = new HashSet<>(Arrays.asList(
            "/su",
            "magisk",
            "supersu",
            "busybox",
            "/xbin/su",
            "/sbin/su",
            "de.robv.android.xposed",
            "org.lsposed",
            "com.topjohnwu.magisk"
    ));

    private RootBypassHelper() {
    }

    public static void hookSecurityClass(XC_LoadPackage.LoadPackageParam lpparam, String className) {
        try {
            Class<?> clazz = XposedHelpers.findClass(className, lpparam.classLoader);
            for (Method method : clazz.getDeclaredMethods()) {
                hookMethodIfSecurityCheck(className, method);
            }
            XposedBridge.log(TAG + " hooked methods in " + className);
        } catch (Throwable t) {
            XposedBridge.log(TAG + " skip class " + className + ": " + t.getMessage());
        }
    }

    private static void hookMethodIfSecurityCheck(String className, Method method) {
        Class<?> returnType = method.getReturnType();
        if (returnType != boolean.class
                && returnType != Boolean.class
                && returnType != int.class
                && returnType != Integer.class) {
            return;
        }

        String name = method.getName();
        if (!looksLikeSecurityMethod(name) && !className.toLowerCase(Locale.US).contains("safemode")
                && !className.toLowerCase(Locale.US).contains("risk")) {
            return;
        }

        try {
            XposedBridge.hookMethod(method, new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) {
                    if (returnType == boolean.class || returnType == Boolean.class) {
                        param.setResult(shouldReturnTrue(name));
                    } else {
                        param.setResult(0);
                    }
                }
            });
        } catch (Throwable t) {
            XposedBridge.log(TAG + " hook failed " + className + "#" + name + ": " + t.getMessage());
        }
    }

    private static boolean looksLikeSecurityMethod(String name) {
        return UNSAFE_NAME.matcher(name).matches() || SAFE_NAME.matcher(name).matches();
    }

    private static boolean shouldReturnTrue(String methodName) {
        if (UNSAFE_NAME.matcher(methodName).matches()) {
            return false;
        }
        if (SAFE_NAME.matcher(methodName).matches()) {
            return true;
        }
        return false;
    }

    public static void hookFileExists(XC_LoadPackage.LoadPackageParam lpparam) {
        try {
            XposedHelpers.findAndHookMethod(
                    File.class,
                    "exists",
                    new XC_MethodHook() {
                        @Override
                        protected void beforeHookedMethod(MethodHookParam param) {
                            File file = (File) param.thisObject;
                            if (file == null) {
                                return;
                            }
                            String path = file.getAbsolutePath().toLowerCase(Locale.US);
                            for (String marker : ROOT_PATH_MARKERS) {
                                if (path.contains(marker)) {
                                    param.setResult(false);
                                    return;
                                }
                            }
                        }
                    }
            );
        } catch (Throwable t) {
            XposedBridge.log(TAG + " File.exists hook failed: " + t.getMessage());
        }
    }

    public static void hookRuntimeExec(XC_LoadPackage.LoadPackageParam lpparam) {
        try {
            XposedHelpers.findAndHookMethod(
                    Runtime.class,
                    "exec",
                    String.class,
                    new XC_MethodHook() {
                        @Override
                        protected void beforeHookedMethod(MethodHookParam param) {
                            String cmd = (String) param.args[0];
                            if (cmd == null) {
                                return;
                            }
                            String lower = cmd.toLowerCase(Locale.US);
                            if (lower.contains("su") || lower.contains("magisk") || lower.contains("which su")) {
                                throw new SecurityException("blocked root probe");
                            }
                        }
                    }
            );
        } catch (Throwable t) {
            XposedBridge.log(TAG + " Runtime.exec hook failed: " + t.getMessage());
        }
    }

    public static void hookSystemGetProperty(XC_LoadPackage.LoadPackageParam lpparam) {
        try {
            XposedHelpers.findAndHookMethod(
                    System.class,
                    "getProperty",
                    String.class,
                    new XC_MethodHook() {
                        @Override
                        protected void beforeHookedMethod(MethodHookParam param) {
                            String key = (String) param.args[0];
                            if (key == null) {
                                return;
                            }
                            if ("ro.debuggable".equals(key) || "ro.secure".equals(key)) {
                                param.setResult("ro.secure".equals(key) ? "1" : "0");
                            }
                        }
                    }
            );
        } catch (Throwable t) {
            XposedBridge.log(TAG + " System.getProperty hook failed: " + t.getMessage());
        }
    }
}
