use crate::data_source_builders::{DataSourceFactory, NullDataSourceBuilder};
use crate::events::processor_builders::{
    EventProcessorBuilder, EventProcessorFactory, NullEventProcessorBuilder,
};
use crate::stores::store_builders::{DataStoreFactory, InMemoryDataStoreBuilder};
use crate::{ServiceEndpointsBuilder, StreamingDataSourceBuilder};
use std::borrow::Borrow;
#[derive(Debug)]
struct Tag {
    key: String,
    value: String,
}
impl Tag {
    fn is_valid(&self) -> Result<(), &str> {
        if self.value.chars().count() > 64 {
            return Err("Value was longer than 64 characters and was discarded");
        }
        if self.key.is_empty() || !self.key.chars().all(Tag::valid_characters) {
            return Err("Key was empty or contained invalid characters");
        }
        if self.value.is_empty() || !self.value.chars().all(Tag::valid_characters) {
            return Err("Value was empty or contained invalid characters");
        }
        Ok(())
    }
    fn valid_characters(c: char) -> bool {
        c.is_ascii_alphanumeric() || matches!(c, '-' | '.' | '_')
    }
}
impl ToString for &Tag {
    fn to_string(&self) -> String {
        format!("{}/{}", self.key, self.value)
    }
}
pub struct ApplicationInfo {
    tags: Vec<Tag>,
}
impl ApplicationInfo {
    pub fn new() -> Self {
        Self { tags: Vec::new() }
    }
    pub fn application_identifier(&mut self, application_id: impl Into<String>) -> &mut Self {
        self.add_tag("application-id", application_id)
    }
    pub fn application_version(&mut self, application_version: impl Into<String>) -> &mut Self {
        self.add_tag("application-version", application_version)
    }
    fn add_tag(&mut self, key: impl Into<String>, value: impl Into<String>) -> &mut Self {
        let tag = Tag {
            key: key.into(),
            value: value.into(),
        };
        match tag.is_valid() {
            Ok(_) => self.tags.push(tag),
            Err(e) => {
                warn!("{}", e)
            }
        }
        self
    }
    pub(crate) fn build(&self) -> Option<String> {
        if self.tags.is_empty() {
            return None;
        }
        let mut tags = self
            .tags
            .iter()
            .map(|tag| tag.to_string())
            .collect::<Vec<String>>();
        tags.sort();
        tags.dedup();
        Some(tags.join(" "))
    }
}
impl Default for ApplicationInfo {
    fn default() -> Self {
        Self::new()
    }
}
pub struct Config {
    sdk_key: String,
    service_endpoints_builder: ServiceEndpointsBuilder,
    data_store_builder: Box<dyn DataStoreFactory>,
    data_source_builder: Box<dyn DataSourceFactory>,
    event_processor_builder: Box<dyn EventProcessorFactory>,
    application_tag: Option<String>,
    offline: bool,
}
impl Config {
    pub fn sdk_key(&self) -> &str {
        &self.sdk_key
    }
    pub fn service_endpoints_builder(&self) -> &ServiceEndpointsBuilder {
        &self.service_endpoints_builder
    }
    pub fn data_store_builder(&self) -> &(dyn DataStoreFactory) {
        self.data_store_builder.borrow()
    }
    pub fn data_source_builder(&self) -> &(dyn DataSourceFactory) {
        self.data_source_builder.borrow()
    }
    pub fn event_processor_builder(&self) -> &(dyn EventProcessorFactory) {
        self.event_processor_builder.borrow()
    }
    pub fn offline(&self) -> bool {
        self.offline
    }
    pub fn application_tag(&self) -> &Option<String> {
        &self.application_tag
    }
}
pub struct ConfigBuilder {
    service_endpoints_builder: Option<ServiceEndpointsBuilder>,
    data_store_builder: Option<Box<dyn DataStoreFactory>>,
    data_source_builder: Option<Box<dyn DataSourceFactory>>,
    event_processor_builder: Option<Box<dyn EventProcessorFactory>>,
    application_info: Option<ApplicationInfo>,
    offline: bool,
    sdk_key: String,
}
impl ConfigBuilder {
    pub fn new(sdk_key: &str) -> Self {
        Self {
            service_endpoints_builder: None,
            data_store_builder: None,
            data_source_builder: None,
            event_processor_builder: None,
            offline: false,
            application_info: None,
            sdk_key: sdk_key.to_string(),
        }
    }
    pub fn service_endpoints(mut self, builder: &ServiceEndpointsBuilder) -> Self {
        self.service_endpoints_builder = Some(builder.clone());
        self
    }
    pub fn data_store(mut self, builder: &dyn DataStoreFactory) -> Self {
        self.data_store_builder = Some(builder.to_owned());
        self
    }
    pub fn data_source(mut self, builder: &dyn DataSourceFactory) -> Self {
        self.data_source_builder = Some(builder.to_owned());
        self
    }
    pub fn event_processor(mut self, builder: &dyn EventProcessorFactory) -> Self {
        self.event_processor_builder = Some(builder.to_owned());
        self
    }
    pub fn offline(mut self, offline: bool) -> Self {
        self.offline = offline;
        self
    }
    pub fn application_info(mut self, application_info: ApplicationInfo) -> Self {
        self.application_info = Some(application_info);
        self
    }
    pub fn build(self) -> Config {
        let service_endpoints_builder = match &self.service_endpoints_builder {
            None => ServiceEndpointsBuilder::new(),
            Some(service_endpoints_builder) => service_endpoints_builder.clone(),
        };
        let data_store_builder = match &self.data_store_builder {
            None => Box::new(InMemoryDataStoreBuilder::new()),
            Some(_data_store_builder) => self.data_store_builder.unwrap(),
        };
        let data_source_builder: Box<dyn DataSourceFactory> = match &self.data_source_builder {
            None if self.offline => Box::new(NullDataSourceBuilder::new()),
            Some(_) if self.offline => {
                warn!("Custom data source builders will be ignored when in offline mode");
                Box::new(NullDataSourceBuilder::new())
            }
            None => Box::new(StreamingDataSourceBuilder::new()),
            Some(_data_source_builder) => self.data_source_builder.unwrap(),
        };
        let event_processor_builder: Box<dyn EventProcessorFactory> =
            match &self.event_processor_builder {
                None if self.offline => Box::new(NullEventProcessorBuilder::new()),
                Some(_) if self.offline => {
                    warn!("Custom event processor builders will be ignored when in offline mode");
                    Box::new(NullEventProcessorBuilder::new())
                }
                None => Box::new(EventProcessorBuilder::new()),
                Some(_event_processor_builder) => self.event_processor_builder.unwrap(),
            };
        let application_tag = match self.application_info {
            Some(tb) => tb.build(),
            _ => None,
        };
        Config {
            sdk_key: self.sdk_key,
            service_endpoints_builder,
            data_store_builder,
            data_source_builder,
            event_processor_builder,
            application_tag,
            offline: self.offline,
        }
    }
}
#[cfg(test)]
mod tests {
    use test_case::test_case;
    use super::*;
    #[test]
    fn client_configured_with_custom_endpoints() {
        let builder = ConfigBuilder::new("sdk-key").service_endpoints(
            ServiceEndpointsBuilder::new().relay_proxy("http://my-relay-hostname:8080"),
        );
        let endpoints = builder.service_endpoints_builder.unwrap().build().unwrap();
        assert_eq!(
            endpoints.streaming_base_url(),
            "http://my-relay-hostname:8080"
        );
        assert_eq!(
            endpoints.polling_base_url(),
            "http://my-relay-hostname:8080"
        );
        assert_eq!(endpoints.events_base_url(), "http://my-relay-hostname:8080");
    }
    #[test]
    fn unconfigured_config_builder_handles_application_tags_correctly() {
        let builder = ConfigBuilder::new("sdk-key");
        let config = builder.build();
        assert_eq!(None, config.application_tag);
    }
    #[test_case("id", "version", Some("application-id/id application-version/version".to_string()))]
    #[test_case("Invalid id", "version", Some("application-version/version".to_string()))]
    #[test_case("id", "Invalid version", Some("application-id/id".to_string()))]
    #[test_case("Invalid id", "Invalid version", None)]
    fn config_builder_handles_application_tags_appropriately(
        id: impl Into<String>,
        version: impl Into<String>,
        expected: Option<String>,
    ) {
        let mut application_info = ApplicationInfo::new();
        application_info
            .application_identifier(id)
            .application_version(version);
        let builder = ConfigBuilder::new("sdk-key");
        let config = builder.application_info(application_info).build();
        assert_eq!(expected, config.application_tag);
    }
    #[test_case("", "abc", Err("Key was empty or contained invalid characters"); "Empty key")]
    #[test_case(" ", "abc", Err("Key was empty or contained invalid characters"); "Key with whitespace")]
    #[test_case("/", "abc", Err("Key was empty or contained invalid characters"); "Key with slash")]
    #[test_case(":", "abc", Err("Key was empty or contained invalid characters"); "Key with colon")]
    #[test_case("🦀", "abc", Err("Key was empty or contained invalid characters"); "Key with emoji")]
    #[test_case("abcABC123.-_", "abc", Ok(()); "Valid key")]
    #[test_case("abc", "", Err("Value was empty or contained invalid characters"); "Empty value")]
    #[test_case("abc", " ", Err("Value was empty or contained invalid characters"); "Value with whitespace")]
    #[test_case("abc", "/", Err("Value was empty or contained invalid characters"); "Value with slash")]
    #[test_case("abc", ":", Err("Value was empty or contained invalid characters"); "Value with colon")]
    #[test_case("abc", "🦀", Err("Value was empty or contained invalid characters"); "Value with emoji")]
    #[test_case("abc", "abcABC123.-_", Ok(()); "Valid value")]
    #[test_case("abc", "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkl", Ok(()); "64 is the max length")]
    #[test_case("abc", "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklm", Err("Value was longer than 64 characters and was discarded"); "65 is too far")]
    fn tag_can_determine_valid_values(key: &str, value: &str, expected_result: Result<(), &str>) {
        let tag = Tag {
            key: key.to_string(),
            value: value.to_string(),
        };
        assert_eq!(expected_result, tag.is_valid());
    }
    #[test_case(vec![], None; "No tags returns None")]
    #[test_case(vec![("application-id".into(), "gonfalon-be".into()), ("application-sha".into(), "abcdef".into())], Some("application-id/gonfalon-be application-sha/abcdef".into()); "Tags are formatted correctly")]
    #[test_case(vec![("key".into(), "xyz".into()), ("key".into(), "abc".into())], Some("key/abc key/xyz".into()); "Keys are ordered correctly")]
    #[test_case(vec![("key".into(), "abc".into()), ("key".into(), "abc".into())], Some("key/abc".into()); "Tags are deduped")]
    #[test_case(vec![("XYZ".into(), "xyz".into()), ("abc".into(), "abc".into())], Some("XYZ/xyz abc/abc".into()); "Keys are ascii sorted correctly")]
    #[test_case(vec![("abc".into(), "XYZ".into()), ("abc".into(), "abc".into())], Some("abc/XYZ abc/abc".into()); "Values are ascii sorted correctly")]
    #[test_case(vec![("".into(), "XYZ".into()), ("abc".into(), "xyz".into())], Some("abc/xyz".into()); "Invalid tags are filtered")]
    #[test_case(Vec::new(), None; "Empty tags returns None")]
    fn application_tag_builder_can_create_tag_string_correctly(
        tags: Vec<(String, String)>,
        expected_value: Option<String>,
    ) {
        let mut application_info = ApplicationInfo::new();
        tags.into_iter().for_each(|(key, value)| {
            application_info.add_tag(key, value);
        });
        assert_eq!(expected_value, application_info.build());
    }
}