package com.miraclegarden.smsmessage.xposed.hook;

import android.util.Base64;

import org.json.JSONArray;
import org.json.JSONObject;

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import de.robv.android.xposed.XposedBridge;

/**
 * 解析 {@code vuwuuwvw} 返回的 obfuscated-key JSON（key 多为 8 位 hex）。
 */
final class MariBankAttestationJsonUtil {

    private static final String TAG = "notiMessageHook/MariBankAttestJson";
    private static final Pattern HEX_KEY = Pattern.compile("^[0-9a-fA-F]{8}$");
    private static final Pattern SUSPICIOUS = Pattern.compile(
            "(root|hook|xposed|lsposed|magisk|frida|substrate|emulator|debug|adb|"
                    + "selinux|su\\b|/proc/|zygisk|riru|shamiko|tamper|integrity|"
                    + "jailbreak|virtual|mock|proxy|vpn|developer)",
            Pattern.CASE_INSENSITIVE);

    private MariBankAttestationJsonUtil() {
    }

    static void logVuwuuwvwCall(byte[][] inputs, String output) {
        if (inputs != null) {
            for (int i = 0; i < inputs.length; i++) {
                if (inputs[i] != null && inputs[i].length > 0) {
                    MariBankCaptureUtil.logBytes("vuwuuwvw.in" + i, inputs[i]);
                }
            }
        }
        if (output == null || output.isEmpty()) {
            return;
        }
        boolean registerLike = isRegisterAttestationCall(inputs, output);
        String label = registerLike ? "vuwuuwvw.out REGISTER" : "vuwuuwvw.out";
        MariBankCaptureUtil.logText(label, output);
        parseAndSummarize(output, registerLike);
    }

    static boolean isRegisterAttestationCall(byte[][] inputs, String output) {
        if (inputs != null) {
            for (byte[] in : inputs) {
                if (in != null && in.length >= 200 && in.length <= 400) {
                    return true;
                }
            }
        }
        return output != null && output.length() >= 2200 && output.length() <= 2500;
    }

    static void parseAndSummarize(String json, boolean highlight) {
        try {
            JSONObject obj = new JSONObject(json);
            List<String> keys = new ArrayList<>();
            Iterator<String> it = obj.keys();
            while (it.hasNext()) {
                keys.add(it.next());
            }
            Collections.sort(keys);
            XposedBridge.log(TAG + " keys=" + keys.size()
                    + (highlight ? " [REGISTER-LIKE]" : "")
                    + " sample=" + keys.subList(0, Math.min(6, keys.size())));

            List<String> hits = new ArrayList<>();
            for (String key : keys) {
                Object val = obj.get(key);
                scanValue(key, val, hits);
            }
            if (!hits.isEmpty()) {
                XposedBridge.log(TAG + " SUSPICIOUS count=" + hits.size());
                for (int i = 0; i < Math.min(hits.size(), 24); i++) {
                    XposedBridge.log(TAG + "  " + hits.get(i));
                }
            } else {
                XposedBridge.log(TAG + " no plain suspicious strings in values");
            }
            logKeyGuesses(keys);
        } catch (Throwable t) {
            XposedBridge.log(TAG + " parse fail: " + t.getMessage()
                    + " head=" + json.substring(0, Math.min(120, json.length())));
        }
    }

    private static void scanValue(String key, Object val, List<String> hits) {
        if (val instanceof String) {
            String s = (String) val;
            noteIfSuspicious(key, "str", s, hits);
            if (s.length() >= 8 && s.length() <= 512 && looksBase64(s)) {
                byte[] decoded = tryBase64(s);
                if (decoded != null) {
                    String inner = new String(decoded, StandardCharsets.UTF_8);
                    if (isMostlyPrintable(inner)) {
                        noteIfSuspicious(key, "b64utf8", inner, hits);
                    } else {
                        noteIfSuspicious(key, "b64hex", hexPreview(decoded, 32), hits);
                    }
                }
            }
            return;
        }
        if (val instanceof Number || val instanceof Boolean) {
            String s = String.valueOf(val);
            if ("1".equals(s) || "true".equalsIgnoreCase(s)) {
                hits.add(key + " =" + s + " (flag?)");
            }
            return;
        }
        if (val instanceof JSONObject) {
            JSONObject nested = (JSONObject) val;
            Iterator<String> it = nested.keys();
            while (it.hasNext()) {
                String nk = it.next();
                try {
                    scanValue(key + "." + nk, nested.get(nk), hits);
                } catch (Throwable ignored) {
                }
            }
            return;
        }
        if (val instanceof JSONArray) {
            JSONArray arr = (JSONArray) val;
            for (int i = 0; i < arr.length(); i++) {
                try {
                    scanValue(key + "[" + i + "]", arr.get(i), hits);
                } catch (Throwable ignored) {
                }
            }
        }
    }

