package com.fabledsword.minstrel.player

import android.content.Context
import android.net.wifi.WifiManager
import android.os.Build
import android.os.PowerManager
import timber.log.Timber

/**
 * Holds a WiFi + CPU wake lock for the lifetime of an active UPnP cast.
 *
 * While casting, the wrapped ExoPlayer is paused, so its automatic
 * `WAKE_MODE_NETWORK` locks are released and nothing keeps the phone's radio
 * awake. On a locked, on-battery phone the WiFi enters power-save within
 * seconds and the CPU dozes; that stalls the 1 Hz liveness poll to the
 * renderer and the queue-extend calls to the server. The poll then trips the
 * drop threshold and playback falls back to a phone that *also* has no
 * network — silence, while the renderer was streaming fine the whole time.
 *
 * A high-performance / low-latency [WifiManager.WifiLock] keeps the radio out
 * of power-save, and a partial [PowerManager.WakeLock] keeps the poll
 * coroutine scheduled. Both are acquired when a route goes active and released
 * the moment it drops or the user switches back to the phone.
 *
 * All methods are idempotent (`setReferenceCounted(false)` + held-checks) and
 * synchronized, so they are safe to call from any thread.
 */
class CastNetworkLock(context: Context) {

    private val appContext = context.applicationContext

    private val wifiLock: WifiManager.WifiLock? =
        (appContext.getSystemService(Context.WIFI_SERVICE) as? WifiManager)
            ?.createWifiLock(wifiLockMode(), LOCK_TAG)
            ?.apply { setReferenceCounted(false) }

    private val wakeLock: PowerManager.WakeLock? =
        (appContext.getSystemService(Context.POWER_SERVICE) as? PowerManager)
            ?.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOCK_TAG)
            ?.apply { setReferenceCounted(false) }

    @Synchronized
    fun acquire() {
        runCatching { wifiLock?.takeUnless { it.isHeld }?.acquire() }
            .onFailure { Timber.w(it, "CastNetworkLock: wifi acquire failed") }
        runCatching { wakeLock?.takeUnless { it.isHeld }?.acquire() }
            .onFailure { Timber.w(it, "CastNetworkLock: wake acquire failed") }
        Timber.w(
            "CastNetworkLock acquired (wifi=%s wake=%s)",
            wifiLock?.isHeld, wakeLock?.isHeld,
        )
    }

    @Synchronized
    fun release() {
        runCatching { wifiLock?.takeIf { it.isHeld }?.release() }
            .onFailure { Timber.w(it, "CastNetworkLock: wifi release failed") }
        runCatching { wakeLock?.takeIf { it.isHeld }?.release() }
            .onFailure { Timber.w(it, "CastNetworkLock: wake release failed") }
        Timber.w("CastNetworkLock released")
    }

    private companion object {
        const val LOCK_TAG = "minstrel:upnp-cast"

        // WIFI_MODE_FULL_LOW_LATENCY (API 29+) both disables power-save and
        // lowers latency; FULL_HIGH_PERF is the pre-Q equivalent for keeping
        // the radio fully awake.
        fun wifiLockMode(): Int =
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                WifiManager.WIFI_MODE_FULL_LOW_LATENCY
            } else {
                @Suppress("DEPRECATION")
                WifiManager.WIFI_MODE_FULL_HIGH_PERF
            }
    }
}
