pub(crate) mod logging_utils;

#[cfg(test)]
mod trace_logging_test;

use core::fmt;
use std::sync::{
    atomic::{AtomicU16, AtomicU8, Ordering},
    LazyLock,
};

use arc_swap::ArcSwap;
use std::sync::Arc;

#[derive(Clone, Copy, PartialEq, Debug)]
pub enum LogLevel {
    None = 0,
    Error = 1,
    Warning = 2,
    Info = 3,
    Debug = 4,
}

impl From<u8> for LogLevel {
    fn from(value: u8) -> Self {
        match value {
            1 => LogLevel::Error,
            2 => LogLevel::Warning,
            3 => LogLevel::Info,
            4 => LogLevel::Debug,
            _ => LogLevel::None,
        }
    }
}

impl fmt::Display for LogLevel {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let level_str = match self {
            LogLevel::None => "NONE",
            LogLevel::Error => "ERROR",
            LogLevel::Warning => "WARNING",
            LogLevel::Info => "INFO",
            LogLevel::Debug => "DEBUG",
        };
        write!(f, "{}", level_str)
    }
}

pub trait Logger: Send + Sync {
    fn log(&self, log_level: LogLevel, message: &str);
}

pub struct DefaultLogger {}

impl Logger for DefaultLogger {
    fn log(&self, _log_level: LogLevel, message: &str) {
        println!("{}", message);
    }
}

// MARK: - Set up

pub struct KameleoonLogger {
    log_level: AtomicU8,
    logger: ArcSwap<Box<dyn Logger>>,
}

pub static LOGGER_INSTANCE: LazyLock<KameleoonLogger> = LazyLock::new(|| KameleoonLogger::default());

impl KameleoonLogger {
    pub fn new(log_level: LogLevel, logger: Box<dyn Logger>) -> Self {
        KameleoonLogger {
            log_level: AtomicU8::new(log_level as u8),
            logger: ArcSwap::from_pointee(logger),
        }
    }

    pub fn default() -> Self {
        KameleoonLogger::new(LogLevel::Warning, Box::new(DefaultLogger {}))
    }

    pub fn should_log(&self, level: LogLevel) -> bool {
        match level {
            LogLevel::None => false,
            _ => (level as u8) <= self.log_level.load(Ordering::Relaxed),
        }
    }

    // Lock-free logger read via ArcSwap guard.
    pub fn write_log(&self, level: LogLevel, message: &str) {
        let guard = self.logger.load();
        guard.log(level, &format!("Kameleoon [{level}]: {message}"));
    }

    // Regular logging function: Good for logs that are usually visible (e.g., warning/error).
    // Message string is always interpolated, even if the log level is too low to emit the log.
    pub fn log(level: LogLevel, message: &str) {
        if !LOGGER_INSTANCE.should_log(level) {
            return;
        }
        LOGGER_INSTANCE.write_log(level, message);
    }

    // Lazy logging function: Recommended for debug/info logs that are often disabled.
    // Message string is only interpolated *if* the log level allows logging — avoids unnecessary computation.
    fn log_lazy<F>(level: LogLevel, msg_fn: F)
    where
        F: FnOnce() -> String,
    {
        if !LOGGER_INSTANCE.should_log(level) {
            return;
        }
        let message = msg_fn();
        LOGGER_INSTANCE.write_log(level, &message);
    }
}

// MARK: - Public log functions

impl KameleoonLogger {
    /// Set a custom logger implementation.
    pub fn set_logger(logger: Box<dyn Logger>) {
        LOGGER_INSTANCE.logger.store(Arc::new(logger));
    }

    // Get the current log level (lock-free)
    pub fn get_log_level() -> LogLevel {
        LogLevel::from(LOGGER_INSTANCE.log_level.load(Ordering::Relaxed))
    }

    // Set the log level (lock-free)
    pub fn set_log_level(log_level: LogLevel) {
        LOGGER_INSTANCE.log_level.store(log_level as u8, Ordering::Relaxed);
    }

    // MARK: - Direct logging

    pub fn error(message: &str) {
        KameleoonLogger::log(LogLevel::Error, message);
    }

    pub fn warn(message: &str) {
        KameleoonLogger::log(LogLevel::Warning, message);
    }

    pub fn info(message: &str) {
        KameleoonLogger::log(LogLevel::Info, message);
    }

    pub fn debug(message: &str) {
        KameleoonLogger::log(LogLevel::Debug, message);
    }

    // MARK: - Lazy logging

    pub fn error_lazy<F>(msg_fn: F)
    where
        F: FnOnce() -> String,
    {
        KameleoonLogger::log_lazy(LogLevel::Error, msg_fn);
    }

    pub fn warn_lazy<F>(msg_fn: F)
    where
        F: FnOnce() -> String,
    {
        KameleoonLogger::log_lazy(LogLevel::Warning, msg_fn);
    }

    pub fn info_lazy<F>(msg_fn: F)
    where
        F: FnOnce() -> String,
    {
        KameleoonLogger::log_lazy(LogLevel::Info, msg_fn);
    }

    pub fn debug_lazy<F>(msg_fn: F)
    where
        F: FnOnce() -> String,
    {
        KameleoonLogger::log_lazy(LogLevel::Debug, msg_fn);
    }
}

// MARK: - Macros (Lazy interpolation)

pub(crate) static TRACE_ID_COUNTER: AtomicU16 = AtomicU16::new(0);

#[macro_export]
#[doc(hidden)]
macro_rules! get_trace_id {
    () => {
        $crate::logging::TRACE_ID_COUNTER.fetch_add(1, ::std::sync::atomic::Ordering::Relaxed)
    };
}

