use std::{collections::HashMap, iter, sync::Arc};

use kameleoon_trace_logging::trace_method;

use crate::{
    data::{BaseData, assigned_variation::AssignedVariation, forced_variation::ForcedVariation},
    datafile::{
        datafile::DataFile, experiment::Experiment, feature_flag::FeatureFlag, rule::Rule, variation::OFF_VALUE,
        variation_exposition::VariationByExposition,
    },
    error::{ErrorCode, KErr, KameleoonError},
    evaluation::{evaluated_experiment::EvaluatedExperiment, evaluation_context::EvaluationContext},
    logging::LogLevel,
    storage::visitor_manager::{AddDataOptions, VisitorManager},
    tracking::trackable::{Trackable, TrackingState},
    utils::{KRes, version::SemVer},
};

pub(crate) trait EvaluationManager {
    /// returns (variation_key, eval_exp?, vars_to_assign)
    fn get_evaluated_experiment_info<'a, D, F, R, E, V>(
        &self,
        datafile: &'a D,
        visitor_code: &'a str,
        feature_flag: &'a F,
    ) -> (&'a str, Option<EvaluatedExperiment<'a, V, E>>, Vec<AssignedVariation>)
    where
        D: DataFile<FeatureFlag = F, Experiment = E>,
        F: FeatureFlag<Rule = R> + 'static,
        R: Rule<Experiment = E> + 'static,
        E: Experiment<VariationByExposition = V> + 'static,
        V: VariationByExposition;

    fn is_feature_active<D, F, R, E, V>(
        &self,
        datafile: &D,
        visitor_code: &str,
        feature_key: &str,
        track: bool,
    ) -> KRes<bool>
    where
        D: DataFile<FeatureFlag = F, Experiment = E>,
        F: FeatureFlag<Rule = R> + 'static,
        R: Rule<Experiment = E> + 'static,
        E: Experiment<VariationByExposition = V> + 'static,
        V: VariationByExposition;

    fn get_variation<D, F, R, E, V>(
        &self,
        datafile: &D,
        visitor_code: &str,
        feature_key: &str,
        track: bool,
    ) -> KRes<crate::types::Variation>
    where
        D: DataFile<FeatureFlag = F, Experiment = E>,
        F: FeatureFlag<Rule = R> + 'static,
        R: Rule<Experiment = E> + 'static,
        E: Experiment<VariationByExposition = V> + 'static,
        V: VariationByExposition;

    fn get_variations<D, F, R, E, V>(
        &self,
        datafile: &D,
        visitor_code: &str,
        only_active: bool,
        track: bool,
    ) -> HashMap<String, crate::types::Variation>
    where
        D: DataFile<FeatureFlag = F, Experiment = E>,
        F: FeatureFlag<Rule = R> + 'static,
        R: Rule<Experiment = E> + 'static,
        E: Experiment<VariationByExposition = V> + 'static,
        V: VariationByExposition;

    fn set_forced_variation(
        &self,
        datafile: &impl DataFile,
        visitor_code: &str,
        experiment_id: u32,
        variation_key: &str,
        force_targeting: bool,
    ) -> KRes<()>;

    fn reset_forced_variation(&self, visitor_code: &str, experiment_id: u32);
}

pub(crate) struct EvaluationManagerImpl<VM> {
    visitor_manager: Arc<VM>,
    sdk_name: String,
    sdk_version: SemVer,
}

impl<VM: VisitorManager> EvaluationManagerImpl<VM> {
    #[trace_method(LogLevel::Debug, skip(retval, visitor_manager))]
    pub fn new(visitor_manager: Arc<VM>, sdk_name: String, sdk_version: SemVer) -> Self {
        Self {
            visitor_manager,
            sdk_name,
            sdk_version,
        }
    }

    fn save_variations(&self, visitor_code: &str, vars_to_assign: Vec<AssignedVariation>, track: bool) {
        if vars_to_assign.len() == 0 {
            return;
        }
        self.visitor_manager.get_or_create(visitor_code).add_data(
            vars_to_assign.into_iter().map(|av| {
                if !track {
                    av.state_cell().change_state(TrackingState::Sent);
                }
                BaseData::AssignedVariation(av)
            }),
            true,
        );
    }
}

