package com.example.jso

import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.os.Build
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
import java.io.File
import java.net.InetSocketAddress
import java.net.Socket
import java.security.MessageDigest
import java.util.Locale

/**
 * Self-contained Android RASP probe. Change the package name and register
 * JsoRaspPackage in MainApplication. For signature pinning add manifest data:
 * <meta-data android:name="com.javascriptobfuscator.JSO_EXPECTED_CERT_SHA256"
 *            android:value="YOUR_RELEASE_CERT_SHA256" />
 */
class JsoRaspModule(private val context: ReactApplicationContext) :
    ReactContextBaseJavaModule(context) {

    override fun getName() = "JsoRasp"

    @ReactMethod
    fun probe(promise: Promise) {
        try {
            val maps = runCatching { File("/proc/self/maps").readText().lowercase(Locale.US) }.getOrDefault("")
            val tools = linkedSetOf<String>()
            if (rootArtifacts().any { File(it).exists() } || canExecuteSu()) tools += "su"
            if (maps.contains("frida") || localPortOpen(27042) || localPortOpen(27043)) tools += "Frida"
            if (maps.contains("xposed") || classPresent("de.robv.android.xposed.XposedBridge")) tools += "Xposed"
            if (rootArtifacts().any { it.contains("magisk") && File(it).exists() } || maps.contains("magisk")) tools += "Magisk"

            val result = Arguments.createMap()
            result.putString("platform", "android")
            result.putBoolean("isRooted", tools.contains("su") || tools.contains("Magisk"))
            result.putBoolean("isDebuggable", (context.applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE) != 0 || android.os.Debug.isDebuggerConnected())
            result.putBoolean("isEmulator", isEmulator())
            result.putBoolean("hooksDetected", tools.contains("Frida") || tools.contains("Xposed"))
            result.putBoolean("signatureTampered", !signatureMatchesConfiguredPin())
            result.putBoolean("unofficialStore", !installedFromApprovedStore())
            val detected = Arguments.createArray()
            tools.forEach(detected::pushString)
            result.putArray("detectedTools", detected)
            promise.resolve(result)
        } catch (error: Exception) {
            promise.reject("JSO_RASP_ERROR", error.message, error)
        }
    }

    private fun rootArtifacts() = arrayOf(
        "/system/app/Superuser.apk", "/system/bin/su", "/system/xbin/su", "/sbin/su",
        "/su/bin/su", "/data/local/su", "/data/local/bin/su", "/data/local/xbin/su",
        "/sbin/.magisk", "/data/adb/magisk", "/data/adb/modules"
    )

    private fun canExecuteSu(): Boolean = runCatching {
        val process = Runtime.getRuntime().exec(arrayOf("sh", "-c", "command -v su"))
        val found = process.inputStream.bufferedReader().readLine()?.isNotBlank() == true
        process.destroy()
        found
    }.getOrDefault(false)

    private fun classPresent(name: String) = runCatching { Class.forName(name); true }.getOrDefault(false)

    private fun localPortOpen(port: Int): Boolean = runCatching {
        Socket().use { it.connect(InetSocketAddress("127.0.0.1", port), 75) }
        true
    }.getOrDefault(false)

    private fun isEmulator(): Boolean {
        val fingerprint = Build.FINGERPRINT.lowercase(Locale.US)
        val model = Build.MODEL.lowercase(Locale.US)
        val product = Build.PRODUCT.lowercase(Locale.US)
        return fingerprint.startsWith("generic") || fingerprint.contains("vbox") || fingerprint.contains("test-keys") ||
            model.contains("google_sdk") || model.contains("emulator") || model.contains("android sdk built for") ||
            product.contains("sdk") || product.contains("genymotion") || Build.HARDWARE.contains("goldfish") || Build.HARDWARE.contains("ranchu")
    }

    private fun configuredCertificatePin(): String? {
        val info = context.packageManager.getApplicationInfo(context.packageName, PackageManager.GET_META_DATA)
        return info.metaData?.getString("com.javascriptobfuscator.JSO_EXPECTED_CERT_SHA256")
            ?.replace(":", "")?.uppercase(Locale.US)?.takeIf { it.isNotBlank() }
    }

    @Suppress("DEPRECATION")
    private fun signatureMatchesConfiguredPin(): Boolean {
        val expected = configuredCertificatePin() ?: return true
        val signatures = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            val info = context.packageManager.getPackageInfo(context.packageName, PackageManager.GET_SIGNING_CERTIFICATES)
            val signing = info.signingInfo ?: return false
            if (signing.hasMultipleSigners()) signing.apkContentsSigners else signing.signingCertificateHistory
        } else {
            context.packageManager.getPackageInfo(context.packageName, PackageManager.GET_SIGNATURES).signatures
        }
        return signatures.any { signature ->
            MessageDigest.getInstance("SHA-256").digest(signature.toByteArray()).joinToString("") { "%02X".format(it) } == expected
        }
    }

    @Suppress("DEPRECATION")
    private fun installedFromApprovedStore(): Boolean {
        val installer = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            runCatching { context.packageManager.getInstallSourceInfo(context.packageName).installingPackageName }.getOrNull()
        } else context.packageManager.getInstallerPackageName(context.packageName)
        return installer == null || installer in setOf("com.android.vending", "com.amazon.venezia", "com.sec.android.app.samsungapps")
    }
}
