package com.miraclegarden.smsmessage.xposed.hook;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.nio.charset.StandardCharsets;

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;

/**
 * SHPSSDK attestation 生成链 Hook（含 native JNI）。
 * 目标：在 {@code rdVerifyInfo.data/dataKey} 组装前，让 native 采集层读到「干净环境」。
 */
public final class MariBankAttestationHook {

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

    private static volatile boolean installed = false;

    private MariBankAttestationHook() {
    }

    public static void installLate(XC_LoadPackage.LoadPackageParam lpparam) {
        if (installed) {
            return;
        }
        installed = true;

        int n = 0;
        n += hookAttestationClass(lpparam, "com.shopee.shpssdkbank.wvvvuwwu");
        n += hookAttestationClass(lpparam, "com.shopee.shpssdk.wvvvuwwu");
        n += hookAttestationClass(lpparam,
                "com.shopee.shpssdkbank.uwuvuvvww.uvwuuuuuw.vvvvuwwvu");
        n += hookKnownAttestationMethods(lpparam);
        hookEnvironmentProbes(lpparam);
        XposedBridge.log(TAG + " attestation hooks=" + n);
    }

    /** 逆向确认的 attestation / requestDefense 桥接方法。 */
    private static int hookKnownAttestationMethods(XC_LoadPackage.LoadPackageParam lpparam) {
        int count = 0;
        count += hookMethodByName(lpparam, "com.shopee.shpssdkbank.wvvvuwwu", "vvuwuuvuu");
        count += hookMethodByName(lpparam, "com.shopee.shpssdkbank.wvvvuwwu", "wwvwvwuvv");
        count += hookMethodByName(lpparam, "com.shopee.shpssdkbank.wvvvuwwu", "vuwuuuwv");
        count += hookMethodByName(lpparam, "com.shopee.shpssdkbank.uwuvuvvww.vvuuuuvvv", "wuvwuvwwu");
        return count;
    }

    private static int hookMethodByName(
            XC_LoadPackage.LoadPackageParam lpparam, String className, String methodName) {
        int count = 0;
        try {
            Class<?> clazz = XposedHelpers.findClass(className, lpparam.classLoader);
            for (Method method : clazz.getDeclaredMethods()) {
                if (!methodName.equals(method.getName())) {
                    continue;
                }
                if (hookAttestationMethod(className, method)) {
                    count++;
                }
            }
        } catch (Throwable t) {
            XposedBridge.log(TAG + " skip " + className + "." + methodName + ": " + t.getMessage());
        }
        return count;
    }

    private static int hookAttestationClass(
            XC_LoadPackage.LoadPackageParam lpparam, String className) {
        int count = 0;
        try {
            Class<?> clazz = XposedHelpers.findClass(className, lpparam.classLoader);
            for (Method method : clazz.getDeclaredMethods()) {
                if (!Modifier.isStatic(method.getModifiers())) {
                    continue;
                }
                if (hookAttestationMethod(className, method)) {
                    count++;
                }
            }
        } catch (Throwable t) {
            XposedBridge.log(TAG + " skip class " + className + ": " + t.getMessage());
        }
        return count;
    }

