//! Logger wrapper for LevelDB custom logging functionality.
//!
//! This module provides a safe Rust wrapper around the custom logger implementation
//! that can be used with LevelDB options.
//!
//! # Usage
//!
//! To use the logger:
//! 1. Implement the [`LogHandler`] trait for your custom handler
//! 2. Create a [`Logger`] instance with your handler
//! 3. Pass the logger to LevelDB options using `Options::set_logger()`

use crate::binding::leveldb_logger_t;
use std::ffi::{CStr, c_void};
use std::os::raw::c_char;
use std::time::SystemTime;

/// Trait for handling log messages.
///
/// Implement this trait to create custom log handlers.
/// The handler receives:
/// - `timestamp`: SystemTime when the log was created
/// - `thread_id`: Thread ID from LevelDB internally
/// - `message`: The formatted log message from LevelDB internally. Note: The `message`` is not guaranteed to end with a newline.
/// The `log` function should be guaranteed for thread-safe.
pub trait LogHandler: Send + Sync {
    fn log(&self, timestamp: SystemTime, thread_id: &str, message: &str);
}

/// Represents a log line with timestamp and rust_logger pointer.
/// It is used by C++ layer as callback.
#[repr(C)]
pub struct leveldb_rs_log_line_t {
    timestamp: SystemTime,
    rust_logger: *mut c_void,
}

unsafe extern "C" {
    /// Create a new RsLogger instance with rust_logger pointer
    /// Returns a pointer to the logger
    ///
    /// # Safety
    ///
    /// - `rust_logger` must be a valid pointer to a Rust Logger object
    pub fn leveldb_rs_logger_create(rust_logger: *mut c_void) -> *mut leveldb_logger_t;

    /// Destroy the RsLogger instance and free associated resources
    fn leveldb_rs_logger_destroy(logger: *mut leveldb_logger_t);
}

/// Create a new leveldb_rs_log_line_t with rust_logger. It would keep current timestamp.
///
/// # Safety
///
/// - `rust_logger` must be a valid pointer to a Rust Logger object
#[unsafe(no_mangle)]
unsafe extern "C" fn leveldb_rs_logline_create(
    rust_logger: *mut c_void,
) -> *mut leveldb_rs_log_line_t {
    let logline = Box::new(leveldb_rs_log_line_t {
        timestamp: SystemTime::now(),
        rust_logger,
    });
    Box::into_raw(logline)
}

/// Destroy a leveldb_rs_log_line_t instance.
///
/// # Safety
///
/// - `logline` must be a valid pointer returned from `leveldb_rs_logline_create`
/// - `logline` must not be used after this call
#[unsafe(no_mangle)]
unsafe extern "C" fn leveldb_rs_logline_destroy(logline: *mut leveldb_rs_log_line_t) {
    unsafe {
        let _ = Box::from_raw(logline);
    }
}

/// Process a log message using the Logger's process method.
///
/// # Safety
///
/// - `logline` must be a valid pointer to an instance of struct leveldb_rs_log_line_t
/// - `thread_id` must be a valid null-terminated C string
/// - `message` must be a valid null-terminated C string
#[unsafe(no_mangle)]
unsafe extern "C" fn leveldb_rs_logline_process(
    logline: *mut leveldb_rs_log_line_t,
    thread_id: *const c_char,
    message: *const c_char,
) {
    if logline.is_null() || thread_id.is_null() || message.is_null() {
        return;
    }

    unsafe {
        let thread_id_str = match CStr::from_ptr(thread_id).to_str() {
            Ok(s) => s,
            Err(e) => {
                eprintln!("Warning: Invalid UTF-8 in thread_id, error: {}", e);
                "invalid_thread_id"
            }
        };
        let message_str = match CStr::from_ptr(message).to_str() {
            Ok(s) => s,
            Err(e) => {
                eprintln!("Warning: Invalid UTF-8 in log message, error: {}", e);
                "invalid_message"
            }
        };
        let logline_ref = &*logline;
        let logger = logline_ref.rust_logger as *mut Logger;
        (*logger).process(logline_ref, thread_id_str, message_str);
    }
}

