'use strict';
/**
 * MariBank 注册 trace — attach 模式优先，聚焦 Java 层（OkHttp / Gson / 加密包装）
 */
const TAG = '[MB-TRACE]';
const MAX_STR = 2000;

function log(msg) {
  console.log(TAG + ' ' + msg);
}

function shouldLogUrl(url) {
  if (!url) return false;
  const u = String(url).toLowerCase();
  return u.indexOf('register') >= 0 || u.indexOf('dfp') >= 0
      || u.indexOf('risk') >= 0 || u.indexOf('uapi') >= 0;
}

function hexPreview(arr, limit) {
  const n = Math.min(arr.length, limit || 64);
  let hex = '';
  for (let i = 0; i < n; i++) {
    const b = (arr[i] & 0xff).toString(16);
    hex += (b.length === 1 ? '0' : '') + b;
  }
  if (arr.length > n) hex += '...';
  return hex;
}

function dumpJava(tag, obj) {
  if (obj === null || obj === undefined) {
    log(tag + ' = null');
    return;
  }
  try {
    const cls = obj.getClass().getName();
    if (cls === '[B') {
      const arr = Java.cast(obj, Java.use('[B'));
      let text = '';
      try {
        text = Java.use('java.lang.String').$new(arr, 'UTF-8').toString();
      } catch (e) {
        text = '<bin>';
      }
      const show = text.length > MAX_STR ? text.substring(0, MAX_STR) + '...' : text;
      log(tag + ' byte[' + arr.length + '] hex=' + hexPreview(arr, 48) + ' text=' + show);
      return;
    }
    if (cls === 'java.lang.String') {
      const s = Java.cast(obj, Java.use('java.lang.String')).toString();
      const show = s.length > MAX_STR ? s.substring(0, MAX_STR) + '...' : s;
      log(tag + ' String(' + s.length + ') ' + show);
      return;
    }
    log(tag + ' ' + cls + ' = ' + obj.toString());
  } catch (e) {
    log(tag + ' err=' + e);
  }
}

function hookOkHttp() {
  const RealCall = Java.use('okhttp3.RealCall');
  const orig = RealCall.execute;
  RealCall.execute.implementation = function () {
    const req = this.request();
    const url = req.url().toString();
    const method = req.method();
    if (shouldLogUrl(url)) {
      log('HTTP >> ' + method + ' ' + url);
      try {
        const body = req.body();
        if (body) {
          const Buffer = Java.use('okio.Buffer');
          const buf = Buffer.$new();
          body.writeTo(buf);
          const bytes = buf.readByteArray();
          if (bytes) dumpJava('  reqBody', Java.array('byte', bytes));
        }
      } catch (e) {
        log('  reqBody err: ' + e);
      }
    }
    const resp = orig.call(this);
    if (shouldLogUrl(url)) {
      try {
        const peek = resp.peekBody(Java.use('java.lang.Long').parseLong('1048576'));
        const s = peek.string();
        const show = s.length > MAX_STR ? s.substring(0, MAX_STR) + '...' : s;
        log('HTTP << ' + resp.code() + ' ' + show);
      } catch (e) {
        log('HTTP resp err: ' + e);
      }
    }
    return resp;
  };
  log('hooked RealCall.execute');
}

function hookGson() {
  const Gson = Java.use('com.google.gson.Gson');
  Gson.toJson.overload('java.lang.Object').implementation = function (obj) {
    const ret = this.toJson(obj);
    if (ret) {
      const low = ret.toLowerCase();
      if (low.indexOf('mobile') >= 0 || low.indexOf('phone') >= 0
          || low.indexOf('risktoken') >= 0 || low.indexOf('register') >= 0
          || low.indexOf('4067') >= 0) {
        const show = ret.length > MAX_STR ? ret.substring(0, MAX_STR) + '...' : ret;
        log('Gson.toJson ' + show);
      }
    }
    return ret;
  };
  log('hooked Gson.toJson');
}

function hookRisk() {
  const vv = Java.use('com.shopee.shpssdkbank.uwuvuvvww.vvuuuuvvv');
  vv.wwvuwuwvu.overload('android.content.Context').implementation = function (ctx) {
    const ret = this.wwvuwuwvu(ctx);
    dumpJava('riskToken', ret);
    return ret;
  };
  log('hooked vvuuuuvvv.wwvuwuwvu');
}

function hookEncryptWrapper() {
  const D = Java.use('com.shopee.bke.lib.jni.utils.d');
  const methods = D.class.getDeclaredMethods();
  for (let i = 0; i < methods.length; i++) {
    const m = methods[i];
    const name = m.getName();
    if (m.getModifiers() & 0x0100) continue;
    try {
      D[name].overloads.forEach(function (ovl) {
        ovl.implementation = function () {
          const args = [].slice.call(arguments);
          log('>> EncryptWrapper.' + name);
          args.forEach(function (a, idx) { dumpJava('  in' + idx, a); });
          const ret = ovl.apply(this, args);
          if (ret && ret.getClass) {
            const cn = ret.getClass().getName();
            if (cn === '[Ljava.lang.String;') {
              const arr = Java.cast(ret, Java.use('[Ljava.lang.String;'));
              for (let j = 0; j < arr.length; j++) dumpJava('  out' + j, arr[j]);
            } else {
              dumpJava('  ret', ret);
            }
          }
          return ret;
        };
      });
    } catch (e) {}
  }
  log('hooked NativeEncryptUtilsWrapper (utils.d)');
}

function installAll() {
  Java.perform(function () {
    log('Java.perform OK pid=' + Process.id);
    try { hookOkHttp(); } catch (e) { log('okhttp fail: ' + e); }
    try { hookGson(); } catch (e) { log('gson fail: ' + e); }
    try { hookRisk(); } catch (e) { log('risk fail: ' + e); }
    try { hookEncryptWrapper(); } catch (e) { log('encrypt fail: ' + e); }
    log('READY — 请在 App 输入号码点 Next');
  });
}

function waitForJava(n) {
  n = n || 0;
  if (typeof Java === 'undefined' || !Java.available) {
    if (n % 5 === 0) log('waiting Java.available attempt=' + n);
    setTimeout(function () { waitForJava(n + 1); }, 500);
    return;
  }
  installAll();
}

setImmediate(function () {
  log('script loaded pid=' + Process.id);
  waitForJava(0);
});
