//! JNI facade for Android callers of EasyTier.
//!
//! This file intentionally lists every Java-visible native method exported by
//! `libeasytier_android_jni.so`. The implementation details live in sibling
//! modules so this facade stays readable as an API map.
//!
//! Network management APIs:
//! - `setTunFd(instanceName, fd)`: attach an Android TUN fd to an instance.
//! - `parseConfig(config)`: validate TOML config text.
//! - `runNetworkInstance(config)`: start a local network instance.
//! - `retainNetworkInstance(instanceNames)`: retain named instances and stop the rest.
//! - `listInstances()`: return running instance names and IDs as JSON.
//! - `collectNetworkInfos()`: return running instance info as a JSON string.
//! - `callJsonRpc(...)`: call an exposed EasyTier RPC service with JSON payload.
//!
//! Config server client APIs:
//! - `startConfigServerClient(url, hostname, machineId, secureMode, callback)`:
//!   start the managed remote config client.
//! - `stopConfigServerClient()`: stop the managed client and release its Java callback.
//! - `isConfigServerClientConnected()`: return whether the managed client is connected.
//!
//! Error API:
//! - `getLastError()`: return the latest FFI/JNI error string for the calling thread.
//!
mod callback;
mod config_server_api;
mod error;
mod json_rpc_api;
mod logger;
mod network_api;
mod strings;

use jni::JNIEnv;
use jni::objects::{JClass, JObject, JObjectArray, JString};
use jni::sys::{jboolean, jint, jstring};

/// Attach a TUN file descriptor to an EasyTier network instance.
///
/// Java signature:
/// `EasyTierJNI.setTunFd(instanceName: String, fd: Int): Int`
///
/// `instanceName` must name an instance known to the shared FFI instance cache.
/// The `fd` must be a valid Android TUN file descriptor. On failure this
/// returns `-1` and throws `RuntimeException` with the FFI error message when
/// one is available.
#[unsafe(no_mangle)]
pub extern "system" fn Java_com_easytier_jni_EasyTierJNI_setTunFd(
    env: JNIEnv,
    class: JClass,
    inst_name: JString,
    fd: jint,
) -> jint {
    logger::init();
    network_api::set_tun_fd_jni(env, class, inst_name, fd)
}

/// Validate a TOML network config string.
///
/// Java signature:
/// `EasyTierJNI.parseConfig(config: String): Int`
///
/// This only validates the config text; it does not start or mutate any
/// instance. On failure this returns `-1` and throws `RuntimeException`.
#[unsafe(no_mangle)]
pub extern "system" fn Java_com_easytier_jni_EasyTierJNI_parseConfig(
    env: JNIEnv,
    class: JClass,
    config: JString,
) -> jint {
    logger::init();
    network_api::parse_config_jni(env, class, config)
}

/// Start one local EasyTier network instance from TOML config text.
///
/// Java signature:
/// `EasyTierJNI.runNetworkInstance(config: String): Int`
///
/// The instance name in the config must be unique in the FFI instance cache.
/// On failure this returns `-1` and throws `RuntimeException`.
#[unsafe(no_mangle)]
pub extern "system" fn Java_com_easytier_jni_EasyTierJNI_runNetworkInstance(
    env: JNIEnv,
    class: JClass,
    config: JString,
) -> jint {
    logger::init();
    network_api::run_network_instance_jni(env, class, config)
}

/// Retain the named network instances and stop all other instances.
///
/// Java signature:
/// `EasyTierJNI.retainNetworkInstance(instanceNames: Array<String>?): Int`
///
/// Passing `null` or an empty array stops all instances. Null elements inside a
/// non-empty array are invalid. On failure this returns `-1` and throws
/// `RuntimeException`.
#[unsafe(no_mangle)]
pub extern "system" fn Java_com_easytier_jni_EasyTierJNI_retainNetworkInstance(
    env: JNIEnv,
    class: JClass,
    instance_names: JObjectArray,
) -> jint {
    logger::init();
    network_api::retain_network_instance_jni(env, class, instance_names)
}

/// Collect running network instance information.
///
/// Java signature:
/// `EasyTierJNI.collectNetworkInfos(maxLength: Int): String?`
///
/// Returns a JSON string containing `NetworkInstanceRunningInfoMap`, or null if
/// collection fails. `maxLength` limits how many FFI entries are collected. On
/// failure this throws `RuntimeException` when an error message is available.
#[unsafe(no_mangle)]
pub extern "system" fn Java_com_easytier_jni_EasyTierJNI_collectNetworkInfos(
    env: JNIEnv,
    class: JClass,
    max_length: jint,
) -> jstring {
    logger::init();
    network_api::collect_network_infos_jni(env, class, max_length)
}

