use crate::{
    data::BrowserKind,
    datafile::datafile::DataFile,
    targeting::{conditions::TargetingCondition, models::BrowserCPModel, NumberTargetingOperator, TargetingData},
    utils::version::parse_float_version,
};

pub(crate) struct BrowserCondition {
    browser_kind: BrowserKind,
    version: Option<(f32, NumberTargetingOperator)>,
}

impl BrowserCondition {
    pub fn from_json(model: BrowserCPModel) -> Option<Self> {
        let browser_kind = BrowserKind::from(model.browser.as_str());
        let version = model.version.map_or(Some(None), |version| {
            parse_float_version(&version).map(|version| {
                let version_match_type = NumberTargetingOperator::from_str(&model.version_match_type?)?;
                Some((version, version_match_type))
            })
        })?;
        return Some(Self { browser_kind, version });
    }

    fn check_version(&self, browser_version: Option<f32>) -> bool {
        self.version.map_or(true, |(version, version_match_type)| {
            browser_version.map_or(false, |browser_version| match version_match_type {
                NumberTargetingOperator::Equal => browser_version == version,
                NumberTargetingOperator::Greater => browser_version > version,
                NumberTargetingOperator::Lower => browser_version < version,
            })
        })
    }
}

impl TargetingCondition for BrowserCondition {
    fn check(&self, data: &TargetingData<impl DataFile>) -> Option<bool> {
        let browser = data.visitor_data.browser.r().get()?;
        return Some((browser.kind == self.browser_kind) && self.check_version(browser.version));
    }
}

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

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

    #[rstest]
    #[case(1, None, false)]
    #[case(1, Some(Browser::new(BrowserKind::Chrome, 90.0)), true)]
    #[case(1, Some(Browser::new(BrowserKind::Chrome, 90.1)), false)]
    #[case(1, Some(Browser::new(BrowserKind::Chrome, 89.9)), false)]
    #[case(1, Some(Browser::new(BrowserKind::Firefox, 1.1)), true)]
    #[case(1, Some(Browser::new(BrowserKind::Firefox, 0.9)), false)]
    #[case(1, Some(Browser::new(BrowserKind::Firefox, 1.0)), false)]
    #[case(1, Some(Browser::new(BrowserKind::InternetExplorer, 20.62)), true)]
    #[case(1, Some(Browser::new(BrowserKind::InternetExplorer, 21.1)), false)]
    #[case(1, Some(Browser::new(BrowserKind::InternetExplorer, 21.0)), false)]
    #[case(1, Some(Browser::new(BrowserKind::Opera, None)), true)]
    #[case(1, Some(Browser::new(BrowserKind::Safari, None)), false)]
    #[case(2, Some(Browser::new(BrowserKind::Chrome, 90.0)), false)]
    fn test_check(#[case] file_index: i32, #[case] browser: Option<Browser>, #[case] expected: bool) {
        let datafile = MockDataFile::default();
        let visitor = Visitor::new();
        if let Some(browser) = browser {
            visitor.add_data([BaseData::Browser(browser)].into_iter(), true);
        }
        assert_targeting(
            &format!("resources/conditions/mock_segment_browser{file_index}.json"),
            &TargetingDataBuilder::new().build(&datafile, &visitor.rdata()),
            expected,
        );
    }
}