/// A safe wrapper around the custom LevelDB logger implementation.
/// Check module [`logger`](crate::extension::logger) for usage.
pub struct Logger {
    raw_ptr: *mut leveldb_logger_t,
    handler: Box<dyn LogHandler + Send + Sync>,
}

impl Logger {
    /// Create a new logger instance with a custom handler.
    pub fn new<H: LogHandler + Send + Sync + 'static>(handler: H) -> Self {
        Logger {
            raw_ptr: std::ptr::null_mut(),
            handler: Box::new(handler),
        }
    }

    /// Process a log message using the internal handler.
    /// This method is called from C++ layer
    pub fn process(&self, logline: &leveldb_rs_log_line_t, thread_id: &str, message: &str) {
        self.handler.log(logline.timestamp, thread_id, message);
    }

    /// Set the raw pointer (used internally by options)
    ///
    /// # Safety
    ///
    /// This should only be called once during initialization of Options
    pub unsafe fn set_raw_ptr(&mut self, ptr: *mut leveldb_logger_t) {
        if !ptr.is_null() {
            self.raw_ptr = ptr;
        }
    }

    /// Get a raw pointer to the logger.
    ///
    /// # Safety
    ///
    /// This returns a raw pointer that should only be used with the LevelDB C API.
    /// The Logger instance manages the lifetime of this pointer.
    pub fn raw_ptr(&self) -> *mut leveldb_logger_t {
        self.raw_ptr
    }
}

impl Drop for Logger {
    fn drop(&mut self) {
        unsafe {
            if !self.raw_ptr.is_null() {
                leveldb_rs_logger_destroy(self.raw_ptr);
                self.raw_ptr = std::ptr::null_mut();
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::sync::{Arc, Mutex};

    struct TestHandler;

    impl LogHandler for TestHandler {
        fn log(&self, _time: SystemTime, _thread_id: &str, _msg: &str) {
            // Simple handler for testing
        }
    }

    #[test]
    fn test_logger_lifecycle() {
        let logger = Logger::new(TestHandler);
        // Test that drop doesn't panic
        drop(logger);
    }

    #[test]
    fn test_logger_raw_ptr() {
        let mut logger = Logger::new(TestHandler);
        assert!(logger.raw_ptr().is_null());
        unsafe {
            let rust_logger_ptr = &mut logger as *mut Logger as *mut c_void;
            let raw_ptr = leveldb_rs_logger_create(rust_logger_ptr);
            logger.set_raw_ptr(raw_ptr);
        }
        let raw_ptr = logger.raw_ptr();
        assert!(!raw_ptr.is_null());
    }

    #[test]
    fn test_logline_lifecycle() {
        let logger = Logger::new(TestHandler);
        let logger_ptr = &logger as *const Logger as *mut c_void;
        unsafe {
            let logline = leveldb_rs_logline_create(logger_ptr);
            assert!(!logline.is_null());
            leveldb_rs_logline_destroy(logline);
        }
    }

    #[test]
    fn test_logline_process() {
        let received = Arc::new(Mutex::new(Vec::new()));
        struct TestProcessHandler {
            received: Arc<Mutex<Vec<(SystemTime, String, String)>>>,
        }
        impl LogHandler for TestProcessHandler {
            fn log(&self, time: SystemTime, thread_id: &str, msg: &str) {
                self.received
                    .lock()
                    .unwrap()
                    .push((time, thread_id.to_string(), msg.to_string()));
            }
        }
        let handler = TestProcessHandler {
            received: received.clone(),
        };
        let logger = Logger::new(handler);
        let logger_ptr = &logger as *const Logger as *mut c_void;
        unsafe {
            let logline = leveldb_rs_logline_create(logger_ptr);
            let thread_id = std::ffi::CString::new("thread-test-id").unwrap();
            let message = std::ffi::CString::new("Test message\n").unwrap();
            leveldb_rs_logline_process(logline, thread_id.as_ptr(), message.as_ptr());
            leveldb_rs_logline_destroy(logline);
        }

        let logs = received.lock().unwrap();
        assert_eq!(logs.len(), 1);
        assert_eq!(logs[0].1, "thread-test-id");
        assert_eq!(logs[0].2, "Test message\n");
    }
}