    private static void noteIfSuspicious(String key, String kind, String text, List<String> hits) {
        if (text == null || text.isEmpty()) {
            return;
        }
        Matcher m = SUSPICIOUS.matcher(text);
        if (m.find()) {
            String show = text.length() > 120 ? text.substring(0, 120) + "..." : text;
            hits.add(key + " " + kind + " hit=" + m.group().toLowerCase(Locale.ROOT) + " val=" + show);
        }
    }

    private static void logKeyGuesses(List<String> keys) {
        Set<String> candidates = knownFieldNames();
        List<String> matched = new ArrayList<>();
        for (String key : keys) {
            if (!HEX_KEY.matcher(key).matches()) {
                continue;
            }
            String lower = key.toLowerCase(Locale.ROOT);
            for (String name : candidates) {
                if (hashKey(name).equals(lower)) {
                    matched.add(key + "=>" + name);
                }
            }
        }
        if (!matched.isEmpty()) {
            XposedBridge.log(TAG + " key guesses: " + matched);
        }
    }

    private static Set<String> knownFieldNames() {
        Set<String> names = new LinkedHashSet<>();
        String[] base = {
                "root", "hook", "xposed", "lsposed", "magisk", "frida", "adb", "debug",
                "debuggable", "emulator", "simulator", "vpn", "proxy", "mock",
                "selinux", "su", "supersu", "zygisk", "riru", "shamiko", "substrate",
                "integrity", "safetynet", "playIntegrity", "deviceId", "androidId",
                "serial", "fingerprint", "model", "brand", "manufacturer", "board",
                "host", "tags", "type", "user", "display", "product", "hardware",
                "usb", "wifi", "adb_enabled", "development_settings_enabled",
                "RISK_ROOT", "RISK_HOOK", "RISK_USB_ADB", "RISK_WIFI_ADB", "RISK_ADB",
                "RISK_EMULATOR", "RISK_DEBUG", "RISK_VPN", "RISK_PROXY", "RISK_MOCK",
                "rdVerifyInfo", "deviceFingerprint", "data", "dataKey", "riskToken",
                "isRoot", "isHook", "isDebug", "isAdb", "isEmulator", "isVirtual",
                "tamper", "jailbreak", "bootloader", "verifiedbootstate", "vbmeta",
                "init.svc.adbd", "/proc/self/maps", "RealInterceptorChain",
        };
        Collections.addAll(names, base);
        return names;
    }

    private static String hashKey(String name) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] dig = md.digest(name.getBytes(StandardCharsets.UTF_8));
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < 4; i++) {
                sb.append(String.format(Locale.ROOT, "%02x", dig[i] & 0xff));
            }
            return sb.toString();
        } catch (Throwable t) {
            return "";
        }
    }

    private static boolean looksBase64(String s) {
        return s.matches("^[A-Za-z0-9+/=]+$");
    }

    private static byte[] tryBase64(String s) {
        try {
            return Base64.decode(s, Base64.DEFAULT);
        } catch (Throwable t) {
            return null;
        }
    }

    private static boolean isMostlyPrintable(String s) {
        if (s == null || s.isEmpty()) {
            return false;
        }
        int ok = 0;
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if (c >= 0x20 && c < 0x7f) {
                ok++;
            }
        }
        return ok * 100 / s.length() >= 85;
    }

    private static String hexPreview(byte[] b, int max) {
        if (b == null) {
            return "";
        }
        int n = Math.min(b.length, max);
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < n; i++) {
            sb.append(String.format(Locale.ROOT, "%02x", b[i] & 0xff));
        }
        if (b.length > n) {
            sb.append("...");
        }
        return sb.toString();
    }
}
