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;

/**
 * register body 加密前 Hook：{@code com.shopee.bke.lib.jni.utils.d} / {@code uvwuvwuv}。
 * 勿 Hook {@code utils.f}（SoUtils），否则会 libsdkutils 白屏。
 */
public final class MariBankSdkUtilsHook {

    private static final String TAG = "notiMessageHook/MariBankEncrypt";
    private static final int MAX_LOG = 2000;

    private static final String WRAPPER = "com.shopee.bke.lib.jni.utils.d";
    private static final String NATIVE_ENCRYPT = "com.shopee.bke.lib.jni.utils.uvwuvwuv";

    private static volatile boolean installed = false;

    private MariBankSdkUtilsHook() {
    }

    /** BkeApplication.onCreate 之后安装（libsdkutils 已加载）。 */
    public static void installLate(XC_LoadPackage.LoadPackageParam lpparam) {
        if (installed) {
            return;
        }
        installed = true;
        int n = hookEncryptWrapper(lpparam);
        n += hookNativeEncryptUtils(lpparam);
        n += hookGsonRegister(lpparam);
        XposedBridge.log(TAG + " late encrypt hooks=" + n);
    }

    private static int hookEncryptWrapper(XC_LoadPackage.LoadPackageParam lpparam) {
        int count = 0;
        try {
            Class<?> clazz = XposedHelpers.findClass(WRAPPER, lpparam.classLoader);
            for (Method method : clazz.getDeclaredMethods()) {
                if (Modifier.isStatic(method.getModifiers())) {
                    continue;
                }
                XposedBridge.hookMethod(method, new XC_MethodHook() {
                    @Override
                    protected void beforeHookedMethod(MethodHookParam param) {
                        String name = method.getName();
                        XposedBridge.log(TAG + " >> utils.d." + name);
                        for (int i = 0; i < param.args.length; i++) {
                            Object sanitized = sanitizeArg(param.args[i]);
                            if (sanitized != param.args[i]) {
                                param.args[i] = sanitized;
                            }
                            logArg("  in" + i, param.args[i]);
                        }
                    }

                    @Override
                    protected void afterHookedMethod(MethodHookParam param) {
                        Object result = param.getResult();
                        if (result == null) {
                            return;
                        }
                        if (result instanceof String[]) {
                            for (int i = 0; i < ((String[]) result).length; i++) {
                                logArg("  out" + i, ((String[]) result)[i]);
                            }
                        } else if (result instanceof byte[]) {
                            logArg("  out", result);
                        }
                    }
                });
                count++;
            }
            if (count > 0) {
                XposedBridge.log(TAG + " hooked utils.d methods=" + count);
            }
        } catch (Throwable t) {
            XposedBridge.log(TAG + " skip utils.d: " + t.getMessage());
        }
        return count;
    }

    private static int hookNativeEncryptUtils(XC_LoadPackage.LoadPackageParam lpparam) {
        int count = 0;
        try {
            Class<?> clazz = XposedHelpers.findClass(NATIVE_ENCRYPT, lpparam.classLoader);
            for (Method method : clazz.getDeclaredMethods()) {
                if (!Modifier.isStatic(method.getModifiers())) {
                    continue;
                }
                XposedBridge.hookMethod(method, new XC_MethodHook() {
                    @Override
                    protected void beforeHookedMethod(MethodHookParam param) {
                        XposedBridge.log(TAG + " >> uvwuvwuv." + method.getName());
                        for (int i = 0; i < param.args.length; i++) {
                            Object sanitized = sanitizeArg(param.args[i]);
                            if (sanitized != param.args[i]) {
                                param.args[i] = sanitized;
                            }
                            logArg("  in" + i, param.args[i]);
                        }
                    }
                });
                count++;
            }
            if (count > 0) {
                XposedBridge.log(TAG + " hooked uvwuvwuv methods=" + count);
            }
        } catch (Throwable t) {
            XposedBridge.log(TAG + " skip uvwuvwuv: " + t.getMessage());
        }
        return count;
    }

    private static int hookGsonRegister(XC_LoadPackage.LoadPackageParam lpparam) {
        try {
            XposedHelpers.findAndHookMethod(
                    "com.google.gson.Gson",
                    lpparam.classLoader,
                    "toJson",
                    Object.class,
                    new XC_MethodHook() {
                        @Override
                        protected void afterHookedMethod(MethodHookParam param) {
                            if (!(param.getResult() instanceof String)) {
                                return;
                            }
                            String json = (String) param.getResult();
                            if (!looksLikeRegisterJson(json)) {
                                return;
                            }
                            String sanitized = MariBankRegisterPayloadUtil.isRegistrationPayload(json)
                                    ? MariBankRegisterPayloadUtil.sanitizeRegistrationJson(json)
                                    : MariBankRiskTokenUtil.sanitizeAllInText(json);
                            if (!sanitized.equals(json)) {
                                param.setResult(sanitized);
                                json = sanitized;
                            }
                            if (MariBankRegisterPayloadUtil.isRegistrationPayload(json)) {
                                XposedBridge.log(TAG + " Gson REGISTRATION: " + truncate(json));
                            }
                        }
                    }
            );
            return 1;
        } catch (Throwable t) {
            XposedBridge.log(TAG + " skip Gson.toJson: " + t.getMessage());
            return 0;
        }
    }

    private static boolean looksLikeRegisterJson(String text) {
        if (text == null || text.length() < 8) {
            return false;
        }
        if (MariBankRegisterPayloadUtil.isRegistrationPayload(text)) {
            return true;
        }
        String lower = text.toLowerCase();
        return lower.contains("rdverifyinfo")
                || lower.contains("devicefingerprint")
                || (lower.contains("encphone") && lower.contains("scene"));
    }

    private static Object sanitizeArg(Object arg) {
        if (arg instanceof String) {
            String s = (String) arg;
            if (!s.contains("|") && !looksLikeRegisterJson(s)) {
                return arg;
            }
            String out = MariBankRegisterPayloadUtil.isRegistrationPayload(s)
                    ? MariBankRegisterPayloadUtil.sanitizeRegistrationJson(s)
                    : MariBankRiskTokenUtil.sanitizeAllInText(s);
            return out.equals(s) ? arg : out;
        }
        if (arg instanceof byte[]) {
            byte[] bytes = (byte[]) arg;
            byte[] out = MariBankRegisterPayloadUtil.sanitizeRegistrationBytes(bytes);
            return out == bytes ? arg : out;
        }
        return arg;
    }

    private static void logArg(String label, Object arg) {
        if (arg == null) {
            XposedBridge.log(TAG + label + " null");
            return;
        }
        if (arg instanceof String) {
            String s = (String) arg;
            if (s.length() > 4 || s.contains("|") || looksLikeRegisterJson(s)) {
                XposedBridge.log(TAG + label + " String(" + s.length() + ") " + truncate(s));
            }
            return;
        }
        if (arg instanceof byte[]) {
            byte[] bytes = (byte[]) arg;
            String text;
            try {
                text = new String(bytes, StandardCharsets.UTF_8);
            } catch (Throwable t) {
                text = "<bin>";
            }
            if (text.contains("|") || looksLikeRegisterJson(text) || bytes.length < 512) {
                XposedBridge.log(TAG + label + " byte[" + bytes.length + "] " + truncate(text));
            }
        }
    }

    private static String truncate(String s) {
        if (s == null) {
            return "";
        }
        if (s.length() <= MAX_LOG) {
            return s;
        }
        return s.substring(0, MAX_LOG) + "...";
    }
}
