//! Event callback and log handler infrastructure.
//!
//! The FFI layer exposes callbacks that fire from Rust's event system and log
//! framework. Callbacks must be non-blocking from Rust's perspective; native
//! layers should dispatch heavy work to their own queues.
//!
//! ## Telemetry
//!
//! Telemetry is configured explicitly by the mobile app before agent init.
//! When enabled, Rust exports OTLP traces + logs via gRPC using the configured
//! endpoint or the shared default endpoint from `querymt-utils`.
//! `QMT_TELEMETRY_LEVEL` remains an optional developer override for verbosity.

use crate::types::FfiErrorCode;
use std::sync::Mutex;
use tracing_subscriber::layer::Context;
use tracing_subscriber::prelude::*;

#[derive(Clone, Debug)]
pub struct MobileTelemetryConfig {
    pub enabled: bool,
    pub endpoint: Option<String>,
}

// ─── Callback Function Pointer Types ────────────────────────────────────────

/// C callback for agent events.
///
/// `user_data` is the opaque pointer supplied at registration time.
/// Use `Option<EventHandlerFn>` for nullable parameters.
pub type EventHandlerFn = unsafe extern "C" fn(
    agent_handle: u64,
    session_handle: u64,
    event_json: *const std::ffi::c_char,
    user_data: *mut std::ffi::c_void,
);

/// C callback for log messages.
/// Use `Option<LogHandlerFn>` for nullable parameters.
pub type LogHandlerFn = unsafe extern "C" fn(
    level: i32,
    message: *const std::ffi::c_char,
    user_data: *mut std::ffi::c_void,
);

// ─── Registered Callback State ──────────────────────────────────────────────

/// Holds the event handler callback configuration for a single agent.
pub struct EventCallbacks {
    pub handler: EventHandlerFn,
    pub user_data: *mut std::ffi::c_void,
}

// Safety: EventCallbacks is only ever used from the FFI thread that registered it.
// The user_data pointer is opaque to Rust and managed by the native layer.
unsafe impl Send for EventCallbacks {}
unsafe impl Sync for EventCallbacks {}

// ─── Global Log Handler ─────────────────────────────────────────────────────

/// Global log handler state.
static GLOBAL_LOG_HANDLER: Mutex<Option<LogHandlerState>> = Mutex::new(None);

struct LogHandlerState {
    handler: LogHandlerFn,
    user_data: *mut std::ffi::c_void,
}

// Safety: same reasoning as EventCallbacks.
unsafe impl Send for LogHandlerState {}
unsafe impl Sync for LogHandlerState {}

/// Register the global log handler.
pub fn set_log_handler(
    handler: Option<LogHandlerFn>,
    user_data: *mut std::ffi::c_void,
) -> Result<(), FfiErrorCode> {
    if handler.is_none() && !user_data.is_null() {
        return Err(FfiErrorCode::InvalidArgument);
    }

    let mut log = GLOBAL_LOG_HANDLER.lock().unwrap();
    match handler {
        Some(h) => {
            *log = Some(LogHandlerState {
                handler: h,
                user_data,
            });
        }
        None => {
            *log = None;
        }
    }
    Ok(())
}

/// Invoke the global log handler if one is registered.
fn invoke_log_handler(level: log::Level, message: &str) {
    let state = GLOBAL_LOG_HANDLER.lock().unwrap();
    if let Some(ref state) = *state {
        let c_message = std::ffi::CString::new(message).unwrap_or_default();
        let level_code = match level {
            log::Level::Error => 1,
            log::Level::Warn => 2,
            log::Level::Info => 3,
            log::Level::Debug => 4,
            log::Level::Trace => 4,
        };
        unsafe {
            (state.handler)(level_code, c_message.as_ptr(), state.user_data);
        }
    }
}

// ─── Tracing Layer for FFI Callback ─────────────────────────────────────────

/// A `tracing_subscriber::Layer` that forwards every event to the FFI log
/// callback registered via `set_log_handler`. This works whether or not OTLP
/// telemetry is active.
struct FfiCallbackLayer;

impl<S> tracing_subscriber::Layer<S> for FfiCallbackLayer
where
    S: tracing::Subscriber,
{
    fn on_event(&self, event: &tracing::Event<'_>, _ctx: Context<'_, S>) {
        // Convert the tracing event to a string and forward to FFI callback.
        let mut visitor = FfiEventVisitor(String::new());
        event.record(&mut visitor);

        let level = match *event.metadata().level() {
            tracing::Level::ERROR => log::Level::Error,
            tracing::Level::WARN => log::Level::Warn,
            tracing::Level::INFO => log::Level::Info,
            tracing::Level::DEBUG => log::Level::Debug,
            tracing::Level::TRACE => log::Level::Trace,
        };
        invoke_log_handler(level, &visitor.0);
    }
}

