use std::collections::HashMap;

use crate::{
    datafile::{datafile::DataFile as InternalDataFile, feature_flag::FeatureFlag as InternalFeatureFlag},
    types::FeatureFlag,
};

#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub struct DataFile {
    pub feature_flags: HashMap<String, FeatureFlag>,
    pub date_modified: u64,
}

impl DataFile {
    pub(crate) fn from_internal(datafile: &impl InternalDataFile) -> Self {
        let feature_flags = datafile
            .ordered_feature_flags()
            .iter()
            .map(|feature_flag| {
                (feature_flag.feature_key().to_owned(), FeatureFlag::from_internal(feature_flag.as_ref()))
            })
            .collect();
        Self {
            feature_flags,
            date_modified: datafile.date_modified(),
        }
    }
}

#[cfg(test)]
mod tests {
    use std::collections::HashMap;

    use crate::{
        types::{JsonValue, Variable, Variation},
        utils::test_utils::load_datafile,
    };

    use super::DataFile;

    fn load_test_datafile() -> DataFile {
        let internal_datafile = load_datafile("resources/datafiles/datafile_tndueuutdq.json");
        DataFile::from_internal(&internal_datafile)
    }

    fn variable(key: &str, kind: &str, value: JsonValue) -> Variable {
        Variable {
            key: key.to_owned(),
            kind: kind.to_owned(),
            value,
        }
    }

    fn variation(
        name: &str,
        key: &str,
        id: Option<u32>,
        experiment_id: Option<u32>,
        variables: Vec<Variable>,
    ) -> Variation {
        Variation {
            key: key.to_owned(),
            name: name.to_owned(),
            id,
            experiment_id,
            variables,
        }
    }

    fn vars1() -> Vec<Variable> {
        vec![
            variable("variable_string", "STRING", JsonValue::String("variation".to_owned())),
            variable("variable_number", "NUMBER", JsonValue::Number(0.0)),
            variable("variable_bool", "BOOLEAN", JsonValue::Boolean(true)),
            variable("variable_json", "JSON", JsonValue::Json("{}".to_owned())),
        ]
    }

    fn vars2() -> Vec<Variable> {
        vec![
            variable("variable_string", "STRING", JsonValue::String("".to_owned())),
            variable("variable_number", "NUMBER", JsonValue::Number(0.0)),
            variable("variable_bool", "BOOLEAN", JsonValue::Boolean(true)),
            variable("variable_json", "JSON", JsonValue::Json("{}".to_owned())),
        ]
    }

    fn vars_on() -> Vec<Variable> {
        vec![
            variable("variable_string", "STRING", JsonValue::String("".to_owned())),
            variable("variable_number", "NUMBER", JsonValue::Number(11.0)),
            variable("variable_bool", "BOOLEAN", JsonValue::Boolean(false)),
            variable("variable_json", "JSON", JsonValue::Json("{}".to_owned())),
        ]
    }

    fn vars_off() -> Vec<Variable> {
        vec![
            variable("variable_string", "STRING", JsonValue::String("".to_owned())),
            variable("variable_number", "NUMBER", JsonValue::Number(0.0)),
            variable("variable_bool", "BOOLEAN", JsonValue::Boolean(true)),
            variable("variable_json", "JSON", JsonValue::Json("{\"json\": \"off\"}".to_owned())),
        ]
    }

    fn assert_variation_eq(expected: &Variation, actual: &Variation) {
        assert_eq!(expected.name, actual.name);
        assert_eq!(expected.key, actual.key);
        assert_eq!(expected.id, actual.id);
        assert_eq!(expected.experiment_id, actual.experiment_id);
        assert_eq!(expected, actual);
    }

    fn assert_variation_map_eq(expected: &HashMap<String, Variation>, actual: &HashMap<String, Variation>) {
        assert_eq!(expected.len(), actual.len());
        for (key, expected_variation) in expected {
            let actual_variation = actual.get(key).unwrap_or_else(|| panic!("Missing variation: {key}"));
            assert_variation_eq(expected_variation, actual_variation);
        }
    }

