//! The bandwidth speed test over an established session.
//!
//! The host bursts filler over the real data plane — the same path a stream uses, so the answer is
//! about the link the stream will actually take rather than about some generic throughput. It is
//! deliberately *measure-only*: which layer a measured bitrate belongs in (the global default, or a
//! host's bound profile) is a decision the UI makes with the user
//! (design/client-settings-profiles.md §5.3), never one this shim makes for them.
//!
//! Two calls: start, then poll. Both are cheap and non-blocking, so Kotlin can drive them from a
//! coroutine on the main thread the way it polls the stats HUD.

use super::{jni_guard, SessionHandle};
use jni::errors::LogErrorAndDefault;
use jni::objects::{JDoubleArray, JObject};
use jni::sys::{jboolean, jint, jlong};
use jni::EnvUnowned;

/// The `DoubleArray` [`Java_io_unom_punktfunk_kit_NativeBridge_nativeProbeResult`] returns. Kept in
/// one place because Kotlin indexes it positionally; see the Kotlin doc for the field order.
const PROBE_RESULT_LEN: usize = 6;

/// `NativeBridge.nativeSpeedTest(handle, targetKbps, durationMs): Boolean` — ask the host to burst
/// filler at `targetKbps` of goodput for `durationMs` (each clamped host-side to ≤ 3 Gbps / ≤ 5 s),
/// **briefly pausing video**. Non-blocking: poll
/// [`Java_io_unom_punktfunk_kit_NativeBridge_nativeProbeResult`] until its `done` element is 1.
/// Starting a probe resets any prior measurement. `false` on a `0` handle or a closed session.
#[unsafe(no_mangle)]
pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeSpeedTest(
    _env: EnvUnowned,
    _this: JObject,
    handle: jlong,
    target_kbps: jint,
    duration_ms: jint,
) -> jboolean {
    jni_guard(false, || {
        if handle == 0 {
            return false;
        }
        // SAFETY: live handle per the nativeConnect/nativeClose contract.
        let h = unsafe { &*(handle as *const SessionHandle) };
        let target = target_kbps.clamp(0, i32::MAX) as u32;
        let duration = duration_ms.clamp(0, i32::MAX) as u32;
        match h.client.request_probe(target, duration) {
            Ok(()) => true,
            Err(e) => {
                log::warn!("speed test: could not ask the host to probe: {e:?}");
                false
            }
        }
    })
}

/// `NativeBridge.nativeProbeResult(handle): DoubleArray?` — the current measurement, partial until
/// `[0] == 1`. Safe to poll; before any probe it reports zeros. `null` on a `0` handle.
///
/// Layout (doubles so one array carries both the counts and the percentages):
/// `[done, throughputKbps, lossPct, hostDropPct, elapsedMs, recvBytes]`.
#[unsafe(no_mangle)]
pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeProbeResult<'local>(
    mut env: EnvUnowned<'local>,
    _this: JObject<'local>,
    handle: jlong,
) -> JDoubleArray<'local> {
    // `JDoubleArray::default()` is the null reference the old `JObject::null().into_raw()` returned,
    // so Kotlin still reads `null` on every failure path.
    env.with_env(|env| -> jni::errors::Result<JDoubleArray<'local>> {
        if handle == 0 {
            return Ok(JDoubleArray::default());
        }
        // SAFETY: live handle per the nativeConnect/nativeClose contract.
        let h = unsafe { &*(handle as *const SessionHandle) };
        let r = h.client.probe_result();
        let values: [f64; PROBE_RESULT_LEN] = [
            f64::from(u8::from(r.done)),
            f64::from(r.throughput_kbps),
            f64::from(r.loss_pct),
            f64::from(r.host_drop_pct),
            f64::from(r.elapsed_ms),
            r.recv_bytes as f64,
        ];
        let arr = env.new_double_array(PROBE_RESULT_LEN)?;
        arr.set_region(env, 0, &values)?;
        Ok(arr)
    })
    .resolve::<LogErrorAndDefault>()
}
