//! Android-only helpers for WiFi detection and MulticastLock acquisition.
//!
//! SSDP discovery (UPnP) requires receiving multicast UDP on 239.255.255.250:1900.
//! Android filters incoming multicast unless a WifiManager.MulticastLock is held.
//! These helpers acquire the lock for the duration of an SSDP attempt.
//!
//! Cellular networks (CGNAT) almost never expose UPnP/PCP gateways, so we gate
//! UPnP attempts on "is on WiFi" to avoid wasting a 3s discovery timeout on
//! every cellular startup.

#![cfg(target_os = "android")]

use jni::objects::{JObject, JString, JValue};
use jni::JavaVM;
use tracing::{debug, warn};

/// Returns true if the active network is WiFi (or Ethernet).
/// Returns false on cellular, no-network, or any error.
pub fn is_on_wifi() -> bool {
    match check_wifi_inner() {
        Ok(v) => v,
        Err(e) => {
            debug!("Android WiFi check failed: {}", e);
            false
        }
    }
}

fn check_wifi_inner() -> Result<bool, String> {
    let ctx = ndk_context::android_context();
    if ctx.vm().is_null() {
        return Err("ndk_context: null JavaVM (not initialized?)".into());
    }
    if ctx.context().is_null() {
        return Err("ndk_context: null activity context".into());
    }
    let vm = unsafe { JavaVM::from_raw(ctx.vm() as *mut _) }
        .map_err(|e| format!("JavaVM init: {:?}", e))?;
    let mut env = vm
        .attach_current_thread()
        .map_err(|e| format!("attach_current_thread: {:?}", e))?;
    let activity = unsafe { JObject::from_raw(ctx.context() as *mut _) };

    // service = activity.getSystemService(Context.CONNECTIVITY_SERVICE)
    let svc_name = env
        .new_string("connectivity")
        .map_err(|e| format!("new_string: {:?}", e))?;
    let svc = env
        .call_method(
            &activity,
            "getSystemService",
            "(Ljava/lang/String;)Ljava/lang/Object;",
            &[JValue::Object(&svc_name)],
        )
        .map_err(|e| format!("getSystemService: {:?}", e))?
        .l()
        .map_err(|e| format!("getSystemService cast: {:?}", e))?;

    // network = service.getActiveNetwork()
    let network = env
        .call_method(&svc, "getActiveNetwork", "()Landroid/net/Network;", &[])
        .map_err(|e| format!("getActiveNetwork: {:?}", e))?
        .l()
        .map_err(|e| format!("getActiveNetwork cast: {:?}", e))?;

    if network.is_null() {
        return Ok(false);
    }

    // caps = service.getNetworkCapabilities(network)
    let caps = env
        .call_method(
            &svc,
            "getNetworkCapabilities",
            "(Landroid/net/Network;)Landroid/net/NetworkCapabilities;",
            &[JValue::Object(&network)],
        )
        .map_err(|e| format!("getNetworkCapabilities: {:?}", e))?
        .l()
        .map_err(|e| format!("getNetworkCapabilities cast: {:?}", e))?;

    if caps.is_null() {
        return Ok(false);
    }

    // NetworkCapabilities.TRANSPORT_WIFI = 1, TRANSPORT_ETHERNET = 3
    let has_wifi = env
        .call_method(&caps, "hasTransport", "(I)Z", &[JValue::Int(1)])
        .map_err(|e| format!("hasTransport(WIFI): {:?}", e))?
        .z()
        .map_err(|e| format!("hasTransport(WIFI) cast: {:?}", e))?;
    let has_eth = env
        .call_method(&caps, "hasTransport", "(I)Z", &[JValue::Int(3)])
        .map_err(|e| format!("hasTransport(ETH): {:?}", e))?
        .z()
        .map_err(|e| format!("hasTransport(ETH) cast: {:?}", e))?;

    Ok(has_wifi || has_eth)
}

/// RAII guard that holds an Android WifiManager.MulticastLock for its lifetime.
/// Release happens on Drop.
pub struct MulticastLockGuard {
    vm: JavaVM,
    lock: jni::objects::GlobalRef,
}

impl MulticastLockGuard {
    pub fn acquire(tag: &str) -> Option<Self> {
        match Self::acquire_inner(tag) {
            Ok(g) => Some(g),
            Err(e) => {
                debug!("MulticastLock acquire failed: {}", e);
                None
            }
        }
    }