/// List running network instance names and IDs.
///
/// Java signature:
/// `EasyTierJNI.listInstances(maxLength: Int): String?`
///
/// Returns a JSON object whose keys are instance names and whose values are
/// instance ID strings. On failure this returns null and throws
/// `RuntimeException`.
#[unsafe(no_mangle)]
pub extern "system" fn Java_com_easytier_jni_EasyTierJNI_listInstances(
    env: JNIEnv,
    class: JClass,
    max_length: jint,
) -> jstring {
    logger::init();
    network_api::list_instances_jni(env, class, max_length)
}

/// Call an exposed EasyTier RPC method using protobuf JSON.
///
/// Java signature:
/// `EasyTierJNI.callJsonRpc(serviceName, methodName, domainName, payloadJson): String?`
///
/// Instance lifecycle management RPCs are intentionally not exposed here. Use
/// the dedicated EasyTierJNI instance APIs for start/retain/delete/collect.
/// `payloadJson` must include any `instance` selector required by the target
/// RPC. On failure this returns null and throws `RuntimeException`.
#[unsafe(no_mangle)]
pub extern "system" fn Java_com_easytier_jni_EasyTierJNI_callJsonRpc(
    env: JNIEnv,
    class: JClass,
    service_name: JString,
    method_name: JString,
    domain_name: JString,
    payload_json: JString,
) -> jstring {
    logger::init();
    json_rpc_api::call_json_rpc_jni(
        env,
        class,
        service_name,
        method_name,
        domain_name,
        payload_json,
    )
}

/// Return the latest FFI/JNI error string for the calling thread.
///
/// Java signature:
/// `EasyTierJNI.getLastError(): String?`
///
/// This combines the FFI thread-local error with any pending config-server Java
/// callback error. It returns null when no error is available.
#[unsafe(no_mangle)]
pub extern "system" fn Java_com_easytier_jni_EasyTierJNI_getLastError(
    env: JNIEnv,
    class: JClass,
) -> jstring {
    error::get_last_error_jni(env, class)
}

/// Start the managed config-server client.
///
/// Java signature:
/// `EasyTierJNI.startConfigServerClient(url, hostname, machineId, secureMode, callback): Int`
///
/// JNI only converts Java values and keeps the Java callback alive. The FFI
/// layer owns singleton lifecycle, config-server/data-plane mutual exclusion,
/// remote instance tracking, and callback event timing. If `callback` is
/// non-null, each remote apply/delete event is delivered to
/// `ConfigServerEventCallback.onEvent(eventJson)`.
///
/// On failure this returns `-1` and throws `RuntimeException`.
#[unsafe(no_mangle)]
pub extern "system" fn Java_com_easytier_jni_EasyTierJNI_startConfigServerClient(
    mut env: JNIEnv,
    _class: JClass,
    config_server_url: JString,
    hostname: JString,
    machine_id: JString,
    secure_mode: jboolean,
    callback: JObject,
) -> jint {
    logger::init();
    config_server_api::start_config_server_client_jni(
        &mut env,
        config_server_url,
        hostname,
        machine_id,
        secure_mode,
        callback,
    )
}

/// Stop the managed config-server client.
///
/// Java signature:
/// `EasyTierJNI.stopConfigServerClient(): Int`
///
/// The FFI layer performs the actual stop and managed instance cleanup. JNI
/// releases the Java callback reference after FFI stop succeeds. On failure
/// this returns `-1` and throws `RuntimeException`.
#[unsafe(no_mangle)]
pub extern "system" fn Java_com_easytier_jni_EasyTierJNI_stopConfigServerClient(
    env: JNIEnv,
    class: JClass,
) -> jint {
    logger::init();
    config_server_api::stop_config_server_client_jni(env, class)
}

/// Report whether the managed config-server client is connected.
///
/// Java signature:
/// `EasyTierJNI.isConfigServerClientConnected(): Boolean`
///
/// Returns `JNI_TRUE` only when the FFI config-server client exists and reports
/// connected.
#[unsafe(no_mangle)]
pub extern "system" fn Java_com_easytier_jni_EasyTierJNI_isConfigServerClientConnected(
    env: JNIEnv,
    class: JClass,
) -> jboolean {
    logger::init();
    config_server_api::is_config_server_client_connected_jni(env, class)
}
