use crate::{
    datafile::datafile::DataFile,
    targeting::{conditions::TargetingCondition, models::GeolocationCPModel, TargetingData},
    utils::eq_ignore_case,
};

pub(crate) struct GeolocationCondition {
    country: String,
    region: Option<String>,
    city: Option<String>,
}

impl GeolocationCondition {
    pub fn from_json(model: GeolocationCPModel) -> Option<Self> {
        return Some(Self {
            country: model.country.to_lowercase(),
            region: model.region.map(|region| region.to_lowercase()),
            city: model.city.map(|city| city.to_lowercase()),
        });
    }
}

impl TargetingCondition for GeolocationCondition {
    fn check(&self, data: &TargetingData<impl DataFile>) -> Option<bool> {
        let geo = data.visitor_data.geolocation.r().get()?;
        let opt_matches = |expected: &Option<String>, actual: Option<&str>| {
            expected.as_ref().map_or(true, |e| actual.map_or(false, |a| eq_ignore_case(a, e)))
        };
        return Some(
            eq_ignore_case(&geo.country, &self.country)
                && opt_matches(&self.region, geo.region.as_deref())
                && opt_matches(&self.city, geo.city.as_deref()),
        );
    }
}

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

    use rstest::rstest;

    fn new_geo(country: &str, region: Option<&str>, city: Option<&str>) -> Geolocation {
        let mut builder = GeolocationBuilder::default();
        builder.country(country);
        builder.region(region.map(|s| s.to_owned()));
        builder.city(city.map(|s| s.to_owned()));
        return builder.build().unwrap();
    }

    #[rstest]
    #[case(None, false)]
    // Country, region, city
    #[case(Some(new_geo("francE", Some("Ile-de-francE"), Some("pariS"))), true)]
    #[case(Some(new_geo("France", Some("Ile-de-France"), Some("Paris"))), true)]
    #[case(Some(new_geo("Vietnam", Some("Ile-de-France"), Some("Paris"))), false)]
    #[case(Some(new_geo("France", Some("Ile-de-France"), Some("Marseille"))), false)]
    #[case(Some(new_geo("France", Some("Bouches-du-Rhone"), Some("Paris"))), false)]
    #[case(Some(new_geo("France", Some("Ile-de-France"), None)), false)]
    #[case(Some(new_geo("France", None, None)), false)]
    // Country, region
    #[case(Some(new_geo("Belgium", Some("Brussels-Capital Region"), Some("Brussels"))), true)]
    #[case(Some(new_geo("Vietnam", Some("Brussels-Capital Region"), Some("Brussels"))), false)]
    #[case(Some(new_geo("Belgium", Some("Brussels-Capital Region"), Some("Bruges"))), true)]
    #[case(Some(new_geo("Belgium", Some("Brussels-Capital Region"), None)), true)]
    #[case(Some(new_geo("Belgium", Some("Flemish Region"), None)), false)]
    // Country
    #[case(Some(new_geo("Germany", Some("Bavaria"), Some("Munich"))), true)]
    #[case(Some(new_geo("Vietnam", Some("Bavaria"), Some("Munich"))), false)]
    #[case(Some(new_geo("Germany", Some("Bavaria"), Some("Augsburg"))), true)]
    #[case(Some(new_geo("Germany", Some("Brandenburg"), None)), true)]
    #[case(Some(new_geo("Germany", None, None)), true)]
    fn test_check(#[case] geolocation: Option<Geolocation>, #[case] expected: bool) {
        let datafile = MockDataFile::default();
        let visitor = Visitor::new();
        if let Some(geolocation) = geolocation {
            visitor.add_data([BaseData::Geolocation(geolocation)].into_iter(), true);
        }
        assert_targeting(
            "resources/conditions/mock_segment_geolocation.json",
            &TargetingDataBuilder::new().build(&datafile, &visitor.rdata()),
            expected,
        );
    }
}