    fn acquire_inner(tag: &str) -> Result<Self, String> {
        let ctx = ndk_context::android_context();
        if ctx.vm().is_null() {
            return Err("ndk_context: null JavaVM (not initialized?)".into());
        }
        if ctx.context().is_null() {
            return Err("ndk_context: null activity context".into());
        }
        let vm = unsafe { JavaVM::from_raw(ctx.vm() as *mut _) }
            .map_err(|e| format!("JavaVM init: {:?}", e))?;
        let mut env = vm
            .attach_current_thread()
            .map_err(|e| format!("attach_current_thread: {:?}", e))?;
        let activity = unsafe { JObject::from_raw(ctx.context() as *mut _) };

        // wifi = activity.getApplicationContext().getSystemService(Context.WIFI_SERVICE)
        // Application context is important: WifiManager from Activity context can leak the activity.
        let app_ctx = env
            .call_method(
                &activity,
                "getApplicationContext",
                "()Landroid/content/Context;",
                &[],
            )
            .map_err(|e| format!("getApplicationContext: {:?}", e))?
            .l()
            .map_err(|e| format!("getApplicationContext cast: {:?}", e))?;

        let svc_name: JString = env
            .new_string("wifi")
            .map_err(|e| format!("new_string: {:?}", e))?;
        let wifi = env
            .call_method(
                &app_ctx,
                "getSystemService",
                "(Ljava/lang/String;)Ljava/lang/Object;",
                &[JValue::Object(&svc_name)],
            )
            .map_err(|e| format!("getSystemService(wifi): {:?}", e))?
            .l()
            .map_err(|e| format!("getSystemService(wifi) cast: {:?}", e))?;

        if wifi.is_null() {
            return Err("WifiManager is null".into());
        }

        // lock = wifi.createMulticastLock(tag)
        let tag_str = env
            .new_string(tag)
            .map_err(|e| format!("new_string(tag): {:?}", e))?;
        let lock = env
            .call_method(
                &wifi,
                "createMulticastLock",
                "(Ljava/lang/String;)Landroid/net/wifi/WifiManager$MulticastLock;",
                &[JValue::Object(&tag_str)],
            )
            .map_err(|e| format!("createMulticastLock: {:?}", e))?
            .l()
            .map_err(|e| format!("createMulticastLock cast: {:?}", e))?;

        // lock.setReferenceCounted(false)
        let _ = env.call_method(
            &lock,
            "setReferenceCounted",
            "(Z)V",
            &[JValue::Bool(0)],
        );

        // lock.acquire()
        env.call_method(&lock, "acquire", "()V", &[])
            .map_err(|e| format!("acquire: {:?}", e))?;

        let global = env
            .new_global_ref(&lock)
            .map_err(|e| format!("new_global_ref: {:?}", e))?;

        drop(env);
        Ok(MulticastLockGuard { vm, lock: global })
    }
}

impl Drop for MulticastLockGuard {
    fn drop(&mut self) {
        let env = match self.vm.attach_current_thread() {
            Ok(e) => e,
            Err(e) => {
                warn!("MulticastLock release: attach failed: {:?}", e);
                return;
            }
        };
        let mut env = env;
        if let Err(e) = env.call_method(&self.lock, "release", "()V", &[]) {
            warn!("MulticastLock release failed: {:?}", e);
        }
    }
}

/// Stop the Android `NodeService` foreground service. Called from the
/// in-app close button so the network process actually exits rather
/// than continuing to run as a foreground service after the Activity
/// closes (foreground services are kept alive across Activity exit by
/// design).
///
/// Errors are logged but not propagated — best-effort cleanup before
/// `AppHandle::exit(0)` finishes the Activity.
pub fn stop_node_service() {
    if let Err(e) = stop_node_service_inner() {
        warn!("stop_node_service failed (will exit anyway): {}", e);
    }
}

fn stop_node_service_inner() -> Result<(), String> {
    let ctx = ndk_context::android_context();
    if ctx.vm().is_null() {
        return Err("ndk_context: null JavaVM".into());
    }
    if ctx.context().is_null() {
        return Err("ndk_context: null activity context".into());
    }
    let vm = unsafe { JavaVM::from_raw(ctx.vm() as *mut _) }
        .map_err(|e| format!("JavaVM init: {:?}", e))?;
    let mut env = vm
        .attach_current_thread()
        .map_err(|e| format!("attach_current_thread: {:?}", e))?;
    let activity = unsafe { JObject::from_raw(ctx.context() as *mut _) };

    // NodeService.stopFromNative(activity)
    env.call_static_method(
        "com/itsgoin/app/NodeService",
        "stopFromNative",
        "(Landroid/content/Context;)V",
        &[JValue::Object(&activity)],
    )
    .map_err(|e| format!("stopFromNative: {:?}", e))?;

    Ok(())
}