/// Simple visitor that concatenates the event message + fields.
struct FfiEventVisitor(String);

impl tracing::field::Visit for FfiEventVisitor {
    fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
        if field.name() == "message" {
            self.0 = format!("{:?}", value);
        } else {
            use std::fmt::Write;
            let _ = write!(&mut self.0, " {}={:?}", field.name(), value);
        }
    }

    fn record_str(&mut self, field: &tracing::field::Field, value: &str) {
        if field.name() == "message" {
            self.0 = value.to_string();
        } else {
            use std::fmt::Write;
            let _ = write!(&mut self.0, " {}={}", field.name(), value);
        }
    }
}

// ─── Initialization ─────────────────────────────────────────────────────────

static LOGGER_INITIALIZED: std::sync::atomic::AtomicBool =
    std::sync::atomic::AtomicBool::new(false);

/// Explicit telemetry configuration supplied by the mobile app before agent init.
static MOBILE_TELEMETRY_CONFIG: std::sync::Mutex<Option<MobileTelemetryConfig>> =
    std::sync::Mutex::new(None);

/// The OTLP endpoint that was configured (if telemetry is enabled).
/// Stored so mesh_status can report it.
static ACTIVE_OTLP_ENDPOINT: std::sync::Mutex<Option<String>> = std::sync::Mutex::new(None);

/// Return the active OTLP endpoint, if telemetry was enabled.
pub fn active_otlp_endpoint() -> Option<String> {
    ACTIVE_OTLP_ENDPOINT.lock().unwrap().clone()
}

pub fn configure_mobile_telemetry(config: MobileTelemetryConfig) {
    *MOBILE_TELEMETRY_CONFIG.lock().unwrap() = Some(config);
}

/// Initialize the logging/telemetry subsystem.
///
/// Telemetry must be configured explicitly by the mobile app before agent init.
/// When disabled, this function installs nothing and exports nothing.
///
/// This function is idempotent; subsequent calls are no-ops.
pub fn setup_mobile_telemetry() {
    let configured = MOBILE_TELEMETRY_CONFIG.lock().unwrap().clone();
    let config = configured.unwrap_or_else(|| MobileTelemetryConfig {
        enabled: cfg!(debug_assertions),
        endpoint: None,
    });

    log::debug!(
        "setup_mobile_telemetry: enabled={} endpoint={}",
        config.enabled,
        config.endpoint.as_deref().unwrap_or("<default>")
    );

    if !config.enabled {
        return;
    }

    if LOGGER_INITIALIZED
        .compare_exchange(
            false,
            true,
            std::sync::atomic::Ordering::SeqCst,
            std::sync::atomic::Ordering::SeqCst,
        )
        .is_err()
    {
        // Already initialized.
        return;
    }

    init_telemetry(config);
}

/// Full telemetry init: delegates subscriber assembly to `querymt_utils`
/// with the FFI callback layer attached as an extra layer.
fn init_telemetry(mobile_config: MobileTelemetryConfig) {
    use tracing_subscriber::EnvFilter;

    let config = querymt_utils::telemetry::TelemetryConfig {
        service_name: "querymt-mobile",
        service_version: env!("CARGO_PKG_VERSION"),
        use_stderr: true,
        enable_otlp: true,
        endpoint: mobile_config.endpoint,
    };

    // FFI callback filter — forward info+ to native side.
    let ffi_layer = FfiCallbackLayer.with_filter(EnvFilter::new("info")).boxed();

    match querymt_utils::telemetry::try_setup_telemetry_with_layers(config, vec![ffi_layer]) {
        Ok(Some(endpoint)) => {
            // Store for mesh_status diagnostics.
            {
                let mut active = ACTIVE_OTLP_ENDPOINT.lock().unwrap();
                *active = Some(endpoint.clone());
            }
            log::info!("OTLP telemetry initialized, endpoint={}", endpoint);
        }
        Ok(None) => {
            log::warn!("OTLP telemetry unexpectedly disabled during mobile init");
        }
        Err(e) => {
            log::warn!("OTLP telemetry init failed: {e}");
        }
    }
}

/// Backwards-compatible entry point. Equivalent to `setup_mobile_telemetry()`.
pub fn ensure_logger() {
    setup_mobile_telemetry();
}
