package com.meowarex.rlmobile.patcher.util

import android.Manifest
import android.os.Build
import pxb.android.axml.*

object ManifestPatcher {
    private const val ANDROID_NAMESPACE = "http://schemas.android.com/apk/res/android"
    private const val DEBUGGABLE = "debuggable"
    private const val VM_SAFE_MODE = "vmSafeMode"
    private const val USE_EMBEDDED_DEX = "useEmbeddedDex"
    private const val EXTRACT_NATIVE_LIBS = "extractNativeLibs"
    private const val REQUEST_LEGACY_EXTERNAL_STORAGE = "requestLegacyExternalStorage"
    private const val LABEL = "label"
    private const val PACKAGE = "package"
    private const val COMPILE_SDK_VERSION = "compileSdkVersion"
    private const val COMPILE_SDK_VERSION_CODENAME = "compileSdkVersionCodename"
    private const val IS_SPLIT_REQUIRED = "isSplitRequired"
    private const val REQUIRED_SPLIT_TYPES = "requiredSplitTypes"
    private const val SPLIT_TYPES = "splitTypes"
    private const val WAZE_PACKAGE = "com.waze"
    private const val WAZE_INIT_RECEIVER = "radiant.WazeInitReceiver"
    private const val WAZE_ACTION_INIT = "com.waze.sdk.audio.ACTION_INIT"

