use std::{
    sync::Arc,
    time::{Duration, SystemTime},
};

use crate::{
    network::query_builder::{QueryBuilder, QueryParam},
    tracking::trackable::{Trackable, TrackableBase, TrackingStateCell},
};

#[derive(Debug)]
#[cfg_attr(test, derive(Clone, PartialEq))]
pub(crate) struct Visit {
    pub(crate) time_started: SystemTime,
    pub(crate) time_last_activity: SystemTime,
}

impl Visit {
    pub fn new(time_started: SystemTime, time_last_activity: SystemTime) -> Self {
        return Visit {
            time_started,
            time_last_activity,
        };
    }
}

const EVENT_TYPE: &str = "staticData";

#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
#[allow(dead_code)]
pub(crate) struct VisitorVisits {
    pub(crate) trackable_base: TrackableBase,
    pub(crate) visit_number: u32,
    pub(crate) prev_visits: Vec<Visit>,
    pub(crate) time_started: SystemTime,
    pub(crate) time_since_prev_visit: Duration,
}

impl VisitorVisits {
    pub fn new(
        visit_number: u32,
        prev_visits: Vec<Visit>,
        time_started: SystemTime,
        time_since_prev_visit: Duration,
    ) -> Self {
        return Self {
            trackable_base: TrackableBase::default(),
            visit_number,
            prev_visits,
            time_started,
            time_since_prev_visit,
        };
    }

    pub fn prev_visits(&self) -> &[Visit] {
        return &self.prev_visits;
    }

    pub fn into_local(self, time_started: SystemTime) -> Self {
        let time_since_previous_visit = self
            .prev_visits
            .iter()
            .find_map(|v| {
                if time_started >= v.time_last_activity {
                    time_started.duration_since(v.time_last_activity).ok()
                } else {
                    None
                }
            })
            .unwrap_or(Duration::ZERO);
        return Self {
            time_started,
            time_since_prev_visit: time_since_previous_visit,
            ..self
        };
    }
}

impl Trackable for VisitorVisits {
    fn encode_query(&self) -> String {
        QueryBuilder::with_params(vec![
            (QueryParam::Nonce, self.trackable_base.nonce().into()),
            (QueryParam::EventType, EVENT_TYPE.into()),
            (QueryParam::VisitNumber, self.visit_number.to_string().into()),
            (QueryParam::TimeSincePreviousVisit, self.time_since_prev_visit.as_millis().to_string().into()),
        ])
        .build()
    }

    fn state_cell(&self) -> &Arc<TrackingStateCell> {
        self.trackable_base.state_cell()
    }
}

#[cfg(test)]
mod tests {
    use crate::utils::test_utils::remove_nonce_parameter;

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

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

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

    #[rstest]
    fn test_event_type() {
        assert_eq!("staticData", EVENT_TYPE);
    }

    #[test]
    fn test_new() {
        const VISIT_NUMBER: u32 = 1;
        let time_started = millis_to_time(1234567890);
        let time_since_prev_visit = Duration::from_millis(1000);
        let prev_visits = vec![new_visit(20, 25)];
        let vv = VisitorVisits::new(VISIT_NUMBER, prev_visits.clone(), time_started, time_since_prev_visit);
        assert_eq!(VISIT_NUMBER, vv.visit_number);
        assert_eq!(time_started, vv.time_started);
        assert_eq!(time_since_prev_visit, vv.time_since_prev_visit);
        assert_eq!(prev_visits, vv.prev_visits());
    }

    #[rstest]
    #[case(100, vec![new_visit(20, 25), new_visit(10, 15)], 75)]
    #[case(100, vec![new_visit(95, 101), new_visit(80, 85)], 15)]
    #[case(100, vec![new_visit(95, 100), new_visit(80, 85)], 0)]
    #[case(100, vec![new_visit(95, 101)], 0)]
    fn test_into_local(
        #[case] time_started: u64,
        #[case] prev_visits: Vec<Visit>,
        #[case] expected_time_since_prev_visit: u64,
    ) {
        const VISIT_NUMBER: u32 = 10;
        let time_started = millis_to_time(time_started);
        let vv = VisitorVisits::new(VISIT_NUMBER, prev_visits.clone(), SystemTime::UNIX_EPOCH, Duration::ZERO)
            .into_local(time_started);
        assert_eq!(VISIT_NUMBER, vv.visit_number);
        assert_eq!(time_started, vv.time_started);
        assert_eq!(Duration::from_millis(expected_time_since_prev_visit), vv.time_since_prev_visit);
        assert_eq!(prev_visits, vv.prev_visits());
    }

    #[rstest]
    fn test_encode_query() {
        // arrange
        const VISIT_NUMBER: u32 = 123;
        const TIME_SINCE_PREVIOUS_VISIT: u64 = 1098;
        #[rustfmt::skip]
        let expected = format!(
            "{}={}&{}={}&{}={}",
            QueryParam::EventType.as_str(), EVENT_TYPE,
            QueryParam::VisitNumber.as_str(), VISIT_NUMBER,
            QueryParam::TimeSincePreviousVisit.as_str(), TIME_SINCE_PREVIOUS_VISIT,
        );
        let vv = VisitorVisits::new(
            VISIT_NUMBER,
            vec![],
            SystemTime::UNIX_EPOCH,
            Duration::from_millis(TIME_SINCE_PREVIOUS_VISIT),
        );
        // act
        let actual = vv.encode_query();
        // assert
        let actual = remove_nonce_parameter(&actual);
        assert_eq!(expected, actual);
    }
}
