use crate::{
    config::{ConfigManager, ConfigResponse, HotConfigChangeListener, config_build},
    nacos::NacosConfig,
};
use nacos_sdk::api::{
    config::{ConfigChangeListener, ConfigService, ConfigServiceBuilder},
    error::Error,
    props::ClientProps,
};
use std::sync::Arc;
use tracing::{debug, error};

#[derive(Clone)]
pub struct NacosConfiguration {
    config_service: Arc<ConfigService>,
}

impl NacosConfiguration {
    pub async fn init_nacos_configuration(
        config: Arc<NacosConfig>,
    ) -> Result<NacosConfiguration, Error> {
        let mut client_props = ClientProps::new();
        client_props = client_props
            .server_addr(config.server_addr.clone())
            .namespace(
                config
                    .namespace
                    .as_ref()
                    .map_or(Default::default(), |e| e.clone()),
            )
            .auth_username(
                config
                    .username
                    .as_ref()
                    .map_or(Default::default(), |e| e.clone()),
            )
            .auth_password(
                config
                    .password
                    .as_ref()
                    .map_or(Default::default(), |e| e.clone()),
            );
        let builder = ConfigServiceBuilder::new(client_props.clone());
        let builder = if config.username.is_some() {
            builder.enable_auth_plugin_http()
        } else {
            builder
        };
        let config_service = Arc::new(builder.build()?);
        Ok(NacosConfiguration { config_service })
    }

    pub async fn get_config<T: serde::de::DeserializeOwned>(
        &self,
        data_id: &str,
        group: &str,
    ) -> Result<T, crate::error::Error> {
        let nacos_config_response = self
            .config_service
            .get_config(data_id.to_owned(), group.to_owned())
            .await
            .map_err(|error| crate::error::Error::ConfigError(Box::new(error)))?;
        config_build(ConfigResponse {
            content_type: nacos_config_response.content_type().to_owned(),
            content: nacos_config_response.content().to_owned(),
        })
    }

    pub async fn get_config_manager<T: serde::de::DeserializeOwned + Send + Sync + 'static>(
        &self,
        data_id: &str,
        group: &str,
    ) -> Result<ConfigManager<T>, crate::error::Error> {
        let temp_ident: T = self.get_config(data_id, group).await?;
        let (config_listener, receiver) = HotConfigChangeListener::new();
        let config_manager = ConfigManager::build_hot_config(temp_ident, receiver)?;
        self.config_service
            .add_listener(
                data_id.to_owned(),
                group.to_owned(),
                Arc::new(config_listener),
            )
            .await
            .map_err(|error| crate::error::Error::ConfigError(Box::new(error)))?;
        Ok(config_manager)
    }
}

impl ConfigChangeListener for HotConfigChangeListener {
    fn notify(&self, config_resp: nacos_sdk::api::config::ConfigResponse) {
        let sender = self.sender.clone();
        tokio::spawn(async move {
            debug!("Listener ConfigResponse Change : {}", config_resp);
            if let Err(error) = sender
                .send(ConfigResponse {
                    content_type: config_resp.content_type().to_owned(),
                    content: config_resp.content().to_owned(),
                })
                .await
            {
                error!("listener error : {}", error);
            }
        });
    }
}
