package com.miraclegarden.smsmessage.xposed.hook;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.nio.charset.StandardCharsets;
import java.util.HashSet;
import java.util.Set;

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 probesInstalled = false;
    private static volatile boolean loadClassWatcherInstalled = false;
    private static final Set<String> HOOKED_ATTESTATION_CLASSES = new HashSet<>();

    private MariBankAttestationHook() {
    }

    public static int installLate(XC_LoadPackage.LoadPackageParam lpparam) {
        if (!probesInstalled) {
            hookEnvironmentProbes(lpparam);
            probesInstalled = true;
        }
        installLoadClassWatcher(lpparam);
        RootBypassHelper.hookSecurityClass(lpparam, "com.shopee.bke.biz.base.risk.a");

        int n = 0;
        for (String className : MariBankRegionProfile.shpsAttestationCoreClasses(lpparam.packageName)) {
            n += hookAttestationClass(lpparam, className);
        }
        n += hookKnownAttestationMethods(lpparam);
        if (n > 0) {
            XposedBridge.log(TAG + " attestation hooks=" + n
                    + " region=" + MariBankRegionProfile.label(lpparam.packageName)
                    + " classes=" + HOOKED_ATTESTATION_CLASSES.size());
        }
        return n;
    }

    private static void installLoadClassWatcher(XC_LoadPackage.LoadPackageParam lpparam) {
        if (loadClassWatcherInstalled) {
            return;
        }
        loadClassWatcherInstalled = true;
        try {
            XposedHelpers.findAndHookMethod(
                    ClassLoader.class,
                    "loadClass",
                    String.class,
                    boolean.class,
                    new XC_MethodHook() {
                        @Override
                        protected void afterHookedMethod(MethodHookParam param) {
                            if (param.getThrowable() != null) {
                                return;
                            }
                            String name = (String) param.args[0];
                            if (name == null) {
                                return;
                            }
                            if (name.contains("shpssdk")
                                    || name.contains("CharacterCrypto")
                                    || name.contains("bke.biz.base.risk")) {
                                if (MariBankRegionProfile.isSingapore(lpparam.packageName)
                                        && MariBankRegionProfile.isShpssdkLegacyPackage(name)) {
                                    return;
                                }
                                installLate(lpparam);
                            }
                        }
                    });
            XposedBridge.log(TAG + " ClassLoader attestation watcher OK");
        } catch (Throwable t) {
            XposedBridge.log(TAG + " skip ClassLoader watcher: " + t.getMessage());
        }
    }

    /** 逆向确认的 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.wvvvuwwu", "vuwuuwvw");
        count += hookMethodByName(lpparam, "com.shopee.shpssdkbank.wvvvuwwu", "vuwuuwvvw");
        count += hookMethodByName(lpparam, "com.shopee.shpssdkbank.wvvvuwwu", "vuwuuwvu");
        count += hookMethodByName(lpparam, "com.shopee.shpssdkbank.uwuvuvvww.vvuuuuvvv", "wuvwuvwwu");
        count += hookMethodByName(lpparam, "com.shopee.shpssdkbank.uwuvuvvww.vvuuuuvvv", "wwvuwuwvu");
        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) {
        if (HOOKED_ATTESTATION_CLASSES.contains(className)) {
            return 0;
        }
        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++;
                }
            }
            if (count > 0) {
                HOOKED_ATTESTATION_CLASSES.add(className);
            }
        } catch (Throwable t) {
            XposedBridge.log(TAG + " skip class " + className + ": " + t.getMessage());
        }
        return count;
    }

    private static boolean hookAttestationMethod(String className, Method method) {
        final String methodName = method.getName();
        final boolean isVuwuuwvw = "vuwuuwvw".equals(methodName);
        Class<?> rt = method.getReturnType();
        if (rt == String.class) {
            XposedBridge.hookMethod(method, new XC_MethodHook() {
                private byte[][] vuwuInputs;

                @Override
                protected void beforeHookedMethod(MethodHookParam param) {
                    if (!isVuwuuwvw) {
                        return;
                    }
                    vuwuInputs = new byte[param.args.length][];
                    for (int i = 0; i < param.args.length; i++) {
                        if (param.args[i] instanceof byte[]) {
                            byte[] b = (byte[]) param.args[i];
                            vuwuInputs[i] = b;
                            XposedBridge.log(TAG + " >> vuwuuwvw in" + i + " len=" + b.length);
                        }
                    }
                }

                @Override
                protected void afterHookedMethod(MethodHookParam param) {
                    Object result = param.getResult();
                    if (!(result instanceof String)) {
                        return;
                    }
                    String s = (String) result;
                    if (isVuwuuwvw) {
                        MariBankAttestationJsonUtil.logVuwuuwvwCall(vuwuInputs, s);
                    }
                    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;
                            }
                            if (ProbeGuard.isBlockedCommand(pb.command())) {
                                param.setResult(ProbeGuard.fakeFailedProcess(
                                        String.join(" ", pb.command())));
                            }
                        }
                    }
            );
        } catch (Throwable t) {
            XposedBridge.log(TAG + " ProcessBuilder hook failed: " + t.getMessage());
        }
    }
}