impl<VM: VisitorManager> EvaluationManager for EvaluationManagerImpl<VM> {
    #[trace_method(LogLevel::Debug, skip(self, datafile))]
    fn get_evaluated_experiment_info<'a, D, F, R, E, V>(
        &self,
        datafile: &'a D,
        visitor_code: &'a str,
        feature_flag: &'a F,
    ) -> (&'a str, Option<EvaluatedExperiment<'a, V, E>>, Vec<AssignedVariation>)
    where
        D: DataFile<FeatureFlag = F, Experiment = E>,
        F: FeatureFlag<Rule = R> + 'static,
        R: Rule<Experiment = E> + 'static,
        E: Experiment<VariationByExposition = V> + 'static,
        V: VariationByExposition,
    {
        let visitor = self.visitor_manager.get_or_create(visitor_code);
        let visitor_data = visitor.rdata();
        let eval_context = EvaluationContext::new(
            datafile,
            visitor_code,
            visitor_data,
            &self.sdk_name,
            self.sdk_version,
            feature_flag,
        );
        let (eval_exp, vars_to_assign) = eval_context.evaluate();
        let variation_key = calc_variation_key(eval_exp.as_ref(), feature_flag.default_variation_key());
        return (variation_key, eval_exp, vars_to_assign);
    }

    fn is_feature_active<D, F, R, E, V>(
        &self,
        datafile: &D,
        visitor_code: &str,
        feature_key: &str,
        track: bool,
    ) -> KRes<bool>
    where
        D: DataFile<FeatureFlag = F, Experiment = E>,
        F: FeatureFlag<Rule = R> + 'static,
        R: Rule<Experiment = E> + 'static,
        E: Experiment<VariationByExposition = V> + 'static,
        V: VariationByExposition,
    {
        let ff = match datafile.get_feature_flag(feature_key) {
            Ok(ff) => ff,
            Err(err) if err.code() == ErrorCode::FeatureEnvironmentDisabled => return Ok(false),
            Err(err) => return Err(err),
        };
        let (variation_key, _eval_exp, vars_to_assign) = self.get_evaluated_experiment_info(datafile, visitor_code, ff);
        self.save_variations(visitor_code, vars_to_assign, track);
        Ok(variation_key != OFF_VALUE)
    }

    fn get_variation<D, F, R, E, V>(
        &self,
        datafile: &D,
        visitor_code: &str,
        feature_key: &str,
        track: bool,
    ) -> KRes<crate::types::Variation>
    where
        D: DataFile<FeatureFlag = F, Experiment = E>,
        F: FeatureFlag<Rule = R> + 'static,
        R: Rule<Experiment = E> + 'static,
        E: Experiment<VariationByExposition = V> + 'static,
        V: VariationByExposition,
    {
        let ff = datafile.get_feature_flag(feature_key)?;
        let (variation_key, eval_exp, vars_to_assign) = self.get_evaluated_experiment_info(datafile, visitor_code, ff);
        match ff.get_variation(variation_key) {
            Some(v) => {
                self.save_variations(visitor_code, vars_to_assign, track);
                Ok(crate::types::Variation::from_evaluated_experiment_info(v, eval_exp.as_ref()))
            }
            None => {
                let error_str =
                    format!("Variation key '{}' not found for feature key '{}'", variation_key, ff.feature_key());
                Err(KameleoonError::new_internal(error_str))
            }
        }
    }

    fn get_variations<D, F, R, E, V>(
        &self,
        datafile: &D,
        visitor_code: &str,
        only_active: bool,
        track: bool,
    ) -> HashMap<String, crate::types::Variation>
    where
        D: DataFile<FeatureFlag = F, Experiment = E>,
        F: FeatureFlag<Rule = R> + 'static,
        R: Rule<Experiment = E> + 'static,
        E: Experiment<VariationByExposition = V> + 'static,
        V: VariationByExposition,
    {
        datafile
            .ordered_feature_flags()
            .iter()
            .filter_map(|ff| {
                if !ff.is_environment_enabled() {
                    return None;
                }
                let (variation_key, eval_exp, vars_to_assign) =
                    self.get_evaluated_experiment_info(datafile, visitor_code, ff);
                self.save_variations(visitor_code, vars_to_assign, track); //~ to think about changes to move it under `if` to avoid saving `off`` variations
                if only_active && (variation_key == OFF_VALUE) {
                    return None;
                }
                let var = ff.get_variation(variation_key)?;
                let ext_var = crate::types::Variation::from_evaluated_experiment_info(var, eval_exp.as_ref());
                Some((ff.feature_key().to_owned(), ext_var))
            })
            .collect()
    }

    #[trace_method(LogLevel::Debug, skip(self, datafile))]
    fn set_forced_variation(
        &self,
        datafile: &impl DataFile,
        visitor_code: &str,
        experiment_id: u32,
        variation_key: &str,
        force_targeting: bool,
    ) -> KRes<()> {
        let info = datafile.get_info_by_exp_id(experiment_id).ok_or_else(|| {
            KErr::new(ErrorCode::FeatureExperimentNotFound, format!("Experiment {experiment_id} not found"))
        })?;
        let ff = datafile.feature_flags_by_id().get(&info.feature_flag_id()).ok_or_else(|| {
            KErr::new(ErrorCode::FeatureNotFound, format!("Feature {} not found", info.feature_flag_id()))
        })?;
        let rule = ff
            .get_rule(info.rule_id())
            .ok_or_else(|| KErr::new(ErrorCode::FeatureRuleNotFound, format!("Rule {} not found", info.rule_id())))?;
        let var_by_exp = rule.experiment().get_variation_by_key(variation_key)?;
        let forced_variation = ForcedVariation::new(
            ff.feature_key().to_owned(),
            rule.id(),
            experiment_id,
            var_by_exp.variation_id(),
            force_targeting,
        );
        self.visitor_manager.add_data(
            visitor_code,
            iter::once(BaseData::ForcedVariation(forced_variation)),
            AddDataOptions {
                overwrite: true,
                track: true,
            },
        );
        Ok(())
    }

    #[trace_method(LogLevel::Debug, skip(self))]
    fn reset_forced_variation(&self, visitor_code: &str, experiment_id: u32) {
        if let Some(visitor) = self.visitor_manager.get(visitor_code) {
            visitor.wdata().forced_variations.remove(&experiment_id);
        }
    }
}