    #[test]
    fn test_all_feature_flags() {
        #[rustfmt::skip]
        let mut expected_feature_keys = vec![
            "bucketing_custom_data", "complextargeting", "complextargeting_2", "copy_copy_copy_test_first_level_or_operator",
            "copy_copy_test_first_level_or_operator", "copy_test_first_level_or_operator", "feature_flag_campaign_conditions",
            "feature_flag_can_change_segment__no_tests_for_this_ff_", "feature_flag_disabled_all_enviroments", "feature_flag_liga_stavok",
            "feature_flag_multi_environment_test", "feature_flag_test_real_time_configuration_service",
            "feature_flag_test_scheduling", "ff_new_rules", "sdk_android_technical_conditions", "sdk_campaigns",
            "sdk_ios_technical_conditions", "sdk_js", "sdk_segments", "sdk_technical_conditions", "sdk_utility",
            "sdk_visit_behavior", "sdk_visited_pages", "test_feature_variables", "test_first_level_or_operator",
            "test_pagination_1", "test_pagination_10", "test_pagination_11", "test_pagination_12", "test_pagination_2",
            "test_pagination_3_", "test_pagination_4", "test_pagination_5", "test_pagination_6", "test_pagination_7",
            "test_pagination_8", "test_pagination_9", "test_segment_lower_greater", "test_visitorcode___goal_conversion",
            "testff", "mutually_exclusive_flag_1", "mutually_exclusive_flag_2",
        ];
        expected_feature_keys.sort_unstable();

        let datafile = load_test_datafile();
        let mut actual_feature_keys = datafile.feature_flags.keys().map(String::as_str).collect::<Vec<_>>();
        actual_feature_keys.sort_unstable();

        assert_eq!(expected_feature_keys, actual_feature_keys);
    }

    #[test]
    fn test_date_modified() {
        let datafile = load_test_datafile();

        assert_eq!(1755774975000, datafile.date_modified);
    }

    #[test]
    fn test_feature_flag_variations() {
        let expected_variations = HashMap::from([
            ("variation_1".to_owned(), variation("variation_1", "variation_1", None, None, vars1())),
            ("variation_2".to_owned(), variation("variation_2", "variation_2", None, None, vars2())),
            ("on".to_owned(), variation("On", "on", None, None, vars_on())),
            ("off".to_owned(), variation("Off", "off", None, None, vars_off())),
        ]);

        let datafile = load_test_datafile();
        let feature_flag = datafile.feature_flags.get("ff_new_rules").unwrap();

        assert_variation_map_eq(&expected_variations, &feature_flag.variations);
    }

    #[test]
    fn test_feature_flag_environment() {
        let datafile = load_test_datafile();

        let feature_flag_env_enabled = datafile.feature_flags.get("ff_new_rules").unwrap();
        let feature_flag_env_disabled = datafile.feature_flags.get("feature_flag_disabled_all_enviroments").unwrap();

        assert!(feature_flag_env_enabled.environment_enabled);
        assert!(!feature_flag_env_disabled.environment_enabled);
    }

    #[test]
    fn test_default_variation_key() {
        let datafile = load_test_datafile();
        let feature_flag = datafile.feature_flags.get("ff_new_rules").unwrap();

        assert_eq!("off", feature_flag.default_variation_key);
    }

    #[test]
    fn test_default_variation() {
        let expected_variation = variation("Off", "off", None, None, vars_off());

        let datafile = load_test_datafile();
        let feature_flag = datafile.feature_flags.get("ff_new_rules").unwrap();
        let actual_variation = feature_flag.default_variation();

        assert_variation_eq(&expected_variation, actual_variation);
    }

    #[test]
    fn testrule_variations() {
        let expected_variations_rule_1 = HashMap::from([
            ("variation_1".to_owned(), variation("variation_1", "variation_1", Some(859380), Some(202387), vars1())),
            ("variation_2".to_owned(), variation("variation_2", "variation_2", Some(859381), Some(202387), vars2())),
            ("off".to_owned(), variation("Off", "off", Some(0), Some(202387), vars_off())),
        ]);
        let expected_variations_rule_2 =
            HashMap::from([("on".to_owned(), variation("On", "on", Some(866770), Some(202386), vars_on()))]);

        let datafile = load_test_datafile();
        let feature_flag = datafile.feature_flags.get("ff_new_rules").unwrap();

        assert_eq!(2, feature_flag.rules.len());
        assert_variation_map_eq(&expected_variations_rule_1, &feature_flag.rules[0].variations);
        assert_variation_map_eq(&expected_variations_rule_2, &feature_flag.rules[1].variations);
    }
}
