package com.miraclegarden.smsmessage.xposed.hook;

import java.lang.reflect.Method;
import java.util.Arrays;
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;

/**
 * DFP（device fingerprint）初始化诊断：register 前若 dfp 为空会弹
 * "The system is currently unavailable"，且不会发 /uapi/v2/register。
 */
public final class MariBankDfpHook {

    private static final String TAG = "notiMessageHook/MariBankDfp";
    private static final Set<String> HOOKED_CLASSES = new HashSet<>();

    private static volatile boolean loadClassWatcherInstalled = false;
    private static volatile boolean registerVmHooked = false;

    private MariBankDfpHook() {
    }

    public static void installLate(XC_LoadPackage.LoadPackageParam lpparam) {
        hookRegisterViewModel(lpparam);
        hookKnownDfpClasses(lpparam);
        installLoadClassWatcher(lpparam);
    }

    private static void hookRegisterViewModel(XC_LoadPackage.LoadPackageParam lpparam) {
        if (registerVmHooked) {
            return;
        }
        String className = "com.shopee.bke.biz.user.viewmodel.RegisterViewModel";
        try {
            Class<?> clazz = XposedHelpers.findClass(className, lpparam.classLoader);
            registerVmHooked = true;
            for (Method method : clazz.getDeclaredMethods()) {
                XposedBridge.hookMethod(method, new XC_MethodHook() {
                    @Override
                    protected void beforeHookedMethod(MethodHookParam param) {
                        String args = param.args == null ? "[]" : Arrays.toString(param.args);
                        if (args.length() > 800) {
                            args = args.substring(0, 800) + "...";
                        }
                        XposedBridge.log(TAG + " RegisterViewModel."
                                + method.getName() + " args=" + args);
                    }

                    @Override
                    protected void afterHookedMethod(MethodHookParam param) {
                        Object result = param.getResult();
                        if (result == null) {
                            return;
                        }
                        String rs = String.valueOf(result);
                        if (rs.length() > 600) {
                            rs = rs.substring(0, 600) + "...";
                        }
                        if (rs.isEmpty()
                                || rs.toLowerCase().contains("dfp")
                                || rs.toLowerCase().contains("error")
                                || rs.toLowerCase().contains("unavailable")
                                || rs.contains("3100012")
                                || rs.contains("4067")) {
                            XposedBridge.log(TAG + " RegisterViewModel."
                                    + method.getName() + " result=" + rs);
                        }
                    }
                });
            }
            XposedBridge.log(TAG + " RegisterViewModel hooked methods="
                    + clazz.getDeclaredMethods().length);
        } catch (Throwable t) {
            XposedBridge.log(TAG + " skip RegisterViewModel: " + t.getMessage());
        }
    }

    /** 启动时类可能已加载，先尝试已知命名。 */
    private static void hookKnownDfpClasses(XC_LoadPackage.LoadPackageParam lpparam) {
        String[] candidates = {
                "com.shopee.bke.lib.jni.security.CharacterCryptoManager",
                "com.shopee.bke.lib.jni.security.CharacterCryptoManagerWrapper",
                "com.shopee.bke.lib.jni.utils.NativeEncryptUtilsWrapper",
        };
        for (String cn : candidates) {
            tryHookDfpClass(lpparam, cn);
        }
    }

    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("CharacterCrypto")
                                    || name.contains("NativeEncryptUtils")
                                    || (name.contains("dfp") && name.contains("bke"))) {
                                tryHookDfpClass(lpparam, name);
                            }
                        }
                    });
            XposedBridge.log(TAG + " ClassLoader dfp watcher OK");
        } catch (Throwable t) {
            XposedBridge.log(TAG + " skip ClassLoader watcher: " + t.getMessage());
        }
    }

    private static void tryHookDfpClass(XC_LoadPackage.LoadPackageParam lpparam, String className) {
        if (HOOKED_CLASSES.contains(className)) {
            return;
        }
        try {
            Class<?> clazz = XposedHelpers.findClass(className, lpparam.classLoader);
            int count = 0;
            for (Method method : clazz.getDeclaredMethods()) {
                String mn = method.getName();
                if (!mn.startsWith("getDfp") && !mn.contains("Fingerprint")) {
                    continue;
                }
                if (method.getReturnType() != String.class) {
                    continue;
                }
                XposedBridge.hookMethod(method, new XC_MethodHook() {
                    @Override
                    protected void afterHookedMethod(MethodHookParam param) {
                        Object result = param.getResult();
                        String s = result instanceof String ? (String) result : null;
                        if (s == null || s.isEmpty()) {
                            XposedBridge.log(TAG + " EMPTY " + className + "."
                                    + method.getName() + " → register may fail");
                        } else {
                            String tail = s.length() > 80
                                    ? s.substring(0, 40) + "..." + s.substring(s.length() - 20)
                                    : s;
                            XposedBridge.log(TAG + " " + className + "."
                                    + method.getName() + " len=" + s.length() + " val=" + tail);
                        }
                    }
                });
                count++;
            }
            if (count > 0) {
                HOOKED_CLASSES.add(className);
                XposedBridge.log(TAG + " hooked " + count + " dfp getters in " + className);
            }
        } catch (Throwable ignored) {
        }
    }

    /** 拦截 dfp 空检查日志源（含 register scene 文案的类方法）。 */
    public static void hookDfpEmptyGuards(XC_LoadPackage.LoadPackageParam lpparam) {
        try {
            XposedHelpers.findAndHookMethod(
                    "android.util.Log",
                    lpparam.classLoader,
                    "e",
                    String.class,
                    String.class,
                    new XC_MethodHook() {
                        @Override
                        protected void beforeHookedMethod(MethodHookParam param) {
                            String msg = param.args[1] instanceof String ? (String) param.args[1] : null;
                            if (msg == null) {
                                return;
                            }
                            String lower = msg.toLowerCase();
                            if (lower.contains("dfp is empty")
                                    || lower.contains("getdfp empty")
                                    || lower.contains("getdfp onerror")) {
                                XposedBridge.log(TAG + " Log.e: " + msg);
                                logStack();
                            }
                        }
                    });
        } catch (Throwable t) {
            XposedBridge.log(TAG + " skip Log.e dfp guard: " + t.getMessage());
        }
    }

    private static void logStack() {
        for (StackTraceElement frame : Thread.currentThread().getStackTrace()) {
            String cn = frame.getClassName();
            if (cn.startsWith("com.shopee.bke") || cn.contains("CharacterCrypto")) {
                XposedBridge.log(TAG + "   at " + cn + "." + frame.getMethodName());
            }
        }
    }
}
