use std::time::SystemTime;

use crate::{data::visitor_visits::VisitorVisits, datafile::datafile::DataFile, targeting::TargetingData};

const MIN_VISITOR_VISIT_COUNT: u32 = 2;
const MAX_VISITOR_VISIT_COUNT: u32 = 25;

#[derive(Clone, Copy)]
pub(crate) enum VisitScope {
    CurrentVisit,
    Visitor,
}

pub(crate) struct VisitorScopeCondition {
    visit_scope: VisitScope,
    visit_count: u32,
}

impl VisitorScopeCondition {
    pub fn new(visit_scope: Option<String>, visit_count: Option<u32>, default_visit_scope: VisitScope) -> Self {
        Self {
            visit_scope: Self::parse_visit_scope(visit_scope.as_deref(), default_visit_scope),
            visit_count: visit_count.unwrap_or(MAX_VISITOR_VISIT_COUNT),
        }
    }

    pub fn assignment_threshold<D: DataFile>(&self, data: &TargetingData<D>) -> SystemTime {
        self.assignment_threshold_from_visits(data.visitor_data.visitor_visits.r().get().map(|vv| vv.as_ref()))
    }

    fn assignment_threshold_from_visits(&self, visitor_visits: Option<&VisitorVisits>) -> SystemTime {
        let Some(visitor_visits) = visitor_visits else {
            return SystemTime::UNIX_EPOCH;
        };
        let prev_visits = visitor_visits.prev_visits();
        if matches!(self.visit_scope, VisitScope::CurrentVisit)
            || self.visit_count < MIN_VISITOR_VISIT_COUNT
            || prev_visits.is_empty()
        {
            return visitor_visits.time_started;
        }
        let visit_index = (self.visit_count - MIN_VISITOR_VISIT_COUNT) as usize;
        prev_visits[visit_index.min(prev_visits.len() - 1)].time_started
    }

    fn parse_visit_scope(value: Option<&str>, default_visit_scope: VisitScope) -> VisitScope {
        match value {
            Some(value) if value.eq_ignore_ascii_case("CURRENT_VISIT") => VisitScope::CurrentVisit,
            Some(value) if value.eq_ignore_ascii_case("VISITOR") => VisitScope::Visitor,
            _ => default_visit_scope,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::data::visitor_visits::{Visit, VisitorVisits};
    use rstest::rstest;
    use std::time::{Duration, SystemTime};

    fn millis_to_time(millis: u64) -> SystemTime {
        SystemTime::UNIX_EPOCH + Duration::from_millis(millis)
    }

    fn new_visit(time_started: u64) -> Visit {
        Visit::new(millis_to_time(time_started), millis_to_time(time_started))
    }

    #[rstest]
    #[case(None, Some(1), VisitScope::CurrentVisit, true, 900)]
    #[case(Some("CURRENT_VISIT"), Some(1), VisitScope::CurrentVisit, true, 900)]
    #[case(Some("CURRENT_VISIT"), Some(25), VisitScope::CurrentVisit, true, 900)]
    #[case(Some("CURRENT_VISIT"), Some(1), VisitScope::CurrentVisit, false, 900)]
    #[case(Some("VISITOR"), Some(1), VisitScope::CurrentVisit, true, 900)]
    #[case(Some("VISITOR"), Some(2), VisitScope::CurrentVisit, true, 500)]
    #[case(Some("VISITOR"), Some(3), VisitScope::CurrentVisit, true, 100)]
    #[case(Some("VISITOR"), Some(25), VisitScope::CurrentVisit, true, 100)]
    #[case(Some("VISITOR"), Some(2), VisitScope::CurrentVisit, false, 900)]
    #[case(Some("VISITOR"), Some(25), VisitScope::CurrentVisit, false, 900)]
    fn test_assignment_threshold(
        #[case] visit_scope: Option<&str>,
        #[case] visit_count: Option<u32>,
        #[case] default_visit_scope: VisitScope,
        #[case] with_prev_visits: bool,
        #[case] expected_millis: u64,
    ) {
        let visitor_visits =
            VisitorVisits::new(10, vec![new_visit(500), new_visit(100)], millis_to_time(900), Duration::ZERO);
        let visitor_visits_without_prev = VisitorVisits::new(10, vec![], millis_to_time(900), Duration::ZERO);

        let condition = VisitorScopeCondition::new(visit_scope.map(str::to_owned), visit_count, default_visit_scope);
        let vv = if with_prev_visits { Some(&visitor_visits) } else { Some(&visitor_visits_without_prev) };

        assert!(condition.assignment_threshold_from_visits(vv) == millis_to_time(expected_millis));
    }

    #[rstest]
    #[case(Some("CURRENT_VISIT"), Some(1), VisitScope::CurrentVisit)]
    #[case(Some("VISITOR"), Some(2), VisitScope::CurrentVisit)]
    fn test_assignment_threshold_no_visits(
        #[case] visit_scope: Option<&str>,
        #[case] visit_count: Option<u32>,
        #[case] default_visit_scope: VisitScope,
    ) {
        let condition = VisitorScopeCondition::new(visit_scope.map(str::to_owned), visit_count, default_visit_scope);
        assert!(condition.assignment_threshold_from_visits(None) == SystemTime::UNIX_EPOCH);
    }
}
