package eu.darken.sdmse.common.adb.shizuku

import android.content.Context
import android.content.pm.PackageManager
import android.os.Handler
import android.os.HandlerThread
import dagger.hilt.android.qualifiers.ApplicationContext
import eu.darken.sdmse.common.coroutine.DispatcherProvider
import eu.darken.sdmse.common.debug.logging.Logging.Priority.WARN
import eu.darken.sdmse.common.debug.logging.asLog
import eu.darken.sdmse.common.debug.logging.log
import eu.darken.sdmse.common.debug.logging.logTag
import eu.darken.sdmse.common.flow.setupCommonEventHandlers
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.channels.trySendBlocking
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext
import rikka.shizuku.Shizuku
import rikka.shizuku.ShizukuBinderWrapper
import rikka.shizuku.ShizukuProvider
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class ShizukuWrapper @Inject constructor(
    @ApplicationContext private val context: Context,
    private val dispatcherProvider: DispatcherProvider,
) {

    /**
     * Package that declares the Shizuku permission, or null if no installed app declares it.
     *
     * Detects Shizuku via its permission ([ShizukuProvider.PERMISSION]) instead of a fixed package
     * name. The permission name is shared across Shizuku forks, so this keeps working when a fork
     * hides its package from enumeration ("Hide Shizuku from other apps") or ships under a different
     * package name. Permissions live in a global namespace, so the lookup isn't subject to the
     * package-visibility filtering that hides the app itself.
     */
    suspend fun getManagerPackage(): String? = withContext(dispatcherProvider.IO) {
        try {
            context.packageManager
                .getPermissionInfo(ShizukuProvider.PERMISSION, 0)
                .packageName
                ?.takeUnless { it.isBlank() }
        } catch (e: PackageManager.NameNotFoundException) {
            log(TAG) { "getManagerPackage(): Shizuku permission not declared by any app" }
            null
        } catch (e: CancellationException) {
            throw e
        } catch (e: Exception) {
            log(TAG, WARN) { "getManagerPackage(): Lookup failed: ${e.asLog()}" }
            null
        }
    }

    private val handlerThread: HandlerThread by lazy {
        HandlerThread("shizuku:binder-handler")
    }
    private val handler: Handler by lazy {
        handlerThread.start()
        Handler(handlerThread.looper)
    }

    val baseServiceBinder: Flow<ShizukuBaseServiceBinder?> = callbackFlow {
        val sendBinder = {
            val binder = Shizuku.getBinder()
            log(TAG) { "Sending binder: $binder" }
            trySendBlocking(binder?.let { ShizukuBinderWrapper(it) })
        }

        val onReceive = Shizuku.OnBinderReceivedListener {
            log(TAG) { "binderFlow(): OnBinderReceivedListener" }
            sendBinder()
        }
        val onDead = Shizuku.OnBinderDeadListener {
            log(TAG) { "binderFlow(): OnBinderDeadListener :(" }
            sendBinder()
        }
        log(TAG) { "binderFlow(): Registering..." }

        Shizuku.addBinderReceivedListener(onReceive, handler)
        Shizuku.addBinderDeadListener(onDead, handler)

        sendBinder()

        log(TAG) { "binderFlow(): Awaiting close" }
        awaitClose {
            log(TAG) { "binderFlow(): Closing..." }
            Shizuku.removeBinderReceivedListener(onReceive)
            Shizuku.removeBinderDeadListener(onDead)
        }
    }
        .map { binder -> binder?.let { ShizukuBaseServiceBinder(it) } }


    val permissionGrantEvents: Flow<ShizukuPermissionRequest> = callbackFlow {
        val requestListener = Shizuku.OnRequestPermissionResultListener { requestCode, grantResult ->
            log(TAG) { "permissionFlow(): Event: $requestCode -> $grantResult" }
            trySendBlocking(ShizukuPermissionRequest(requestCode = requestCode, grantResult = grantResult))
        }

        log(TAG) { "permissionFlow(): Registering..." }
        Shizuku.addRequestPermissionResultListener(requestListener, handler)

        log(TAG) { "permissionFlow(): Awaiting close" }
        awaitClose {
            log(TAG) { "permissionFlow(): Closing..." }
            Shizuku.removeRequestPermissionResultListener(requestListener)
        }
    }
        .setupCommonEventHandlers(TAG) { "grantEvents" }

    data class ShizukuPermissionRequest(
        val requestCode: Int,
        val grantResult: Int,
    )

    // Seams for the two Shizuku statics behind isGranted(). Mirrors the AdbHostLauncher seam: the
    // Shizuku statics are untouchable in JVM unit tests, even via mockkStatic, because the class
    // initializer builds a Handler on the main Looper. Overridden in tests, never in production.
    internal var pingBinderAction: () -> Boolean = { Shizuku.pingBinder() }
    internal var checkSelfPermissionAction: () -> Int = { Shizuku.checkSelfPermission() }

    private fun pingBinderSafe(): Boolean = try {
        pingBinderAction()
    } catch (e: NullPointerException) {
        // Upstream race: the binder can be nulled between Shizuku's null check and the ping.
        false
    }

    suspend fun isGranted(): Boolean? = withContext(dispatcherProvider.IO) {
        // Shizuku.checkSelfPermission() latches its granted state process-wide and never clears it on
        // binder death, so without this gate it reports a stale `true` with no live binder behind it.
        // null already means "cannot know" (see the ISE catch below), which covers this case too.
        if (!pingBinderSafe()) {
            log(TAG) { "isGranted()=null (binder not alive)" }
            return@withContext null
        }
        val granted = try {
            checkSelfPermissionAction() == PackageManager.PERMISSION_GRANTED
        } catch (e: IllegalStateException) {
            log(TAG, WARN) { "isGranted(): $e" }
            null
        }
        log(TAG) { "isGranted()=$granted" }
        granted
    }


    suspend fun isCompatible(): Boolean {
        return !Shizuku.isPreV11()
    }

    suspend fun requestPermission() = withContext(dispatcherProvider.IO) {
        log(TAG) { "requestPermission()" }
        Shizuku.requestPermission(433)
    }

    companion object {
        private val TAG = logTag("ADB", "Shizuku", "Wrapper")
    }

}