package com.keylightpiano.pianotimeline.data

import android.content.Context
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch

/**
 * Observes whether the device currently has usable internet.
 *
 * Two signals feed the same stream:
 *   1. NetworkCallback — the fast path. Fires instantly on transitions.
 *   2. A periodic poll (every 3s) of isOnlineNow() — a backstop, because on some
 *      devices (notably Samsung, and worse with a VPN active) the onLost /
 *      onCapabilitiesChanged callbacks for LOSING a connection don't reliably
 *      fire while the app is running. Reconnect callbacks are reliable; loss
 *      ones are not. The poll catches the misses. isOnlineNow() is a cheap
 *      synchronous OS query (no network I/O), so 3s polling is negligible.
 *
 * distinctUntilChanged collapses the inevitable duplicate emissions from having
 * two sources, so collectors only see actual state changes.
 *
 * Note: on a device with an always-on VPN, the active network stays validated
 * even in airplane mode (the tunnel counts as a validated network). This class
 * reports that as "online", which is technically correct — reachability of a
 * specific server would need an actual probe. For our purposes that VPN edge
 * case is intentionally not handled.
 */
class NetworkMonitor(context: Context) {

    private val connectivityManager =
        context.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager

    /** Ask the OS, right now, whether the active network can reach the internet. */
    fun isOnlineNow(): Boolean {
        val active = connectivityManager.activeNetwork ?: return false
        val caps = connectivityManager.getNetworkCapabilities(active) ?: return false
        return caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) &&
                caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
    }

    fun observe(): Flow<Boolean> = callbackFlow {
        // A request with NO capability filter: we want to hear about ALL network
        // transitions (including a network being lost), then decide online-ness
        // ourselves via isOnlineNow(). A filtered request only reports networks
        // that already match, which is part of why loss can be missed.
        val request = NetworkRequest.Builder().build()

        val callback = object : ConnectivityManager.NetworkCallback() {
            override fun onAvailable(network: Network) { trySend(isOnlineNow()) }
            override fun onLost(network: Network) { trySend(isOnlineNow()) }
            override fun onUnavailable() { trySend(isOnlineNow()) }
            override fun onCapabilitiesChanged(network: Network, caps: NetworkCapabilities) {
                trySend(isOnlineNow())
            }
        }

        connectivityManager.registerNetworkCallback(request, callback)

        // Seed the initial value so collectors don't wait for the first transition.
        trySend(isOnlineNow())

        // Backstop poll: re-check the real state every 3s to catch loss events the
        // callbacks miss. distinctUntilChanged means quiet periods cost nothing
        // downstream — identical values never propagate.
        val pollJob = launch {
            while (isActive) {
                delay(3000L)
                trySend(isOnlineNow())
            }
        }

        awaitClose {
            pollJob.cancel()
            connectivityManager.unregisterNetworkCallback(callback)
        }
    }.distinctUntilChanged()
}
