use std::time::SystemTime;

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

enum CampaignType {
    Experiment,
    Personalization,
    Any,
}

pub(crate) struct ExclusiveExperimentCondition {
    campaign_type: CampaignType,
    visitor_scope_condition: VisitorScopeCondition,
}

impl ExclusiveExperimentCondition {
    pub fn from_json(model: ExclusiveExperimentCPModel) -> Option<Self> {
        let campaign_type = match model.campaign_type.as_str() {
            "EXPERIMENT" => CampaignType::Experiment,
            "PERSONALIZATION" => CampaignType::Personalization,
            "ANY" => CampaignType::Any,
            _ => return None,
        };
        return Some(Self {
            campaign_type,
            visitor_scope_condition: VisitorScopeCondition::new(
                model.visit_params.visit_scope,
                model.visit_params.visit_count,
                VisitScope::Visitor,
            ),
        });
    }

    fn check_experiment(&self, exp_id: u32, data: &TargetingData<impl DataFile>, threshold: SystemTime) -> bool {
        return data
            .visitor_data
            .assigned_variations
            .r()
            .iter()
            .all(|av| av.experiment_id == exp_id || av.assignment_time < threshold);
    }

    fn check_personalization(&self, data: &TargetingData<impl DataFile>, threshold: SystemTime) -> bool {
        return data.visitor_data.personalizations.r().iter().all(|p| p.assignment_time < threshold);
    }
}

impl TargetingCondition for ExclusiveExperimentCondition {
    fn check(&self, data: &TargetingData<impl DataFile>) -> Option<bool> {
        let exp_id = data.campaign_id?;
        let threshold = self.visitor_scope_condition.assignment_threshold(data);
        let targeting = match self.campaign_type {
            CampaignType::Experiment => self.check_experiment(exp_id, data, threshold),
            CampaignType::Personalization => self.check_personalization(data, threshold),
            CampaignType::Any => {
                self.check_personalization(data, threshold) && self.check_experiment(exp_id, data, threshold)
            }
        };
        return Some(targeting);
    }
}

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

    use rstest::rstest;

    #[rstest]
    #[case("exp", None, None, false)]
    #[case("exp", Some(1), None, true)]
    #[case("exp", Some(1), Some((vec![], vec![])), true)]
    #[case("exp", Some(1), Some((vec![new_var(1, 10, 1000)], vec![new_perso(1, 10, 500)])), true)]
    #[case("exp", Some(1), Some((vec![new_var(2, 20, 1000)], vec![])), false)]
    #[case("exp", Some(1), Some((vec![new_var(2, 20, 500)], vec![])), true)]
    #[case("perso", Some(1), None, true)]
    #[case("perso", Some(1), Some((vec![], vec![])), true)]
    #[case("perso", Some(1), Some((vec![], vec![new_perso(1, 10, 50)])), true)]
    #[case("perso", Some(1), Some((vec![], vec![new_perso(1, 10, 100)])), false)]
    #[case("any", Some(1), None, true)]
    #[case("any", Some(1), Some((vec![], vec![])), true)]
    #[case("any", Some(1), Some((vec![new_var(1, 10, 1000)], vec![new_perso(1, 10, 899)])), true)]
    #[case("any", Some(1), Some((vec![new_var(1, 10, 1000)], vec![new_perso(1, 10, 1000)])), false)]
    #[case("any", Some(1), Some((vec![new_var(2, 20, 1000)], vec![])), false)]
    #[case("any", Some(1), Some((vec![new_var(2, 20, 899)], vec![])), true)]
    fn test_check(
        #[case] file_suffix: &str,
        #[case] campaign_id: Option<u32>,
        #[case] visitor_data: Option<(Vec<AssignedVariation>, Vec<Personalization>)>,
        #[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((avs, ps)) = visitor_data {
            visitor.add_data(avs.into_iter().map(BaseData::AssignedVariation), true);
            visitor.add_data(ps.into_iter().map(BaseData::Personalization), true);
        }
        assert_targeting(
            &format!("resources/conditions/mock_segment_exclusive_experiment_{file_suffix}.json"),
            &TargetingDataBuilder::new().campaign_id_opt(campaign_id).build(&datafile, &visitor.rdata()),
            expected,
        );
    }
}
