use std::ffi::CStr;
use std::sync::Once;

static INIT: Once = Once::new();

fn severity_name(severity: i32) -> &'static str {
    match severity {
        1 => "FATAL",
        2 => "ERROR",
        3 => "WARN",
        4 => "INFO",
        5 => "DEBUG",
        6 => "TRACE",
        _ => "UNKNOWN",
    }
}

unsafe extern "C" fn sdk_callback(
    _thread_id: u64,
    severity: i32,
    _timestamp: xbbg_core::ffi::blpapi_Logging_Datetime_t,
    category: *const std::ffi::c_char,
    message: *const std::ffi::c_char,
) {
    let cat = if category.is_null() {
        "sdk"
    } else {
        CStr::from_ptr(category).to_str().unwrap_or("sdk")
    };
    let msg = if message.is_null() {
        ""
    } else {
        CStr::from_ptr(message).to_str().unwrap_or("")
    };

    match severity {
        1 => {
            xbbg_log::error!(target: "xbbg.sdk", category = cat, "[{}] {}", severity_name(severity), msg)
        }
        2 => {
            xbbg_log::error!(target: "xbbg.sdk", category = cat, "[{}] {}", severity_name(severity), msg)
        }
        3 => {
            xbbg_log::warn!(target: "xbbg.sdk", category = cat, "[{}] {}", severity_name(severity), msg)
        }
        4 => {
            xbbg_log::info!(target: "xbbg.sdk", category = cat, "[{}] {}", severity_name(severity), msg)
        }
        5 => {
            xbbg_log::debug!(target: "xbbg.sdk", category = cat, "[{}] {}", severity_name(severity), msg)
        }
        _ => {
            xbbg_log::trace!(target: "xbbg.sdk", category = cat, "[{}] {}", severity_name(severity), msg)
        }
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SdkLogLevel {
    Off,
    Fatal,
    Error,
    Warn,
    Info,
    Debug,
    Trace,
}

impl SdkLogLevel {
    fn to_raw(self) -> i32 {
        match self {
            Self::Off => 0,
            Self::Fatal => 1,
            Self::Error => 2,
            Self::Warn => 3,
            Self::Info => 4,
            Self::Debug => 5,
            Self::Trace => 6,
        }
    }
}

impl std::str::FromStr for SdkLogLevel {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_ascii_lowercase().as_str() {
            "off" => Ok(Self::Off),
            "fatal" => Ok(Self::Fatal),
            "error" => Ok(Self::Error),
            "warn" | "warning" => Ok(Self::Warn),
            "info" => Ok(Self::Info),
            "debug" => Ok(Self::Debug),
            "trace" => Ok(Self::Trace),
            _ => Err(format!(
                "invalid SDK log level: {s} (expected off/fatal/error/warn/info/debug/trace)"
            )),
        }
    }
}

pub fn register_sdk_logging(level: SdkLogLevel) {
    if level == SdkLogLevel::Off {
        return;
    }

    INIT.call_once(|| {
        let rc = unsafe {
            xbbg_core::ffi::blpapi_Logging_registerCallback(
                Some(sdk_callback),
                level.to_raw() as xbbg_core::ffi::blpapi_Logging_Severity_t,
            )
        };
        if rc != 0 {
            xbbg_log::warn!("failed to register Bloomberg SDK logging callback: rc={rc}");
        } else {
            xbbg_log::info!(level = ?level, "Bloomberg SDK logging callback registered");
        }
    });
}