fn calc_variation_key<'a, V: VariationByExposition, E: Experiment<VariationByExposition = V>>(
    eval_exp: Option<&EvaluatedExperiment<'a, V, E>>,
    default_value: &'a str,
) -> &'a str {
    eval_exp.map_or(default_value, |eval_exp| eval_exp.var_by_exp.variation_key())
}

// MARK: - Tests

#[cfg(test)]
mod tests {
    use crate::{
        datafile::datafile_manager::MockDataFileManager,
        storage::visitor_manager::VisitorManagerImpl,
        types::JsonValue,
        utils::test_utils::{TEST_VISITOR_CODE, TestDataFile, load_datafile},
    };

    use super::*;
    use arc_swap::Guard;
    use rstest::rstest;
    use rstest_reuse::{apply, template};
    use tokio::runtime::Handle;

    fn make_datafile() -> TestDataFile {
        load_datafile("resources/datafiles/evaluation_datafile.json")
    }

    fn make_visitor_manager(datafile: Arc<TestDataFile>, runtime_handle: Handle) -> impl VisitorManager {
        let mut datafile_manager = MockDataFileManager::new();
        datafile_manager.expect_datafile().returning(move || Guard::from_inner(datafile.clone()));
        let datafile_manager = Arc::new(datafile_manager);
        VisitorManagerImpl::new(datafile_manager, runtime_handle)
    }

    fn make_evaluation_manager(visitor_manager: Arc<impl VisitorManager>) -> impl EvaluationManager {
        EvaluationManagerImpl::new(visitor_manager, "sdk".to_owned(), SemVer::new(1, 2, 3))
    }

    fn make_var(
        key: &str,
        id: Option<u32>,
        experiment_id: Option<u32>,
        variables: Vec<(&str, &str, JsonValue)>,
    ) -> crate::types::Variation {
        crate::types::Variation {
            key: key.to_string(),
            name: key.to_string(),
            id,
            experiment_id,
            variables: variables
                .into_iter()
                .map(|(k, t, v)| crate::types::Variable {
                    key: k.to_owned(),
                    kind: t.to_owned(),
                    value: v,
                })
                .collect(),
        }
    }

