use std::{
    borrow::Borrow,
    collections::{HashMap, hash_map::Entry},
    hash::Hash,
    marker::PhantomData,
};

use crate::{
    data::{PageView, keyed::Keyed, page_view_visit::PageViewVisit},
    storage::{NormalWritingMode, OverwritingWritingMode, PageViewVisitWritingMode, WritingMode},
};

// MARK: - readable map storage

#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub(crate) struct ReadableMapStorage<K: Eq + Hash, V> {
    values: Option<Box<HashMap<K, V>>>,
}

impl<K: Eq + Hash, V> ReadableMapStorage<K, V> {
    pub fn new() -> Self {
        return Self { values: None };
    }

    pub fn len(&self) -> usize {
        match &self.values {
            Some(values) => values.len(),
            None => 0,
        }
    }

    pub fn iter(&self) -> ReadableMapStorageIter<'_, K, V> {
        let inner_iter = if let Some(values) = &self.values { Some(values.values()) } else { None };
        return ReadableMapStorageIter { inner_iter };
    }

    pub fn get<Q>(&self, key: &Q) -> Option<&V>
    where
        Q: ?Sized,
        K: Borrow<Q>,
        Q: Hash + Eq,
    {
        match &self.values {
            Some(values) => values.get(key),
            None => None,
        }
    }

    fn values_mut(&mut self) -> &mut HashMap<K, V> {
        if self.values.is_none() {
            self.values = Some(Box::new(HashMap::new()));
        }
        return self.values.as_mut().unwrap();
    }
}

pub(crate) struct ReadableMapStorageIter<'a, K, V> {
    inner_iter: Option<std::collections::hash_map::Values<'a, K, V>>,
}

impl<'a, K, V> Iterator for ReadableMapStorageIter<'a, K, V> {
    type Item = &'a V;

    fn next(&mut self) -> Option<Self::Item> {
        return self.inner_iter.as_mut().and_then(|iter| iter.next());
    }
}

// MARK: - read-write map storage

#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub(crate) struct MapStorage<K: Eq + Hash + Clone, V: Keyed<K>, M: WritingMode> {
    mode: PhantomData<M>,
    r: ReadableMapStorage<K, V>,
}

impl<K: Eq + Hash + Clone, V: Keyed<K>, M: WritingMode> MapStorage<K, V, M> {
    pub fn new() -> Self {
        return Self {
            mode: PhantomData {},
            r: ReadableMapStorage::new(),
        };
    }

    pub fn r(&self) -> &ReadableMapStorage<K, V> {
        return &self.r;
    }

    pub fn remove(&mut self, key: &K) -> Option<V> {
        match &mut self.r.values {
            Some(values) => values.remove(key),
            None => None,
        }
    }

    pub fn clear(&mut self) {
        if let Some(values) = &mut self.r.values {
            values.clear();
        }
    }

    fn insert_value(&mut self, value: V) {
        let key = value.key().to_owned();
        match &mut self.r.values {
            Some(values) => {
                values.insert(key, value);
            }
            None => {
                let mut map = Box::new(HashMap::new());
                map.insert(key, value);
                self.r.values = Some(map);
            }
        }
    }
}

impl<K: Eq + Hash + Clone, V: Keyed<K>> MapStorage<K, V, NormalWritingMode> {
    pub fn put(&mut self, value: V) {
        self.insert_value(value);
    }
}

impl<K: Eq + Hash + Clone, V: Keyed<K>> MapStorage<K, V, OverwritingWritingMode> {
    pub fn put(&mut self, value: V, overwrite: bool) {
        let overwrite = overwrite
            || match &self.r.values {
                Some(values) => !values.contains_key(value.key()),
                None => true,
            };
        if overwrite {
            self.insert_value(value);
        }
    }
}

impl MapStorage<String, PageViewVisit, PageViewVisitWritingMode> {
    pub fn overwrite(&mut self, pv: PageView) {
        let values = self.r.values_mut();
        if let Some(former) = values.get_mut(&pv.url) {
            *former = former.overwrite(pv);
        } else {
            values.insert(pv.url.clone(), PageViewVisit::new(pv, 1, None));
        }
    }

    pub fn merge(&mut self, other_pvv: PageViewVisit) {
        match self.r.values_mut().entry(other_pvv.page_view().url.clone()) {
            Entry::Occupied(mut e) => {
                let pvv = e.get_mut();
                pvv.merge(&other_pvv);
            }
            Entry::Vacant(e) => {
                e.insert(other_pvv);
            }
        }
    }
}