    private static boolean hookAttestationMethod(String className, Method method) {
        Class<?> rt = method.getReturnType();
        if (rt == String.class) {
            XposedBridge.hookMethod(method, new XC_MethodHook() {
                @Override
                protected void afterHookedMethod(MethodHookParam param) {
                    Object result = param.getResult();
                    if (!(result instanceof String)) {
                        return;
                    }
                    String s = (String) result;
                    String sanitized = sanitizeAttestationString(s);
                    if (!sanitized.equals(s)) {
                        param.setResult(sanitized);
                        XposedBridge.log(TAG + " " + className + "#" + method.getName()
                                + (Modifier.isNative(method.getModifiers()) ? " (native)" : "")
                                + " sanitized len=" + s.length());
                    }
                }
            });
            return true;
        }
        if (rt == byte[].class) {
            XposedBridge.hookMethod(method, new XC_MethodHook() {
                @Override
                protected void afterHookedMethod(MethodHookParam param) {
                    Object result = param.getResult();
                    if (!(result instanceof byte[])) {
                        return;
                    }
                    byte[] bytes = (byte[]) result;
                    byte[] out = MariBankRegisterPayloadUtil.sanitizeRegistrationBytes(bytes);
                    if (out != bytes) {
                        param.setResult(out);
                        XposedBridge.log(TAG + " " + className + "#" + method.getName()
                                + " byte[] sanitized");
                    } else {
                        byte[] tokenOut = MariBankRiskTokenUtil.sanitizeBytes(bytes, 0, bytes.length);
                        if (tokenOut != bytes) {
                            param.setResult(tokenOut);
                            XposedBridge.log(TAG + " " + className + "#" + method.getName()
                                    + " byte[] riskToken sanitized");
                        }
                    }
                }
            });
            return true;
        }
        if (rt == boolean.class || rt == Boolean.class) {
            XposedBridge.hookMethod(method, new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) {
                    param.setResult(false);
                }
            });
            return true;
        }
        if (rt == int.class || rt == Integer.class) {
            XposedBridge.hookMethod(method, new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) {
                    param.setResult(0);
                }
            });
            return true;
        }
        return false;
    }

    private static String sanitizeAttestationString(String s) {
        if (s == null || s.isEmpty()) {
            return s;
        }
        if (MariBankRegisterPayloadUtil.isRegistrationPayload(s)) {
            return MariBankRegisterPayloadUtil.sanitizeRegistrationJson(s);
        }
        if (s.contains("|")) {
            return MariBankRiskTokenUtil.sanitizeRiskToken(s);
        }
        return MariBankRiskTokenUtil.sanitizeAllInText(s);
    }

    private static void hookEnvironmentProbes(XC_LoadPackage.LoadPackageParam lpparam) {
        try {
            XposedHelpers.findAndHookMethod(
                    "android.os.Debug",
                    lpparam.classLoader,
                    "isDebuggerConnected",
                    new XC_MethodHook() {
                        @Override
                        protected void beforeHookedMethod(MethodHookParam param) {
                            param.setResult(false);
                        }
                    }
            );
        } catch (Throwable ignored) {
        }
        try {
            XposedHelpers.findAndHookMethod(
                    "android.os.Debug",
                    lpparam.classLoader,
                    "waitingForDebugger",
                    new XC_MethodHook() {
                        @Override
                        protected void beforeHookedMethod(MethodHookParam param) {
                            param.setResult(false);
                        }
                    }
            );
        } catch (Throwable ignored) {
        }
        RootBypassHelper.hookFileExists(lpparam);
        RootBypassHelper.hookRuntimeExec(lpparam);
        RootBypassHelper.hookSystemGetProperty(lpparam);
        hookProcessBuilder(lpparam);
    }

    private static void hookProcessBuilder(XC_LoadPackage.LoadPackageParam lpparam) {
        try {
            XposedHelpers.findAndHookMethod(
                    ProcessBuilder.class,
                    "start",
                    new XC_MethodHook() {
                        @Override
                        protected void beforeHookedMethod(MethodHookParam param) {
                            ProcessBuilder pb = (ProcessBuilder) param.thisObject;
                            if (pb == null || pb.command() == null) {
                                return;
                            }
                            String joined = String.join(" ", pb.command()).toLowerCase();
                            if (joined.contains(" su") || joined.startsWith("su")
                                    || joined.contains("magisk") || joined.contains("which su")
                                    || joined.contains("getprop ro.debuggable")) {
                                XposedBridge.log(TAG + " blocked ProcessBuilder: " + joined);
                                throw new SecurityException("blocked root probe");
                            }
                        }
                    }
            );
        } catch (Throwable t) {
            XposedBridge.log(TAG + " ProcessBuilder hook failed: " + t.getMessage());
        }
    }
}