    fn get_var(i: u8) -> crate::types::Variation {
        match i {
            0 => make_var(
                "variation_1",
                Some(859380),
                Some(202387),
                vec![
                    ("variable_string", "STRING", JsonValue::String("variation".to_owned())),
                    ("variable_number", "NUMBER", JsonValue::Number(0.0)),
                    ("variable_bool", "BOOLEAN", JsonValue::Boolean(true)),
                    ("variable_json", "JSON", JsonValue::Json("{}".to_owned())),
                ],
            ),
            1 => make_var(
                "off",
                None,
                None,
                vec![
                    ("boolTrueKey", "BOOLEAN", JsonValue::Boolean(true)),
                    ("boolFalseKey", "BOOLEAN", JsonValue::Boolean(false)),
                    ("stringKey", "STRING", JsonValue::String("TestString".to_owned())),
                    ("numberKey", "NUMBER", JsonValue::Number(100.0)),
                    ("numberNullKey", "NUMBER", JsonValue::Number(0.0)),
                    ("numberKey-2", "NUMBER", JsonValue::Number(10.1)),
                    ("css_key", "CSS", JsonValue::Css(".button {\n    color: red;\n}".to_owned())),
                    ("js_key", "JS", JsonValue::Js("console.log(\"Hello, world!\");".to_owned())),
                    ("jsonKey", "JSON", JsonValue::Json("{\"test\":\"json\"}".to_owned())),
                    (
                        "jsonArrayKey",
                        "JSON",
                        JsonValue::Json("[ {\"key\" : \"value-1\"}, { \"key\": \"value-2\" } ]".to_owned()),
                    ),
                ],
            ),
            2 => make_var("off", Some(0), Some(151238), vec![]),
            _ => panic!(),
        }
    }

    fn get_assigned_variations(vm: &impl VisitorManager, vc: &str) -> Vec<AssignedVariation> {
        vm.peek(vc)
            .map_or_else(Vec::new, |v| Vec::from_iter(v.rdata().assigned_variations.r().iter().map(|av| av.clone())))
    }

    fn assert_assigned_variation(var_id: u32, exp_id: u32, track: bool, actual_avs: Vec<AssignedVariation>) {
        assert_eq!(1, actual_avs.len());
        assert_eq!(var_id, actual_avs[0].variation_id);
        assert_eq!(exp_id, actual_avs[0].experiment_id);
        assert_eq!(if track { TrackingState::Unsent } else { TrackingState::Sent }, actual_avs[0].state_cell().state());
    }