// MARK: - tests

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

    use crate::utils::time_utils::millis_to_time;

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

    #[rstest]
    fn test_readable_new() {
        // arrange
        let key1 = "k1".to_owned();
        let key2 = "k2".to_owned();
        let s: ReadableMapStorage<String, i32> = ReadableMapStorage::new();
        // act-assert
        assert_eq!(0, s.len());
        assert_eq!(None, s.get(&key1));
        assert_eq!(None, s.get(&key2));
        assert_eq!(0, s.iter().count());
    }

    #[rstest]
    fn test_readable_one_item() {
        // arrange
        let key1 = "k1".to_owned();
        let key2 = "k2".to_owned();
        let mut s: ReadableMapStorage<String, i32> = ReadableMapStorage::new();
        s.values = Some(Box::new(HashMap::from([(key1.clone(), 123)])));
        // act-assert
        assert_eq!(1, s.len());
        assert_eq!(Some(&123), s.get(&key1));
        assert_eq!(None, s.get(&key2));
        let mut iter = s.iter();
        assert_eq!(Some(&123), iter.next());
        assert_eq!(None, iter.next());
    }

    #[rstest]
    fn test_readable_several_items() {
        // arrange
        let key1 = "k1".to_owned();
        let key2 = "k2".to_owned();
        let mut s: ReadableMapStorage<String, i32> = ReadableMapStorage::new();
        s.values = Some(Box::new(HashMap::from([(key1.clone(), 123), (key2.clone(), 321)])));
        // act-assert
        assert_eq!(2, s.len());
        assert_eq!(Some(&123), s.get(&key1));
        assert_eq!(Some(&321), s.get(&key2));
        let mut counter: [i32; 2] = [0, 0];
        for v in s.iter() {
            if *v == 123 {
                counter[0] += 1;
            } else {
                assert_eq!(321, *v);
                counter[1] += 1;
            }
        }
        assert!(counter.iter().all(|c| *c == 1));
    }

    #[derive(Debug)]
    pub(super) struct Value<'a> {
        k: &'a String,
        v: i32,
    }

    impl<'a> Value<'a> {
        pub fn new(k: &'a String, v: i32) -> Self {
            return Self { k, v };
        }
    }

    impl<'a> Keyed<String> for Value<'a> {
        fn key(&self) -> &String {
            return self.k;
        }
    }

    impl<'a> PartialEq for Value<'a> {
        fn eq(&self, other: &Self) -> bool {
            return (self.k == other.k) && (self.v == other.v);
        }

        fn ne(&self, other: &Self) -> bool {
            return !self.eq(other);
        }
    }

    #[rstest]
    fn test_new() {
        // arrange
        let s: MapStorage<String, Value, NormalWritingMode> = MapStorage::new();
        // act-assert
        assert_eq!(None, s.r.values);
        assert!(std::ptr::eq(&s.r, s.r()));
    }

    #[rstest]
    fn test_remove() {
        // arrange
        let key1 = "k1".to_owned();
        let key2 = "k2".to_owned();
        let mut s: MapStorage<String, Value, NormalWritingMode> = MapStorage::new();
        s.r.values = Some(Box::new(HashMap::from([
            (key1.clone(), Value::new(&key1, 123)),
            (key2.clone(), Value::new(&key2, 321)),
        ])));
        // act
        let removed_item = s.remove(&key1);
        // assert
        assert_eq!(Some(Value::new(&key1, 123)), removed_item);
        assert_eq!(1, s.r.values.as_ref().unwrap().len());
        assert_eq!(None, s.r.values.as_ref().unwrap().get(&key1));
        assert_eq!(Some(&Value::new(&key2, 321)), s.r.values.as_ref().unwrap().get(&key2));
    }

    #[rstest]
    fn test_clear() {
        // arrange
        let key1 = "k1".to_owned();
        let key2 = "k2".to_owned();
        let mut s: MapStorage<String, Value, NormalWritingMode> = MapStorage::new();
        s.r.values = Some(Box::new(HashMap::from([
            (key1.clone(), Value::new(&key1, 123)),
            (key2.clone(), Value::new(&key2, 321)),
        ])));
        // act
        s.clear();
        // assert
        assert!(s.r.values.as_ref().unwrap().is_empty());
    }

    #[rstest]
    fn test_put_one_item_normal_mode() {
        // arrange
        let key1 = "k1".to_owned();
        let mut s: MapStorage<String, Value, NormalWritingMode> = MapStorage::new();
        // act
        s.put(Value::new(&key1, 123));
        // assert
        assert_eq!(1, s.r.values.as_ref().unwrap().len());
        assert_eq!(Some(&Value::new(&key1, 123)), s.r.values.as_ref().unwrap().get(&key1));
    }

    #[rstest]
    fn test_put_several_items_normal_mode() {
        // arrange
        let key1 = "k1".to_owned();
        let key2 = "k2".to_owned();
        let mut s: MapStorage<String, Value, NormalWritingMode> = MapStorage::new();
        // act
        s.put(Value::new(&key1, 123));
        s.put(Value::new(&key2, 321));
        // assert
        assert_eq!(2, s.r.values.as_ref().unwrap().len());
        assert_eq!(Some(&Value::new(&key1, 123)), s.r.values.as_ref().unwrap().get(&key1));
        assert_eq!(Some(&Value::new(&key2, 321)), s.r.values.as_ref().unwrap().get(&key2));
    }

    #[rstest]
    #[case(false)]
    #[case(true)]
    fn test_put_item_overwriting_mode(#[case] overwrite: bool) {
        // arrange
        let key1 = "k1".to_owned();
        let mut s: MapStorage<String, Value, OverwritingWritingMode> = MapStorage::new();
        // act
        s.put(Value::new(&key1, 123), overwrite);
        // assert
        assert_eq!(1, s.r.values.as_ref().unwrap().len());
        assert_eq!(Some(&Value::new(&key1, 123)), s.r.values.as_ref().unwrap().get(&key1));
    }

    #[rstest]
    #[case(false)]
    #[case(true)]
    fn test_put_item_overwriting_mode_overwriting(#[case] overwrite: bool) {
        // arrange
        let key1 = "k1".to_owned();
        let mut s: MapStorage<String, Value, OverwritingWritingMode> = MapStorage::new();
        s.r.values = Some(Box::new(HashMap::from([(key1.clone(), Value::new(&key1, 123))])));
        // act
        s.put(Value::new(&key1, 321), overwrite);
        // assert
        assert_eq!(1, s.r.values.as_ref().unwrap().len());
        assert_eq!(
            Some(&Value::new(&key1, if overwrite { 321 } else { 123 })),
            s.r.values.as_ref().unwrap().get(&key1)
        );
    }

    #[rstest]
    fn test_pvv_mode_overwrite_none() {
        // arrange
        let url = "http://example.net/".to_owned();
        let mut s: MapStorage<String, PageViewVisit, PageViewVisitWritingMode> = MapStorage::new();
        let expected_last_ts = SystemTime::now();
        // act
        s.overwrite(PageView::new_with_url(&url));
        // assert
        assert_eq!(1, s.r.values.as_ref().unwrap().len());
        let pvv = s.r.values.as_ref().unwrap().get(&url).unwrap();
        assert_eq!(1, pvv.count());
        assert!(pvv.last_ts().duration_since(expected_last_ts).unwrap() < Duration::from_millis(1));
        assert_eq!(url, pvv.page_view().url);
        assert_eq!(None, pvv.page_view().title);
    }

    #[rstest]
    fn test_pvv_mode_overwrite_previous_page_view() {
        // arrange
        const TITLE2: &str = "T2";
        let url = "http://example.net/".to_owned();
        let mut s: MapStorage<String, PageViewVisit, PageViewVisitWritingMode> = MapStorage::new();
        s.r.values = Some(Box::new(HashMap::from([(
            url.clone(),
            PageViewVisit::new(PageView::new_with_url(&url), 1, Some(millis_to_time(1234567890))),
        )])));
        let expected_last_ts = SystemTime::now();
        // act
        s.overwrite(PageView::new(&url, Some(TITLE2), vec![]));
        // assert
        assert_eq!(1, s.r.values.as_ref().unwrap().len());
        let pvv = s.r.values.as_ref().unwrap().get(&url).unwrap();
        assert_eq!(2, pvv.count());
        assert!(pvv.last_ts().duration_since(expected_last_ts).unwrap() < Duration::from_millis(1));
        assert_eq!(url, pvv.page_view().url);
        assert_eq!(Some(TITLE2.to_owned()), pvv.page_view().title);
    }

    #[rstest]
    fn test_pvv_mode_merge_with_none() {
        // arrange
        let last_ts = millis_to_time(1234567890);
        let url = "http://example.net/".to_owned();
        let mut s: MapStorage<String, PageViewVisit, PageViewVisitWritingMode> = MapStorage::new();
        // act
        s.merge(PageViewVisit::new(PageView::new_with_url(&url), 4, Some(last_ts)));
        // assert
        assert_eq!(1, s.r.values.as_ref().unwrap().len());
        let pvv = s.r.values.as_ref().unwrap().get(&url).unwrap();
        assert_eq!(4, pvv.count());
        assert_eq!(last_ts, pvv.last_ts());
        assert_eq!(url, pvv.page_view().url);
        assert_eq!(None, pvv.page_view().title);
    }

    #[rstest]
    #[case(millis_to_time(1234567890), millis_to_time(1234567891))]
    #[case(millis_to_time(1234567890), millis_to_time(1234567889))]
    fn test_pvv_mode_merge_with_previous_page_view_visit(#[case] last_ts1: SystemTime, #[case] last_ts2: SystemTime) {
        // arrange
        const TITLE2: &str = "T2";
        let url = "http://example.net/".to_owned();
        let mut s: MapStorage<String, PageViewVisit, PageViewVisitWritingMode> = MapStorage::new();
        s.r.values = Some(Box::new(HashMap::from([(
            url.clone(),
            PageViewVisit::new(PageView::new_with_url(&url), 5, Some(last_ts1)),
        )])));
        // act
        s.merge(PageViewVisit::new(PageView::new(&url, TITLE2.into(), vec![]), 4, Some(last_ts2)));
        // assert
        assert_eq!(1, s.r.values.as_ref().unwrap().len());
        let pvv = s.r.values.as_ref().unwrap().get(&url).unwrap();
        assert_eq!(9, pvv.count());
        assert_eq!(last_ts1.max(last_ts2), pvv.last_ts());
        assert_eq!(url, pvv.page_view().url);
        assert_eq!(None, pvv.page_view().title);
    }
}
