// Copyright (c) Huawei Technologies Co., Ltd. 2026-2026. All rights reserved.

//! Proxy configuration center with singleton pattern.

use std::ffi::CString;
use std::sync::OnceLock;

use parking_lot::Mutex;
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};

use crate::types::SetLogCallbackFunc;
use common::constants::PSK_LEN;

#[derive(Zeroize, ZeroizeOnDrop)]
pub struct SecurePsk(pub [u8; PSK_LEN]);

pub struct ConfigCenter {
    pub ra_agent_url: String,
    pub kdc_agent_url: String,
    pub capath: String,
    pub certpath: String,
    pub privatepath: String,
    pub crlpath: String,
    pub key_pwd: Zeroizing<String>,
    pub psk: Option<SecurePsk>,
}

impl ConfigCenter {
    fn new() -> Self {
        Self {
            ra_agent_url: String::new(),
            kdc_agent_url: String::new(),
            capath: String::new(),
            certpath: String::new(),
            privatepath: String::new(),
            crlpath: String::new(),
            key_pwd: Zeroizing::new(String::new()),
            psk: None,
        }
    }
}

static CONFIG: OnceLock<Mutex<ConfigCenter>> = OnceLock::new();

pub fn get_config_center() -> &'static Mutex<ConfigCenter> {
    CONFIG.get_or_init(|| Mutex::new(ConfigCenter::new()))
}

static LOG_CALLBACK: OnceLock<Mutex<Option<SetLogCallbackFunc>>> = OnceLock::new();

pub fn get_log_callback_holder() -> &'static Mutex<Option<SetLogCallbackFunc>> {
    LOG_CALLBACK.get_or_init(|| Mutex::new(None))
}

pub fn proxy_log_impl(severity: i32, msg: &str, file: &str, line: u32) {
    let holder = get_log_callback_holder();
    let guard = holder.lock();
    let cb = *guard;
    drop(guard);
    if let Some(cb) = cb {
        if let (Ok(c_msg), Ok(c_file)) = (CString::new(msg), CString::new(file)) {
            cb(severity, c_msg.as_ptr(), c_file.as_ptr(), line as i32);
        }
    }
}

#[macro_export]
macro_rules! proxy_log {
    ($severity:expr, $msg:expr) => {
        $crate::config_manager::proxy_log_impl($severity, $msg, file!(), line!())
    };
}

#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
    use super::*;

    fn reset_config() {
        let config = get_config_center();
        let mut guard = config.lock();
        guard.ra_agent_url = String::new();
        guard.kdc_agent_url = String::new();
        guard.capath = String::new();
        guard.certpath = String::new();
        guard.privatepath = String::new();
        guard.crlpath = String::new();
        guard.key_pwd = Zeroizing::new(String::new());
        guard.psk = None;
    }

    // Test: verify get_config_center() returns a valid singleton reference that can be locked
    #[test]
    fn test_singleton_returns_valid_reference() {
        let config = get_config_center();
        let _guard = config.lock();
    }

    // Test: verify initial state has empty strings and no PSK
    #[test]
    fn test_initial_state() {
        let config = get_config_center();
        let guard = config.lock();
        assert!(guard.ra_agent_url.is_empty());
        assert!(guard.kdc_agent_url.is_empty());
        assert!(guard.capath.is_empty());
        assert!(guard.certpath.is_empty());
        assert!(guard.privatepath.is_empty());
        assert!(guard.crlpath.is_empty());
        assert!(guard.key_pwd.is_empty());
        assert!(guard.psk.is_none());
    }

    // Test: verify set and get URLs works through the singleton
    #[test]
    fn test_set_and_get_urls() {
        reset_config();
        let config = get_config_center();
        {
            let mut guard = config.lock();
            guard.ra_agent_url = "https://ra.example.com".to_string();
            guard.kdc_agent_url = "https://kdc.example.com".to_string();
        }
        let guard = config.lock();
        assert_eq!(guard.ra_agent_url, "https://ra.example.com");
        assert_eq!(guard.kdc_agent_url, "https://kdc.example.com");
    }

    // Test: verify PSK lifecycle: set, get, clear
    #[test]
    fn test_psk_lifecycle() {
        reset_config();
        let config = get_config_center();
        {
            let guard = config.lock();
            assert!(guard.psk.is_none());
        }
        {
            let mut guard = config.lock();
            guard.psk = Some(SecurePsk([0u8; PSK_LEN]));
        }
        {
            let guard = config.lock();
            assert!(guard.psk.is_some());
            assert_eq!(guard.psk.as_ref().unwrap().0, [0u8; PSK_LEN]);
        }
        {
            let mut guard = config.lock();
            guard.psk = None;
        }
        let guard = config.lock();
        assert!(guard.psk.is_none());
    }

    // Test: verify get_log_callback_holder() returns holder with initial state None
    #[test]
    fn test_log_callback_holder_initial_state() {
        *get_log_callback_holder().lock() = None;
        {
            let holder = get_log_callback_holder();
            let guard = holder.lock();
            assert!(guard.is_none());
        }
        *get_log_callback_holder().lock() = None;
    }

    // Test: verify get_log_callback_holder() can set and retrieve a callback
    #[test]
    fn test_log_callback_set_and_get() {
        *get_log_callback_holder().lock() = None;
        *get_log_callback_holder().lock() = Some(test_log_cb);
        {
            let holder = get_log_callback_holder();
            let guard = holder.lock();
            assert!(guard.is_some());
        }
        *get_log_callback_holder().lock() = None;
    }

    extern "C" fn test_log_cb(
        _severity: i32,
        _msg: *const std::os::raw::c_char,
        _file: *const std::os::raw::c_char,
        _line: i32,
    ) {
    }

    // Test: verify proxy_log_impl does nothing when no callback is set
    #[test]
    fn test_proxy_log_impl_no_callback() {
        *get_log_callback_holder().lock() = None;
        proxy_log_impl(1, "no callback set", "test.rs", 1);
    }

    // Test: verify proxy_log_impl invokes callback when set
    #[test]
    fn test_proxy_log_impl_with_callback() {
        *get_log_callback_holder().lock() = Some(test_log_cb);
        proxy_log_impl(1, "with callback", "test.rs", 1);
        *get_log_callback_holder().lock() = None;
    }

    // Test: verify proxy_log_impl handles message with embedded NUL byte gracefully
    #[test]
    fn test_proxy_log_impl_with_nul_message() {
        *get_log_callback_holder().lock() = Some(test_log_cb);
        proxy_log_impl(1, "hello\0world", "test.rs", 1);
        *get_log_callback_holder().lock() = None;
    }
}