    fun patchManifest(
        manifestBytes: ByteArray,
        packageName: String,
        appName: String,
        debuggable: Boolean,
        enableWazeIntegration: Boolean,
    ): ByteArray {
        // Extract original package name to rewrite every reference to it
        // (permissions, provider authorities) to the new packageName.
        var originalPackage: String? = null
        AxmlReader(manifestBytes).accept(object : AxmlVisitor() {
            override fun child(ns: String?, name: String?) = object : NodeVisitor() {
                override fun attr(ns: String?, name: String?, resourceId: Int, type: Int, value: Any?) {
                    if (name == PACKAGE && value is String) originalPackage = value
                }
            }
        })
        val origPkg = originalPackage
            ?: throw IllegalStateException("Manifest has no package attribute; refusing to rewrite authorities/permissions without a known originalPackage")

        val reader = AxmlReader(manifestBytes)
        val writer = AxmlWriter()

        reader.accept(object : AxmlVisitor(writer) {
            // Without this, decompiling the finished manifest has the android namespace
            // under an autogenerated name like axml_00 or something.
            override fun ns(prefix: String?, uri: String?, ln: Int) {
                val realUri = uri ?: ANDROID_NAMESPACE
                super.ns(prefix, realUri, ln)
            }

            override fun child(ns: String?, name: String?) =
                object : ReplaceAttrsVisitor(
                    super.child(ns, name),
                    mapOf(
                        PACKAGE to packageName,
                        COMPILE_SDK_VERSION to 23,
                        COMPILE_SDK_VERSION_CODENAME to "6.0-2438415"
                    )
                ) {
                    // Drop split-only manifest attributes because merged all splits into one APK
                    override fun attr(ns: String?, name: String, resourceId: Int, type: Int, value: Any?) {
                        if (name == IS_SPLIT_REQUIRED || name == REQUIRED_SPLIT_TYPES || name == SPLIT_TYPES) return
                        super.attr(ns, name, resourceId, type, value)
                    }

                    private var addExternalStoragePerm = false

                    override fun child(ns: String?, name: String): NodeVisitor {
                        val nv = super.child(ns, name)

                        // Add MANAGE_EXTERNAL_STORAGE when necessary
                        if (addExternalStoragePerm) {
                            super
                                .child(null, "uses-permission")
                                .attr(
                                    ANDROID_NAMESPACE,
                                    "name",
                                    android.R.attr.name,
                                    TYPE_STRING,
                                    Manifest.permission.MANAGE_EXTERNAL_STORAGE
                                )
                            addExternalStoragePerm = false
                        }

                        return when (name) {
                            "queries" -> object : NodeVisitor(nv) {
                                private var hasWazePackage = false

                                override fun child(ns: String?, name: String): NodeVisitor {
                                    val visitor = super.child(ns, name)

                                    return if (enableWazeIntegration && name == "package") {
                                        object : NodeVisitor(visitor) {
                                            override fun attr(
                                                ns: String?,
                                                name: String?,
                                                resourceId: Int,
                                                type: Int,
                                                value: Any?,
                                            ) {
                                                if (name == "name" && value == WAZE_PACKAGE) {
                                                    hasWazePackage = true
                                                }
                                                super.attr(ns, name, resourceId, type, value)
                                            }
                                        }
                                    } else {
                                        visitor
                                    }
                                }

                                override fun end() {
                                    if (enableWazeIntegration && !hasWazePackage) {
                                        super.child(null, "package").apply {
                                            attr(
                                                ANDROID_NAMESPACE,
                                                "name",
                                                android.R.attr.name,
                                                TYPE_STRING,
                                                WAZE_PACKAGE,
                                            )
                                            end()
                                        }
                                    }
                                    super.end()
                                }
                            }

                            "uses-permission" -> object : NodeVisitor(nv) {
                                override fun attr(ns: String?, name: String?, resourceId: Int, type: Int, value: Any?) {
                                    if (name != "maxSdkVersion") {
                                        super.attr(ns, name, resourceId, type, value)
                                    }

                                    if (name == "name" && value == Manifest.permission.READ_EXTERNAL_STORAGE) {
                                        addExternalStoragePerm = true
                                    }
                                }
                            }

                            // Preserve the original targetSdkVersion. Downgrading it (e.g. to 30)
                            // makes Android render MediaStyle notifications with the legacy
                            // compact-row layout instead of the modern auto-rendered controls,
                            // because TIDAL's notification builder skips addAction() on API 33+
                            // expecting the system to auto-infer buttons from the MediaSession —
                            // which only happens for apps targeting SDK >= 31.
                            "uses-sdk" -> nv

                            "permission" -> object : NodeVisitor(nv) {
                                override fun attr(ns: String?, name: String, resourceId: Int, type: Int, value: Any?) {
                                    super.attr(
                                        ns, name, resourceId, type,
                                        when (name) {
                                            "name" -> (value as? String)?.replace(origPkg, packageName) ?: value
                                            else -> value
                                        }
                                    )
                                }
                            }

                            "application" -> object : ReplaceAttrsVisitor(
                                nv,
                                mapOf(
                                    LABEL to appName,
                                    DEBUGGABLE to debuggable,
                                    REQUEST_LEGACY_EXTERNAL_STORAGE to true,
                                    VM_SAFE_MODE to true,
                                    USE_EMBEDDED_DEX to true,
                                    EXTRACT_NATIVE_LIBS to false,
                                )
                            ) {
                                private var addDebuggable = debuggable
                                private var addLegacyStorage = true
                                private var addUseEmbeddedDex = true
                                private var addExtractNativeLibs = true
                                private var addMetadata = true
                                private var hasWazeInitReceiver = false

                                override fun attr(ns: String?, name: String, resourceId: Int, type: Int, value: Any?) {
                                    if (name == REQUEST_LEGACY_EXTERNAL_STORAGE) addLegacyStorage = false
                                    if (name == USE_EMBEDDED_DEX) addUseEmbeddedDex = false
                                    if (name == EXTRACT_NATIVE_LIBS) addExtractNativeLibs = false
                                    if (name == DEBUGGABLE) addDebuggable = false
                                    super.attr(ns, name, resourceId, type, value)
                                }

                                override fun child(ns: String?, name: String): NodeVisitor {
                                    val visitor = super.child(ns, name)

                                    // Adds a <meta-data> tag to make multi-install detection by HomeScreen work
                                    if (addMetadata) {
                                        addMetadata = false
                                        super.child(ANDROID_NAMESPACE, "meta-data").apply {
                                            attr(
                                                ANDROID_NAMESPACE,
                                                "name",
                                                android.R.attr.name,
                                                TYPE_STRING,
                                                "isRadiantLyrics"
                                            )
                                            attr(ANDROID_NAMESPACE, "value", android.R.attr.value, TYPE_INT_BOOLEAN, 1)
                                        }
                                    }

                                    return when (name) {
                                        "activity" -> ReplaceAttrsVisitor(visitor, mapOf("label" to appName))

                                        "receiver" -> object : NodeVisitor(visitor) {
                                            override fun attr(
                                                ns: String?,
                                                name: String,
                                                resourceId: Int,
                                                type: Int,
                                                value: Any?
                                            ) {
                                                if (name == "name" && value == WAZE_INIT_RECEIVER) {
                                                    hasWazeInitReceiver = true
                                                }
                                                super.attr(ns, name, resourceId, type, value)
                                            }
                                        }

                                        "provider" -> object : NodeVisitor(visitor) {
                                            override fun attr(
                                                ns: String?,
                                                name: String,
                                                resourceId: Int,
                                                type: Int,
                                                value: Any?
                                            ) {
                                                super.attr(
                                                    ns, name, resourceId, type,
                                                    if (name == "authorities") {
                                                        (value as? String)?.replace(origPkg, packageName) ?: value
                                                    } else {
                                                        value
                                                    }
                                                )
                                            }
                                        }

                                        else -> visitor
                                    }
                                }

                                override fun end() {
                                    if (addLegacyStorage && Build.VERSION.SDK_INT >= 29) super.attr(
                                        ANDROID_NAMESPACE,
                                        REQUEST_LEGACY_EXTERNAL_STORAGE,
                                        android.R.attr.requestLegacyExternalStorage,
                                        TYPE_INT_BOOLEAN,
                                        1
                                    )
                                    if (addDebuggable) super.attr(
                                        ANDROID_NAMESPACE,
                                        DEBUGGABLE,
                                        android.R.attr.debuggable,
                                        TYPE_INT_BOOLEAN,
                                        1
                                    )

                                    // Disable AOT (Necessary for AOSP Android 15)
                                    if (Build.VERSION.SDK_INT >= 29 && addUseEmbeddedDex) {
                                        super.attr(
                                            ANDROID_NAMESPACE,
                                            USE_EMBEDDED_DEX,
                                            android.R.attr.useEmbeddedDex,
                                            TYPE_INT_BOOLEAN,
                                            1
                                        )
                                    }

                                    if (addExtractNativeLibs) super.attr(
                                        ANDROID_NAMESPACE,
                                        EXTRACT_NATIVE_LIBS,
                                        android.R.attr.extractNativeLibs,
                                        TYPE_INT_BOOLEAN,
                                        0
                                    )

                                    if (enableWazeIntegration && !hasWazeInitReceiver) {
                                        super.child(null, "receiver").apply {
                                            attr(
                                                ANDROID_NAMESPACE,
                                                "name",
                                                android.R.attr.name,
                                                TYPE_STRING,
                                                WAZE_INIT_RECEIVER,
                                            )
                                            attr(
                                                ANDROID_NAMESPACE,
                                                "exported",
                                                android.R.attr.exported,
                                                TYPE_INT_BOOLEAN,
                                                1,
                                            )
                                            child(null, "intent-filter").apply {
                                                child(null, "action").apply {
                                                    attr(
                                                        ANDROID_NAMESPACE,
                                                        "name",
                                                        android.R.attr.name,
                                                        TYPE_STRING,
                                                        WAZE_ACTION_INIT,
                                                    )
                                                    end()
                                                }
                                                end()
                                            }
                                            end()
                                        }
                                    }

                                    super.end()
                                }
                            }

                            else -> nv
                        }
                    }
                }
        })

        return writer.toByteArray()
    }

    private open class ReplaceAttrsVisitor(
        nv: NodeVisitor,
        private val attrs: Map<String, Any>,
    ) : NodeVisitor(nv) {
        override fun attr(ns: String?, name: String, resourceId: Int, type: Int, value: Any?) {
            val replace = attrs.containsKey(name)
            val newValue = attrs[name]

            super.attr(
                ns,
                name,
                resourceId,
                if (newValue is String) TYPE_STRING else type,
                if (replace) newValue else value
            )
        }
    }
}
