use std::time::SystemTime;

use crate::data::{PageView, keyed::Keyed};

#[derive(Debug)]
#[cfg_attr(test, derive(Clone, PartialEq))]
pub(crate) struct PageViewVisit {
    pub(crate) page_view: PageView,
    pub(crate) count: u32,
    pub(crate) last_ts: SystemTime,
}

impl PageViewVisit {
    pub fn new(page_view: PageView, count: u32, last_ts: Option<SystemTime>) -> Self {
        let last_ts = last_ts.unwrap_or_else(SystemTime::now);
        return Self {
            page_view,
            count,
            last_ts,
        };
    }

    pub fn page_view(&self) -> &PageView {
        return &self.page_view;
    }

    pub fn count(&self) -> u32 {
        return self.count;
    }

    pub fn inc_count(&mut self) {
        self.count += 1;
    }

    pub fn last_ts(&self) -> SystemTime {
        return self.last_ts;
    }

    pub fn overwrite(&self, page_view: PageView) -> Self {
        return Self::new(page_view, self.count + 1, None);
    }

    pub fn merge(&mut self, other: &PageViewVisit) {
        self.count += other.count;
        if self.last_ts < other.last_ts {
            self.last_ts = other.last_ts;
        }
    }
}

impl Keyed<String> for PageViewVisit {
    fn key(&self) -> &String {
        return &self.page_view.url;
    }
}

#[cfg(test)]
mod tests {

    use super::*;
    use std::time::Duration;

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

    fn sample_page_view(url: &str, title: Option<&str>, referrers: Vec<i32>) -> PageView {
        PageView::new(url.to_string(), title, referrers)
    }

    #[test]
    fn test_new_with_provided_ts() {
        let page_view = sample_page_view("https://example.com", Some("Example"), vec![1, 2, 3]);
        let ts = millis_to_time(1234567890);
        let visit = PageViewVisit::new(page_view, 3, Some(ts));

        assert_eq!(visit.page_view().url, "https://example.com");
        assert_eq!(visit.page_view().title.as_deref(), Some("Example"));
        assert_eq!(visit.page_view().referrers, &[1, 2, 3]);

        assert_eq!(visit.count(), 3);
        assert_eq!(visit.last_ts(), ts);
    }

    #[test]
    fn test_new_without_ts_sets_current_time() {
        let page_view = sample_page_view("https://now.com", None, vec![]);

        let start = SystemTime::now();

        let visit = PageViewVisit::new(page_view, 1, None);
        let ts = visit.last_ts();

        assert!(ts.duration_since(start).unwrap() <= Duration::from_millis(1));
    }

    #[test]
    fn test_overwrite_increments_count_and_sets_new_ts() {
        let original = sample_page_view("https://one.com", Some("One"), vec![5]);
        let new = sample_page_view("https://two.com", Some("Two"), vec![9]);

        let visit1 = PageViewVisit::new(original, 7, Some(millis_to_time(1000)));
        let visit2 = visit1.overwrite(new);

        assert_eq!(visit2.count(), 8);
        assert_eq!(visit2.page_view().url, "https://two.com");
        assert_eq!(visit2.page_view().title.as_deref(), Some("Two"));
        assert_eq!(visit2.page_view().referrers, &[9]);
        assert!(visit2.last_ts() > millis_to_time(1000));
    }

    #[test]
    fn test_merge_adds_counts_and_uses_latest_ts() {
        let pv = sample_page_view("https://merge.com", None, vec![]);
        let mut visit1 = PageViewVisit::new(pv.clone(), 2, Some(millis_to_time(1000)));
        let visit2 = PageViewVisit::new(pv.clone(), 3, Some(millis_to_time(2000)));

        visit1.merge(&visit2);

        assert_eq!(visit1.count(), 5);
        assert_eq!(visit1.last_ts(), millis_to_time(2000));
    }

    #[test]
    fn test_merge_keeps_later_ts() {
        let pv = sample_page_view("https://merge.com", None, vec![]);
        let mut visit1 = PageViewVisit::new(pv.clone(), 2, Some(millis_to_time(3000)));
        let visit2 = PageViewVisit::new(pv.clone(), 3, Some(millis_to_time(2000)));

        visit1.merge(&visit2);

        assert_eq!(visit1.count(), 5);
        assert_eq!(visit1.last_ts(), millis_to_time(3000));
    }

    #[test]
    fn test_key() {
        const URL: &str = "http://example.com";
        let pvv = PageViewVisit::new(sample_page_view(URL, None, Vec::new()), 1, None);
        assert_eq!(URL, pvv.key().as_str());
    }
}
