use crate::{
    config::KameleoonClientConfig,
    core_client::CoreKameleoonClient,
    error::{ErrorCode, KErr},
    logging::LogLevel,
    utils::KRes,
};
use parking_lot::Mutex;
use std::{
    collections::HashMap,
    sync::{Arc, LazyLock},
};
use kameleoon_trace_logging::trace_method;

static CLIENTS: LazyLock<Mutex<HashMap<String, Arc<CoreKameleoonClient>>>> =
    LazyLock::new(|| Mutex::new(HashMap::new()));

pub struct CoreKameleoonClientFactory;

impl CoreKameleoonClientFactory {
    #[trace_method(LogLevel::Info, skip(retval))]
    pub fn create(
        site_code: &str,
        config_path: Option<&str>,
        config: Option<KameleoonClientConfig>,
        sdk_name: &str,
        sdk_version: &str,
    ) -> KRes<Arc<CoreKameleoonClient>> {
        if site_code.is_empty() {
            return Err(KErr::new(ErrorCode::SiteCodeIsEmpty, "site code is empty"));
        }
        let config = config.unwrap_or_else(|| {
            config_path.and_then(KameleoonClientConfig::read_from_file).unwrap_or_else(KameleoonClientConfig::default)
        });
        let config = config.correct();
        config.validate()?;

        let mut clients = CLIENTS.lock();
        let key = Self::client_key(site_code, config.environment.as_deref());
        if let Some(client) = clients.get(&key) {
            return Ok(client.clone());
        }
        let site_code = site_code.to_owned();
        let sdk_name = sdk_name.to_owned();
        let sdk_version = sdk_version.to_owned();
        let client = Arc::new(CoreKameleoonClient::new(site_code, config, sdk_name, sdk_version)?);
        clients.insert(key, client.clone());
        Ok(client)
    }

    #[trace_method(LogLevel::Info, skip(retval))]
    pub fn forget(site_code: &str) -> KRes<()> {
        Self::forget_with_params(site_code, ForgetParams::None)
    }

    #[trace_method(LogLevel::Info, skip(params, retval))]
    pub fn forget_with_params(site_code: &str, params: ForgetParams) -> KRes<()> {
        let mut clients = CLIENTS.lock();
        clients.remove(&Self::client_key(site_code, params.environment()));
        Ok(())
    }

    fn client_key(site_code: &str, environment: Option<&str>) -> String {
        environment.map(|env| format!("{site_code}_{env}")).unwrap_or_else(|| site_code.to_owned())
    }
}

#[derive(Debug)]
pub enum ForgetParams<'a> {
    None,
    Environment(&'a str),
}

impl<'a> ForgetParams<'a> {
    fn environment(self) -> Option<&'a str> {
        match self {
            Self::None => None,
            Self::Environment(env) => Some(env),
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::{config::KameleoonClientConfig, core_factory::CoreKameleoonClientFactory, error::ErrorCode};

    #[test]
    fn test_create_with_config_returns_error_for_empty_site_code() {
        let err = match CoreKameleoonClientFactory::create("", None, Some(KameleoonClientConfig::default()), "", "") {
            Ok(_) => panic!("Expected error"),
            Err(e) => e,
        };

        assert_eq!(err.code(), ErrorCode::SiteCodeIsEmpty);
        assert_eq!(err.message(), "site code is empty");
    }

    #[test]
    fn test_create_with_config_returns_error_for_invalid_config() {
        const SITE_CODE: &str = "siteCode";
        let err =
            match CoreKameleoonClientFactory::create(SITE_CODE, None, Some(KameleoonClientConfig::default()), "", "") {
                Ok(_) => panic!("Expected error"),
                Err(e) => e,
            };

        assert_eq!(err.code(), ErrorCode::ConfigCredentialsInvalid);
        assert_eq!(err.message(), "Client ID is empty");
    }
}
