use crate::{
    datafile::datafile::DataFile,
    targeting::{conditions::TargetingCondition, models::NewVisitorsCPModel, TargetingData},
};

enum VisitorType {
    New,
    Return,
}

impl VisitorType {
    fn from_str(value: &str) -> Option<Self> {
        match value {
            "NEW" => Some(VisitorType::New),
            "RETURNING" => Some(VisitorType::Return),
            _ => return None,
        }
    }
}

pub(crate) struct VisitorNewReturnCondition {
    visitor_type: VisitorType,
}

impl VisitorNewReturnCondition {
    pub fn from_json(model: NewVisitorsCPModel) -> Option<Self> {
        let visitor_type = VisitorType::from_str(model.visitor_type.as_str())?;
        return Some(Self { visitor_type });
    }
}

impl TargetingCondition for VisitorNewReturnCondition {
    fn check(&self, data: &TargetingData<impl DataFile>) -> Option<bool> {
        let visit_count = data.visitor_data.visitor_visits.r().get().map_or(0, |vv| vv.prev_visits().len() as i32);
        Some(match self.visitor_type {
            VisitorType::New => visit_count == 0,
            VisitorType::Return => visit_count > 0,
        })
    }
}

#[cfg(test)]
mod tests {
    use std::time::{Duration, SystemTime};

    use crate::{
        data::{
            visitor_visits::{Visit, VisitorVisits},
            BaseData,
        },
        datafile::datafile_test_utils::MockDataFile,
        storage::visitor::Visitor,
        targeting::TargetingDataBuilder,
        utils::{test_utils::assert_targeting, time_utils::millis_to_time},
    };

    use rstest::rstest;

    fn new_vv(prev_visits: Vec<Visit>) -> VisitorVisits {
        return VisitorVisits::new(prev_visits.len() as u32, prev_visits, SystemTime::UNIX_EPOCH, Duration::ZERO);
    }

    #[rstest]
    #[case(1, None, true)]
    #[case(2, None, false)]
    #[case(1, Some(new_vv(vec![])), true)]
    #[case(2, Some(new_vv(vec![])), false)]
    #[case(1, Some(new_vv(vec![Visit::new(millis_to_time(1), millis_to_time(1))])), false)]
    #[case(2, Some(new_vv(vec![Visit::new(millis_to_time(1), millis_to_time(1))])), true)]
    fn test_check(#[case] file_index: i32, #[case] visitor_visits: Option<VisitorVisits>, #[case] expected: bool) {
        let datafile = MockDataFile::default();
        let visitor = Visitor::new();
        if let Some(visitor_visits) = visitor_visits {
            visitor.add_data([BaseData::VisitorVisits(visitor_visits)].into_iter(), true);
        }
        assert_targeting(
            &format!("resources/conditions/mock_segment_visit_new_return{file_index}.json"),
            &TargetingDataBuilder::new().build(&datafile, &visitor.rdata()),
            expected,
        );
    }
}
