use super::stores::store::DataStore;
use serde::Serialize;
use launchdarkly_server_sdk_evaluation::{evaluate, Context, FlagValue, Reason};
use std::collections::HashMap;
use std::time::SystemTime;
#[derive(Clone, Copy, Default)]
pub struct FlagDetailConfig {
    client_side_only: bool,
    with_reasons: bool,
    details_only_for_tracked_flags: bool,
}
impl FlagDetailConfig {
    pub fn new() -> Self {
        Self {
            client_side_only: false,
            with_reasons: false,
            details_only_for_tracked_flags: false,
        }
    }
    pub fn client_side_only(&mut self) -> &mut Self {
        self.client_side_only = true;
        self
    }
    pub fn with_reasons(&mut self) -> &mut Self {
        self.with_reasons = true;
        self
    }
    pub fn details_only_for_tracked_flags(&mut self) -> &mut Self {
        self.details_only_for_tracked_flags = true;
        self
    }
}
#[derive(Serialize, Default, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct FlagState {
    #[serde(skip_serializing_if = "Option::is_none")]
    version: Option<u64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    variation: Option<isize>,
    #[serde(skip_serializing_if = "Option::is_none")]
    reason: Option<Reason>,
    #[serde(skip_serializing_if = "std::ops::Not::not")]
    track_events: bool,
    #[serde(skip_serializing_if = "std::ops::Not::not")]
    track_reason: bool,
    #[serde(skip_serializing_if = "Option::is_none")]
    debug_events_until_date: Option<u64>,
}
#[derive(Serialize, Clone, Debug)]
pub struct FlagDetail {
    #[serde(flatten)]
    evaluations: HashMap<String, Option<FlagValue>>,
    #[serde(rename = "$flagsState")]
    flag_state: HashMap<String, FlagState>,
    #[serde(rename = "$valid")]
    valid: bool,
}
impl FlagDetail {
    pub fn new(valid: bool) -> Self {
        Self {
            evaluations: HashMap::new(),
            flag_state: HashMap::new(),
            valid,
        }
    }
    pub fn populate(&mut self, store: &dyn DataStore, context: &Context, config: FlagDetailConfig) {
        let mut evaluations = HashMap::new();
        let mut flag_state = HashMap::new();
        for (key, flag) in store.all_flags() {
            if config.client_side_only && !flag.using_environment_id() {
                continue;
            }
            let detail = evaluate(store.to_store(), &flag, context, None);
            let require_experiment_data = flag.is_experimentation_enabled(&detail.reason);
            let track_events = flag.track_events || require_experiment_data;
            let track_reason = require_experiment_data;
            let currently_debugging = match flag.debug_events_until_date {
                Some(time) => {
                    let today = SystemTime::now();
                    let today_millis = today
                        .duration_since(SystemTime::UNIX_EPOCH)
                        .unwrap()
                        .as_millis();
                    (time as u128) > today_millis
                }
                None => false,
            };
            let mut omit_details = false;
            if config.details_only_for_tracked_flags
                && !(track_events
                    || track_reason
                    || flag.debug_events_until_date.is_some() && currently_debugging)
            {
                omit_details = true;
            }
            let mut reason = if !config.with_reasons && !track_reason {
                None
            } else {
                Some(detail.reason)
            };
            let mut version = Some(flag.version);
            if omit_details {
                reason = None;
                version = None;
            }
            evaluations.insert(key.clone(), detail.value.cloned());
            flag_state.insert(
                key,
                FlagState {
                    version,
                    variation: detail.variation_index,
                    reason,
                    track_events,
                    track_reason,
                    debug_events_until_date: flag.debug_events_until_date,
                },
            );
        }
        self.evaluations = evaluations;
        self.flag_state = flag_state;
    }
}
#[cfg(test)]
mod tests {
    use crate::evaluation::FlagDetail;
    use crate::stores::store::DataStore;
    use crate::stores::store::InMemoryDataStore;
    use crate::stores::store_types::{PatchTarget, StorageItem};
    use crate::test_common::basic_flag;
    use crate::FlagDetailConfig;
    use launchdarkly_server_sdk_evaluation::ContextBuilder;
    #[test]
    fn flag_detail_handles_default_configuration() {
        let context = ContextBuilder::new("bob")
            .build()
            .expect("Failed to create context");
        let mut store = InMemoryDataStore::new();
        store
            .upsert(
                "myFlag",
                PatchTarget::Flag(StorageItem::Item(basic_flag("myFlag"))),
            )
            .expect("patch should apply");
        let mut flag_detail = FlagDetail::new(true);
        flag_detail.populate(&store, &context, FlagDetailConfig::new());
        let expected = json!({
            "myFlag": true,
            "$flagsState": {
                "myFlag": {
                    "version": 42,
                    "variation": 1
                }
            },
            "$valid": true
        });
        assert_eq!(
            serde_json::to_string_pretty(&flag_detail).unwrap(),
            serde_json::to_string_pretty(&expected).unwrap(),
        );
    }
    #[test]
    fn flag_detail_handles_experimentation_reasons_correctly() {
        let context = ContextBuilder::new("bob")
            .build()
            .expect("Failed to create context");
        let mut store = InMemoryDataStore::new();
        let mut flag = basic_flag("myFlag");
        flag.track_events = false;
        flag.track_events_fallthrough = true;
        store
            .upsert("myFlag", PatchTarget::Flag(StorageItem::Item(flag)))
            .expect("patch should apply");
        let mut flag_detail = FlagDetail::new(true);
        flag_detail.populate(&store, &context, FlagDetailConfig::new());
        let expected = json!({
            "myFlag": true,
            "$flagsState": {
                "myFlag": {
                    "version": 42,
                    "variation": 1,
                    "reason": {
                        "kind": "FALLTHROUGH",
                    },
                    "trackEvents": true,
                    "trackReason": true,
                }
            },
            "$valid": true
        });
        assert_eq!(
            serde_json::to_string_pretty(&flag_detail).unwrap(),
            serde_json::to_string_pretty(&expected).unwrap(),
        );
    }
    #[test]
    fn flag_detail_with_reasons_should_include_reason() {
        let context = ContextBuilder::new("bob")
            .build()
            .expect("Failed to create context");
        let mut store = InMemoryDataStore::new();
        store
            .upsert(
                "myFlag",
                PatchTarget::Flag(StorageItem::Item(basic_flag("myFlag"))),
            )
            .expect("patch should apply");
        let mut config = FlagDetailConfig::new();
        config.with_reasons();
        let mut flag_detail = FlagDetail::new(true);
        flag_detail.populate(&store, &context, config);
        let expected = json!({
            "myFlag": true,
            "$flagsState": {
                "myFlag": {
                    "version": 42,
                    "variation": 1,
                    "reason": {
                        "kind": "FALLTHROUGH"
                    }
                }
            },
            "$valid": true
        });
        assert_eq!(
            serde_json::to_string_pretty(&flag_detail).unwrap(),
            serde_json::to_string_pretty(&expected).unwrap(),
        );
    }
    #[test]
    fn flag_detail_details_only_should_exclude_reason() {
        let context = ContextBuilder::new("bob")
            .build()
            .expect("Failed to create context");
        let mut store = InMemoryDataStore::new();
        store
            .upsert(
                "myFlag",
                PatchTarget::Flag(StorageItem::Item(basic_flag("myFlag"))),
            )
            .expect("patch should apply");
        let mut config = FlagDetailConfig::new();
        config.details_only_for_tracked_flags();
        let mut flag_detail = FlagDetail::new(true);
        flag_detail.populate(&store, &context, config);
        let expected = json!({
            "myFlag": true,
            "$flagsState": {
                "myFlag": {
                    "variation": 1,
                }
            },
            "$valid": true
        });
        assert_eq!(
            serde_json::to_string_pretty(&flag_detail).unwrap(),
            serde_json::to_string_pretty(&expected).unwrap(),
        );
    }
    #[test]
    fn flag_detail_details_only_with_tracked_events_includes_version() {
        let context = ContextBuilder::new("bob")
            .build()
            .expect("Failed to create context");
        let mut store = InMemoryDataStore::new();
        let mut flag = basic_flag("myFlag");
        flag.track_events = true;
        store
            .upsert("myFlag", PatchTarget::Flag(StorageItem::Item(flag)))
            .expect("patch should apply");
        let mut config = FlagDetailConfig::new();
        config.details_only_for_tracked_flags();
        let mut flag_detail = FlagDetail::new(true);
        flag_detail.populate(&store, &context, config);
        let expected = json!({
            "myFlag": true,
            "$flagsState": {
                "myFlag": {
                    "version": 42,
                    "variation": 1,
                    "trackEvents": true,
                }
            },
            "$valid": true
        });
        assert_eq!(
            serde_json::to_string_pretty(&flag_detail).unwrap(),
            serde_json::to_string_pretty(&expected).unwrap(),
        );
    }
    #[test]
    fn flag_detail_with_default_config_but_tracked_event_should_include_version() {
        let context = ContextBuilder::new("bob")
            .build()
            .expect("Failed to create context");
        let mut store = InMemoryDataStore::new();
        let mut flag = basic_flag("myFlag");
        flag.track_events = true;
        store
            .upsert("myFlag", PatchTarget::Flag(StorageItem::Item(flag)))
            .expect("patch should apply");
        let mut flag_detail = FlagDetail::new(true);
        flag_detail.populate(&store, &context, FlagDetailConfig::new());
        let expected = json!({
            "myFlag": true,
            "$flagsState": {
                "myFlag": {
                    "version": 42,
                    "variation": 1,
                    "trackEvents": true,
                }
            },
            "$valid": true
        });
        assert_eq!(
            serde_json::to_string_pretty(&flag_detail).unwrap(),
            serde_json::to_string_pretty(&expected).unwrap(),
        );
    }
}