    #[template]
    #[rstest]
    #[case("ff_new_rules", get_var(0))]
    #[case("test_feature_variables", get_var(1))]
    fn get_variation_test_data(#[case] ff_key: &str, #[case] expected_variation: crate::types::Variation) {}

    #[apply(get_variation_test_data)]
    fn test_get_variation_info(#[case] ff_key: &str, #[case] expected_variation: crate::types::Variation) {
        // arrange
        let runtime = tokio::runtime::Runtime::new().unwrap();
        let vc = format!("{TEST_VISITOR_CODE}a");
        let datafile = Arc::new(make_datafile());
        let visitor_manager = Arc::new(make_visitor_manager(datafile.clone(), runtime.handle().clone()));
        let eval_manager = make_evaluation_manager(visitor_manager.clone());
        let ff = datafile.get_feature_flag(ff_key).unwrap();
        // act
        let (variation_key, eval_exp, vars_to_assign) =
            eval_manager.get_evaluated_experiment_info(datafile.as_ref(), &vc, ff);
        // assert
        assert_eq!(expected_variation.key, variation_key);
        match (expected_variation.id, expected_variation.experiment_id) {
            (Some(var_id), Some(exp_id)) => {
                let eval_exp = eval_exp.unwrap();
                assert_eq!(var_id, eval_exp.var_by_exp.variation_id());
                assert_eq!(exp_id, eval_exp.experiment.id());
                assert_eq!(1, vars_to_assign.len());
                assert_eq!(var_id, vars_to_assign[0].variation_id);
                assert_eq!(exp_id, vars_to_assign[0].experiment_id);
            }
            _ => {
                assert!(eval_exp.is_none());
                assert!(vars_to_assign.is_empty());
            }
        }
    }

    #[apply(get_variation_test_data)]
    fn test_is_feature_active(
        #[case] ff_key: &str,
        #[case] expected_variation: crate::types::Variation,
        #[values(false, true)] track: bool,
    ) {
        // arrange
        let runtime = tokio::runtime::Runtime::new().unwrap();
        let vc = format!("{TEST_VISITOR_CODE}a");
        let datafile = Arc::new(make_datafile());
        let visitor_manager = Arc::new(make_visitor_manager(datafile.clone(), runtime.handle().clone()));
        let eval_manager = make_evaluation_manager(visitor_manager.clone());
        // act
        let is_active = eval_manager.is_feature_active(datafile.as_ref(), &vc, ff_key, track).unwrap();
        // assert
        let assigned_variations = get_assigned_variations(visitor_manager.as_ref(), &vc);
        match (expected_variation.id, expected_variation.experiment_id) {
            (Some(var_id), Some(exp_id)) => {
                assert_eq!(expected_variation.is_active(), is_active);
                assert_assigned_variation(var_id, exp_id, track, assigned_variations);
            }
            _ => {
                assert!(!is_active);
                assert!(assigned_variations.is_empty());
            }
        }
    }

    #[apply(get_variation_test_data)]
    fn test_get_variation(
        #[case] ff_key: &str,
        #[case] expected_variation: crate::types::Variation,
        #[values(false, true)] track: bool,
    ) {
        // arrange
        let runtime = tokio::runtime::Runtime::new().unwrap();
        let vc = format!("{TEST_VISITOR_CODE}a");
        let datafile = Arc::new(make_datafile());
        let visitor_manager = Arc::new(make_visitor_manager(datafile.clone(), runtime.handle().clone()));
        let eval_manager = make_evaluation_manager(visitor_manager.clone());
        // act
        let variation = eval_manager.get_variation(datafile.as_ref(), &vc, ff_key, track).unwrap();
        // assert
        let assigned_variations = get_assigned_variations(visitor_manager.as_ref(), &vc);
        assert_eq!(expected_variation, variation);
        match (expected_variation.id, expected_variation.experiment_id) {
            (Some(var_id), Some(exp_id)) => assert_assigned_variation(var_id, exp_id, track, assigned_variations),
            _ => assert!(assigned_variations.is_empty()),
        }
    }

    #[rstest]
    #[case(
        false,
        vec![
            ("ff1", make_var("off", None, None, vec![])),
            ("bucketing_custom_data", make_var("on", Some(1123498), Some(312489), vec![])),
            ("ff_new_rules", get_var(0)),
            ("test_feature_variables", get_var(1)),
            ("ff2", get_var(2)),
        ],
        vec![]
    )]
    #[case(
        true,
        vec![
            ("bucketing_custom_data", make_var("on", Some(1123498), Some(312489), vec![])),
            ("ff_new_rules", get_var(0)),
        ],
        vec![("ff2", get_var(2))]
    )]
    fn test_get_variations(
        #[values(false, true)] track: bool,
        #[case] only_active: bool,
        #[case] expected_returned_variations: Vec<(&str, crate::types::Variation)>,
        #[case] rest_assigned_variations: Vec<(&str, crate::types::Variation)>,
    ) {
        // arrange
        let runtime = tokio::runtime::Runtime::new().unwrap();
        let vc = format!("{TEST_VISITOR_CODE}a");
        let datafile = Arc::new(make_datafile());
        let visitor_manager = Arc::new(make_visitor_manager(datafile.clone(), runtime.handle().clone()));
        let eval_manager = make_evaluation_manager(visitor_manager.clone());
        // act
        let mut variations = eval_manager.get_variations(datafile.as_ref(), &vc, only_active, track);
        // assert
        assert_eq!(expected_returned_variations.len(), variations.len());
        // get assigned variations
        let assigned_variations = get_assigned_variations(visitor_manager.as_ref(), &vc);
        let mut assigned_by_exp_id: HashMap<u32, AssignedVariation> =
            assigned_variations.into_iter().map(|av| (av.experiment_id, av)).collect();
        // helper closure to assert variations
        let assert_assigned = |expected_var: &crate::types::Variation,
                               assigned_by_exp_id: &mut HashMap<u32, AssignedVariation>| {
            if let (Some(var_id), Some(exp_id)) = (expected_var.id, expected_var.experiment_id) {
                let av = assigned_by_exp_id
                    .remove(&exp_id)
                    .unwrap_or_else(|| panic!("missing assigned variation for exp_id={exp_id}"));
                assert_eq!(exp_id, av.experiment_id);
                assert_eq!(var_id, av.variation_id);
                assert_eq!(if track { TrackingState::Unsent } else { TrackingState::Sent }, av.state_cell().state());
            }
        };
        // assert returned variations
        for (ff_key, expected_var) in expected_returned_variations.iter() {
            let actual_var =
                variations.remove(*ff_key).unwrap_or_else(|| panic!("missing variation for ff_key={ff_key}"));
            assert_eq!(*expected_var, actual_var);
            assert_assigned(expected_var, &mut assigned_by_exp_id);
        }
        assert!(variations.is_empty(), "unexpected variations returned: {variations:?}");
        // assert rest assigned variations
        for (ff_key, expected_var) in rest_assigned_variations.iter() {
            assert!(!variations.contains_key(*ff_key), "ff_key={ff_key} should not be returned in get_variations");
            assert_assigned(expected_var, &mut assigned_by_exp_id);
        }
        assert!(assigned_by_exp_id.is_empty(), "unexpected assigned variations: {assigned_by_exp_id:?}");
    }

    #[rstest]
    fn test_single_variation_errors() {
        const VC: &str = "vc1";
        let runtime = tokio::runtime::Runtime::new().unwrap();
        let datafile = Arc::new(make_datafile());
        let visitor_manager = Arc::new(make_visitor_manager(datafile.clone(), runtime.handle().clone()));
        let eval_manager = make_evaluation_manager(visitor_manager.clone());
        // error: feature not found
        let result = eval_manager.is_feature_active(datafile.as_ref(), VC, "nonexistent_ff", false);
        assert_eq!(ErrorCode::FeatureNotFound, result.unwrap_err().code());
        let result = eval_manager.get_variation(datafile.as_ref(), VC, "nonexistent_ff", false);
        assert_eq!(ErrorCode::FeatureNotFound, result.unwrap_err().code());
        // error: feature environment disabled
        let result = eval_manager.is_feature_active(datafile.as_ref(), VC, "env_disabled_ff", false);
        assert!(!result.unwrap());
        let result = eval_manager.get_variation(datafile.as_ref(), VC, "env_disabled_ff", false);
        assert_eq!(ErrorCode::FeatureEnvironmentDisabled, result.unwrap_err().code());
        // assert no variation assigned
        let assigned_variations = get_assigned_variations(visitor_manager.as_ref(), VC);
        assert!(assigned_variations.is_empty());
    }

    #[rstest]
    #[case(151236, "on", true, Ok(ForcedVariation::new("ff1".to_owned(), 751, 151236, 865016, true)))]
    #[case(151237, "on", false, Ok(ForcedVariation::new("ff1".to_owned(), 754, 151237, 866247, false)))]
    #[case(999999, "on", true, Err(ErrorCode::FeatureExperimentNotFound))]
    #[case(151236, "var1", true, Err(ErrorCode::FeatureVariationNotFound))]
    fn test_set_forced_variation(
        #[case] exp_id: u32,
        #[case] var_key: &str,
        #[case] force_targeting: bool,
        #[case] expected: Result<ForcedVariation, ErrorCode>,
    ) {
        // arrange
        const VC: &str = "vc123";
        let runtime = tokio::runtime::Runtime::new().unwrap();
        let datafile = Arc::new(make_datafile());
        let visitor_manager = Arc::new(make_visitor_manager(datafile.clone(), runtime.handle().clone()));
        let eval_manager = make_evaluation_manager(visitor_manager.clone());
        // act
        let result = eval_manager.set_forced_variation(datafile.as_ref(), VC, exp_id, var_key, force_targeting);
        // assert
        match expected {
            Ok(expected_fv) => {
                assert!(result.is_ok());
                let visitor = visitor_manager.peek(VC).unwrap();
                let visitor_data = visitor.rdata();
                assert_eq!(1, visitor_data.forced_variations.r().len());
                assert_eq!(&expected_fv, visitor_data.forced_variations.r().get(&exp_id).unwrap());
            }
            Err(err) => {
                assert_eq!(err, result.unwrap_err().code());
                assert!(visitor_manager.peek(VC).is_none());
            }
        }
    }

    #[rstest]
    fn test_reset_forced_variation() {
        const VC: &str = "vc234";
        const EXP_ID: u32 = 151237;
        let runtime = tokio::runtime::Runtime::new().unwrap();
        let datafile = Arc::new(make_datafile());
        let visitor_manager = Arc::new(make_visitor_manager(datafile.clone(), runtime.handle().clone()));
        let eval_manager = make_evaluation_manager(visitor_manager.clone());
        eval_manager.set_forced_variation(datafile.as_ref(), VC, EXP_ID, "on", true).unwrap();
        // non-existent forced variation
        eval_manager.reset_forced_variation(VC, EXP_ID + 1);
        assert_eq!(1, visitor_manager.peek(VC).unwrap().rdata().forced_variations.r().len());
        // real forced variation
        eval_manager.reset_forced_variation(VC, EXP_ID);
        assert_eq!(0, visitor_manager.peek(VC).unwrap().rdata().forced_variations.r().len());
    }
}
