//! Reference quicproquo server plugin: logs all hook events to stderr.
//!
//! This plugin demonstrates every hook point in the `HookVTable` API.  It
//! writes a single-line human-readable record to stderr for each server event.
//! No state is required, so `user_data` is left null.
//!
//! # Building
//!
//! ```bash
//! cargo build --release -p logging_plugin
//! # Output: target/release/liblogging_plugin.so  (Linux)
//! #         target/release/liblogging_plugin.dylib (macOS)
//! ```
//!
//! # Deploying
//!
//! ```bash
//! cp target/release/liblogging_plugin.so /etc/qpq/plugins/
//! qpq-server --plugin-dir /etc/qpq/plugins
//! ```

use std::ffi::c_void;
use std::slice;

use quicproquo_plugin_api::{
    CAuthEvent, CChannelEvent, CFetchEvent, CMessageEvent, HookVTable, HOOK_CONTINUE, PLUGIN_OK,
};

// ── Helpers ───────────────────────────────────────────────────────────────────

fn hex_prefix(ptr: *const u8, len: usize) -> String {
    if ptr.is_null() || len == 0 {
        return "(none)".to_string();
    }
    let bytes = unsafe { slice::from_raw_parts(ptr, len.min(4)) };
    bytes.iter().map(|b| format!("{:02x}", b)).collect()
}

fn str_from_raw(ptr: *const u8, len: usize) -> &'static str {
    if ptr.is_null() || len == 0 {
        return "";
    }
    // Safety: the server owns the memory and it remains valid for the callback duration.
    let bytes = unsafe { slice::from_raw_parts(ptr, len) };
    std::str::from_utf8(bytes).unwrap_or("<invalid utf8>")
}

// ── Hook callbacks ────────────────────────────────────────────────────────────

unsafe extern "C" fn on_message_enqueue(
    _user_data: *mut c_void,
    event: *const CMessageEvent,
) -> i32 {
    let e = &*event;
    eprintln!(
        "[qpq-plugin:logging] enqueue: recipient={} payload_len={} seq={} has_sender={}",
        hex_prefix(e.recipient_key, e.recipient_key_len),
        e.payload_len,
        e.seq,
        !e.sender_identity.is_null(),
    );
    HOOK_CONTINUE
}

unsafe extern "C" fn on_batch_enqueue(
    _user_data: *mut c_void,
    events: *const CMessageEvent,
    count: usize,
) {
    eprintln!("[qpq-plugin:logging] batch_enqueue: count={}", count);
    let events = slice::from_raw_parts(events, count);
    for (i, e) in events.iter().enumerate() {
        eprintln!(
            "[qpq-plugin:logging]   [{}/{}] recipient={} seq={}",
            i + 1,
            count,
            hex_prefix(e.recipient_key, e.recipient_key_len),
            e.seq,
        );
    }
}

unsafe extern "C" fn on_auth(_user_data: *mut c_void, event: *const CAuthEvent) {
    let e = &*event;
    let username = str_from_raw(e.username, e.username_len);
    if e.success != 0 {
        eprintln!("[qpq-plugin:logging] auth: user='{}' SUCCESS", username);
    } else {
        let reason = str_from_raw(e.failure_reason, e.failure_reason_len);
        eprintln!(
            "[qpq-plugin:logging] auth: user='{}' FAILURE reason='{}'",
            username, reason
        );
    }
}

unsafe extern "C" fn on_channel_created(
    _user_data: *mut c_void,
    event: *const CChannelEvent,
) {
    let e = &*event;
    eprintln!(
        "[qpq-plugin:logging] channel_created: channel={} was_new={} initiator={}",
        hex_prefix(e.channel_id, e.channel_id_len),
        e.was_new != 0,
        hex_prefix(e.initiator_key, e.initiator_key_len),
    );
}

unsafe extern "C" fn on_fetch(_user_data: *mut c_void, event: *const CFetchEvent) {
    let e = &*event;
    if e.message_count > 0 {
        eprintln!(
            "[qpq-plugin:logging] fetch: recipient={} count={}",
            hex_prefix(e.recipient_key, e.recipient_key_len),
            e.message_count,
        );
    }
}

unsafe extern "C" fn on_user_registered(
    _user_data: *mut c_void,
    username: *const u8,
    username_len: usize,
    identity_key: *const u8,
    identity_key_len: usize,
) {
    let name = str_from_raw(username, username_len);
    eprintln!(
        "[qpq-plugin:logging] user_registered: user='{}' key={}",
        name,
        hex_prefix(identity_key, identity_key_len),
    );
}

// ── Plugin entry point ────────────────────────────────────────────────────────

/// Called by the server once at startup.
///
/// # Safety
///
/// `vtable` must point to a zeroed `HookVTable` as provided by `qpq-server`.
#[no_mangle]
pub unsafe extern "C" fn qpq_plugin_init(vtable: *mut HookVTable) -> i32 {
    if vtable.is_null() {
        return -1;
    }
    let v = &mut *vtable;

    // user_data is not needed — all callbacks are stateless.
    v.user_data = std::ptr::null_mut();
    v.on_message_enqueue = Some(on_message_enqueue);
    v.on_batch_enqueue = Some(on_batch_enqueue);
    v.on_auth = Some(on_auth);
    v.on_channel_created = Some(on_channel_created);
    v.on_fetch = Some(on_fetch);
    v.on_user_registered = Some(on_user_registered);
    // error_message and destroy not needed (no state, never rejects).

    eprintln!("[qpq-plugin:logging] initialized");
    PLUGIN_OK
}
