use crate::{
    datafile::{datafile::DataFile, experiment::Experiment, feature_flag::FeatureFlag, rule::Rule},
    targeting::{TargetingData, conditions::TargetingCondition, models::TargetFeatureFlagCPModel},
};

pub(crate) struct TargetFeatureFlagCondition {
    feature_flag_id: u32,
    variation_key: Option<String>,
    rule_id: Option<u32>,
}

impl TargetFeatureFlagCondition {
    pub fn from_json(model: TargetFeatureFlagCPModel) -> Option<Self> {
        Some(Self {
            feature_flag_id: model.feature_flag_id,
            variation_key: model.variation_key,
            rule_id: model.rule_id,
        })
    }

    fn get_rules<'a>(&self, datafile: &'a impl DataFile) -> Option<&'a [impl Rule]> {
        datafile.feature_flags_by_id().get(&self.feature_flag_id).map(|ff| ff.rules())
    }

    fn check_rule(&self, data: &TargetingData<impl DataFile>, rule: &impl Rule) -> bool {
        if let Some(rule_id) = self.rule_id {
            if rule_id != rule.id() {
                return false;
            }
        }
        let variation = match data.visitor_data.assigned_variations.r().get(&rule.experiment().id()) {
            Some(variation) => variation,
            None => return false,
        };
        if let Some(cond_var_key) = self.variation_key.as_ref() {
            let var_by_id = data.datafile.vars_by_id().get(&variation.variation_id);
            var_by_id.map_or(false, |var_by_id| var_by_id.variation_key() == cond_var_key)
        } else {
            true
        }
    }
}

impl TargetingCondition for TargetFeatureFlagCondition {
    fn check(&self, data: &TargetingData<impl DataFile>) -> Option<bool> {
        if data.visitor_data.assigned_variations.r().len() == 0 {
            return Some(false);
        }
        Some(self.get_rules(data.datafile)?.iter().any(|rule| self.check_rule(data, rule)))
    }
}

#[cfg(test)]
mod tests {
    use crate::{
        data::assigned_variation::AssignedVariation,
        datafile::{rule::RuleType, segment::Segment},
        storage::visitor::Visitor,
        targeting::TargetingDataBuilder,
        utils::test_utils::load_datafile,
    };

    use super::*;
    use rstest::rstest;

    #[rstest]
    #[case(237130, 219003, 897559, true)]
    #[case(237130, 219003, 897560, false)]
    #[case(237130, 219004, 897559, false)]
    #[case(237130, 219005, 897565, false)]
    #[case(237131, 219003, 897559, true)]
    #[case(237131, 219003, 897560, true)]
    #[case(237131, 219004, 897560, false)]
    #[case(237131, 219005, 897565, false)]
    #[case(237132, 219003, 897559, true)]
    #[case(237132, 219004, 897561, true)]
    #[case(237132, 219003, 897560, false)]
    #[case(237132, 219005, 897565, false)]
    #[case(237133, 219003, 897559, true)]
    #[case(237133, 219003, 897560, true)]
    #[case(237133, 219004, 897561, true)]
    #[case(237133, 219005, 897565, false)]
    fn test_check(#[case] segment_id: u32, #[case] exp_id: u32, #[case] var_id: u32, #[case] expected: bool) {
        let datafile = load_datafile("resources/conditions/mock_segment_target.json");
        let visitor = Visitor::new();
        visitor.wdata().assigned_variations.put(AssignedVariation::new(exp_id, var_id, RuleType::Unknown), true);
        let tar_tree = datafile.segments().get(&segment_id).unwrap().tree().unwrap();
        let actual = tar_tree.check(&TargetingDataBuilder::new().build(&datafile, &visitor.rdata()));
        assert_eq!(expected, actual);
    }
}