// Internal macro helper - exported but hidden from public API
#[macro_export]
#[doc(hidden)]
macro_rules! write_log {
    ($level:expr, $($arg:tt)*) => {{
        if $crate::logging::LOGGER_INSTANCE.should_log($level) {
            $crate::logging::LOGGER_INSTANCE.write_log($level, &format!($($arg)*));
        }
    }};
}

#[macro_export]
macro_rules! debug_log {
    ($($arg:tt)*) => {
        $crate::write_log!($crate::logging::LogLevel::Debug, $($arg)*)
    };
}

#[macro_export]
macro_rules! info_log {
    ($($arg:tt)*) => {
        $crate::write_log!($crate::logging::LogLevel::Info, $($arg)*)
    };
}

#[macro_export]
macro_rules! warn_log {
    ($($arg:tt)*) => {
        $crate::write_log!($crate::logging::LogLevel::Warning, $($arg)*)
    };
}

#[macro_export]
macro_rules! error_log {
    ($($arg:tt)*) => {
        $crate::write_log!($crate::logging::LogLevel::Error, $($arg)*)
    };
}

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

    // A logger that collects log messages into a vector
    struct TestLogger {
        pub messages: Arc<Mutex<Vec<(LogLevel, String)>>>,
    }

    impl Logger for TestLogger {
        fn log(&self, log_level: LogLevel, message: &str) {
            if message.contains("<LoggerTest>") {
                self.messages.lock().unwrap().push((log_level, message.to_string()));
            }
        }
    }

    fn setup_logger(log_level: LogLevel) -> Arc<Mutex<Vec<(LogLevel, String)>>> {
        let messages = Arc::new(Mutex::new(vec![]));
        let logger = TestLogger {
            messages: Arc::clone(&messages),
        };

        KameleoonLogger::set_logger(Box::new(logger));
        KameleoonLogger::set_log_level(log_level);

        messages
    }

    #[rstest]
    #[case(LogLevel::None, LogLevel::None, false)]
    #[case(LogLevel::None, LogLevel::Error, false)]
    #[case(LogLevel::None, LogLevel::Warning, false)]
    #[case(LogLevel::None, LogLevel::Info, false)]
    #[case(LogLevel::None, LogLevel::Debug, false)]
    #[case(LogLevel::Error, LogLevel::None, false)]
    #[case(LogLevel::Error, LogLevel::Error, true)]
    #[case(LogLevel::Error, LogLevel::Warning, false)]
    #[case(LogLevel::Error, LogLevel::Info, false)]
    #[case(LogLevel::Error, LogLevel::Debug, false)]
    #[case(LogLevel::Warning, LogLevel::None, false)]
    #[case(LogLevel::Warning, LogLevel::Error, true)]
    #[case(LogLevel::Warning, LogLevel::Warning, true)]
    #[case(LogLevel::Warning, LogLevel::Info, false)]
    #[case(LogLevel::Warning, LogLevel::Debug, false)]
    #[case(LogLevel::Info, LogLevel::None, false)]
    #[case(LogLevel::Info, LogLevel::Error, true)]
    #[case(LogLevel::Info, LogLevel::Warning, true)]
    #[case(LogLevel::Info, LogLevel::Info, true)]
    #[case(LogLevel::Info, LogLevel::Debug, false)]
    #[case(LogLevel::Debug, LogLevel::None, false)]
    #[case(LogLevel::Debug, LogLevel::Error, true)]
    #[case(LogLevel::Debug, LogLevel::Warning, true)]
    #[case(LogLevel::Debug, LogLevel::Info, true)]
    #[case(LogLevel::Debug, LogLevel::Debug, true)]
    #[serial(logger)]
    fn test_log_level_behavior(
        #[case] limit_level: LogLevel,
        #[case] message_level: LogLevel,
        #[case] should_log: bool,
    ) {
        let messages = setup_logger(limit_level);

        let msg1 = "<LoggerTest> msg direct";
        let msg2 = "<LoggerTest> msg lambda";
        let msg3 = "<LoggerTest> msg macros";

        KameleoonLogger::log(message_level, msg1);
        KameleoonLogger::log_lazy(message_level, || msg2.to_owned());
        match message_level {
            LogLevel::Debug => debug_log!("{}", msg3),
            LogLevel::Info => info_log!("{}", msg3),
            LogLevel::Warning => warn_log!("{}", msg3),
            LogLevel::Error => error_log!("{}", msg3),
            _ => (),
        }

        let logs = messages.lock().unwrap();
        if should_log {
            assert_eq!(logs.len(), 3);
            assert_eq!(logs[0], (message_level, format!("Kameleoon [{}]: {}", message_level, msg1)));
            assert_eq!(logs[1], (message_level, format!("Kameleoon [{}]: {}", message_level, msg2)));
            assert_eq!(logs[2], (message_level, format!("Kameleoon [{}]: {}", message_level, msg3)));
        } else {
            assert!(logs.is_empty());
        }
    }

    #[rstest]
    #[serial(logger)]
    fn test_format_skipped_if_level_too_low() {
        _ = setup_logger(LogLevel::Error);

        let counter = std::sync::Arc::new(std::sync::Mutex::new(0));
        let c2 = Arc::clone(&counter);

        // Force format! to increment counter if it runs
        debug_log!("<MacroTest> counter = {}", {
            let mut c = c2.lock().unwrap();
            *c += 1;
            *c
        });

        assert_eq!(*counter.lock().unwrap(), 0, "Interpolation was not skipped!");
    }
}
