use parking_lot::RwLockReadGuard;

use crate::{
    datafile::datafile::DataFile,
    storage::visitor_data::VisitorData,
    targeting::{TargetingData, conditions::TargetingCondition, models::ExclusiveExperimentCPModel},
};

enum CampaignType {
    Experiment,
    Personalization,
    Any,
}

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

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 });
    }

    fn check_experiment(&self, exp_id: u32, vd: &RwLockReadGuard<VisitorData>) -> bool {
        return (vd.assigned_variations.r().len() == 0)
            || ((vd.assigned_variations.r().len() == 1) && vd.assigned_variations.r().get(&exp_id).is_some());
    }

    fn check_personalization(&self, vd: &RwLockReadGuard<VisitorData>) -> bool {
        return vd.personalizations.r().len() == 0;
    }
}

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

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

    use rstest::rstest;

    fn new_var(exp_id: u32, var_id: u32) -> AssignedVariation {
        return AssignedVariation::new(exp_id, var_id, RuleType::Unknown);
    }

    fn new_perso(id: u32) -> Personalization {
        return Personalization::new(id);
    }

    #[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)], vec![])), true)]
    #[case("exp", Some(1), Some((vec![new_var(1, 10), new_var(2, 20)], vec![new_perso(1)])), false)]
    #[case("exp", Some(1), Some((vec![new_var(2, 20)], vec![new_perso(1)])), false)]
    #[case("perso", Some(1), None, true)]
    #[case("perso", Some(1), Some((vec![], vec![])), true)]
    #[case("perso", Some(1), Some((vec![new_var(2, 20)], vec![])), true)]
    #[case("perso", Some(1), Some((vec![new_var(2, 20)], vec![new_perso(1)])), 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)], vec![new_perso(1)])), false)]
    #[case("any", Some(1), Some((vec![new_var(2, 20)], vec![])), false)]
    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();
        if let Some((avs, ps)) = visitor_data {
            visitor.add_data(avs.into_iter().map(|av| BaseData::AssignedVariation(av)), true);
            visitor.add_data(ps.into_iter().map(|p| BaseData::Personalization(p)), 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,
        );
    }
}
