use crate::{
    datafile::datafile::DataFile,
    targeting::{
        conditions::{
            visitor_scope_condition::{VisitScope, VisitorScopeCondition},
            TargetingCondition,
        },
        models::TargetExperimentCPModel,
        TargetingData,
    },
};

enum VariationMatchType {
    Any,
    Exact { variation_id: u32 },
}

pub(crate) struct TargetExperimentCondition {
    experiment_id: u32,
    variation_match_type: VariationMatchType,
    visitor_scope_condition: VisitorScopeCondition,
}

impl TargetExperimentCondition {
    pub fn from_json(model: TargetExperimentCPModel) -> Option<Self> {
        let variation_match_type = match model.variation_match_type.as_str() {
            "ANY" => VariationMatchType::Any,
            "EXACT" => VariationMatchType::Exact {
                variation_id: model.variation_id?,
            },
            _ => return None,
        };
        return Some(Self {
            experiment_id: model.experiment_id,
            variation_match_type,
            visitor_scope_condition: VisitorScopeCondition::new(
                model.visit_params.visit_scope,
                model.visit_params.visit_count,
                VisitScope::CurrentVisit,
            ),
        });
    }
}

impl TargetingCondition for TargetExperimentCondition {
    fn check(&self, data: &TargetingData<impl DataFile>) -> Option<bool> {
        let variation = data.visitor_data.assigned_variations.r().get(&self.experiment_id)?;
        if variation.assignment_time < self.visitor_scope_condition.assignment_threshold(data) {
            return Some(false);
        }
        Some(match self.variation_match_type {
            VariationMatchType::Any => true,
            VariationMatchType::Exact { variation_id } => variation.variation_id == variation_id,
        })
    }
}

#[cfg(test)]
mod tests {
    use crate::{
        data::{assigned_variation::AssignedVariation, BaseData},
        datafile::datafile_test_utils::MockDataFile,
        storage::visitor::Visitor,
        targeting::TargetingDataBuilder,
        utils::test_utils::{assert_targeting, new_var, new_visitor_visits},
    };

    use rstest::rstest;

    #[rstest]
    #[case(None, false)]
    #[case(Some(vec![]), false)]
    #[case(Some(vec![new_var(1, 10, 1000), new_var(2, 10, 700)]), true)]
    #[case(Some(vec![new_var(1, 20, 1000), new_var(2, 10, 700)]), true)]
    #[case(Some(vec![new_var(1, 20, 899), new_var(2, 10, 700)]), false)]
    #[case(Some(vec![new_var(1, 10, 899), new_var(2, 20, 101)]), true)]
    #[case(Some(vec![new_var(1, 10, 899), new_var(2, 10, 101)]), false)]
    #[case(Some(vec![new_var(1, 10, 899), new_var(2, 20, 99)]), false)]
    fn test_check(#[case] assigned_variations: Option<Vec<AssignedVariation>>, #[case] expected: bool) {
        let datafile = MockDataFile::default();
        let visitor = Visitor::new();
        visitor.add_data(std::iter::once(BaseData::VisitorVisits(new_visitor_visits())), true);
        if let Some(assigned_variations) = assigned_variations {
            visitor.add_data(assigned_variations.into_iter().map(|av| BaseData::AssignedVariation(av)), true);
        }
        assert_targeting(
            "resources/conditions/mock_segment_target_experiment.json",
            &TargetingDataBuilder::new().build(&datafile, &visitor.rdata()),
            expected,
        );
    }
}
