use brk_traversable::Traversable;
use brk_types::{
    Day1, Day3, Epoch, Halving, Height, Hour1, Hour4, Hour12, Minute10, Minute30, Month1, Month3,
    Month6, StoredU64, Timestamp, Version, Week1, Year1, Year10,
};
use vecdb::{CachedBoxedVec, CachedReadableVec, CachedVec, LazyVec, ReadableBoxedVec, VecValue};

use crate::internal::LazyPreviousDeltaVec;

#[derive(Clone, Traversable)]
pub struct Vecs {
    pub minute10: LazyVec<Height, Minute10, Height, Timestamp>,
    pub minute30: LazyVec<Height, Minute30, Height, Timestamp>,
    pub hour1: LazyVec<Height, Hour1, Height, Timestamp>,
    pub hour4: LazyVec<Height, Hour4, Height, Timestamp>,
    pub hour12: LazyVec<Height, Hour12, Height, Timestamp>,
    pub day1: CachedVec<LazyVec<Height, Day1, Height, Timestamp>>,
    pub day3: LazyVec<Height, Day3, Height, Timestamp>,
    pub epoch: LazyVec<Height, Epoch, Height, Timestamp>,
    pub halving: LazyVec<Height, Halving, Height, Timestamp>,
    pub week1: LazyVec<Height, Week1, Height, Timestamp>,
    pub month1: LazyVec<Height, Month1, Height, Timestamp>,
    pub month3: LazyVec<Height, Month3, Height, Timestamp>,
    pub month6: LazyVec<Height, Month6, Height, Timestamp>,
    pub year1: LazyVec<Height, Year1, Height, Timestamp>,
    pub year10: LazyVec<Height, Year10, Height, Timestamp>,
    pub tx_index_count: LazyPreviousDeltaVec<Height, StoredU64>,
}

impl Vecs {
    pub(crate) fn new(
        version: Version,
        timestamps: ReadableBoxedVec<Height, Timestamp>,
        transaction_count: ReadableBoxedVec<Height, StoredU64>,
    ) -> Self {
        Self {
            minute10: Self::from_timestamps("minute10", timestamps.clone(), |_, timestamp| {
                Minute10::from_timestamp(timestamp)
            }),
            minute30: Self::from_timestamps("minute30", timestamps.clone(), |_, timestamp| {
                Minute30::from_timestamp(timestamp)
            }),
            hour1: Self::from_timestamps("hour1", timestamps.clone(), |_, timestamp| {
                Hour1::from_timestamp(timestamp)
            }),
            hour4: Self::from_timestamps("hour4", timestamps.clone(), |_, timestamp| {
                Hour4::from_timestamp(timestamp)
            }),
            hour12: Self::from_timestamps("hour12", timestamps.clone(), |_, timestamp| {
                Hour12::from_timestamp(timestamp)
            }),
            day1: CachedVec::wrap(Self::from_timestamps(
                "day1",
                timestamps.clone(),
                |_, timestamp| Self::day1_from_timestamp(timestamp),
            )),
            day3: Self::from_timestamps("day3", timestamps.clone(), |_, timestamp| {
                Day3::from_timestamp(timestamp)
            }),
            epoch: Self::from_timestamps("epoch", timestamps.clone(), Self::epoch_from_height),
            halving: Self::from_timestamps(
                "halving",
                timestamps.clone(),
                Self::halving_from_height,
            ),
            week1: Self::from_timestamps("week1", timestamps.clone(), |_, timestamp| {
                Self::week1_from_timestamp(timestamp)
            }),
            month1: Self::from_timestamps("month1", timestamps.clone(), |_, timestamp| {
                Self::month1_from_timestamp(timestamp)
            }),
            month3: Self::from_timestamps("month3", timestamps.clone(), |_, timestamp| {
                Self::month3_from_timestamp(timestamp)
            }),
            month6: Self::from_timestamps("month6", timestamps.clone(), |_, timestamp| {
                Self::month6_from_timestamp(timestamp)
            }),
            year1: Self::from_timestamps("year1", timestamps.clone(), |_, timestamp| {
                Self::year1_from_timestamp(timestamp)
            }),
            year10: Self::from_timestamps("year10", timestamps, |_, timestamp| {
                Self::year10_from_timestamp(timestamp)
            }),
            tx_index_count: LazyPreviousDeltaVec::new("tx_index_count", version, transaction_count),
        }
    }

    fn from_timestamps<T: VecValue>(
        name: &str,
        timestamps: ReadableBoxedVec<Height, Timestamp>,
        compute: fn(Height, Timestamp) -> T,
    ) -> LazyVec<Height, T, Height, Timestamp> {
        LazyVec::init(name, Version::ZERO, timestamps, compute)
    }

    pub(crate) fn day1_from_timestamp(timestamp: Timestamp) -> Day1 {
        Day1::try_from(brk_types::Date::from(timestamp)).unwrap()
    }

    pub(crate) fn month1_from_timestamp(timestamp: Timestamp) -> Month1 {
        Month1::from(Self::day1_from_timestamp(timestamp))
    }

    pub(crate) fn week1_from_timestamp(timestamp: Timestamp) -> Week1 {
        Week1::from(Self::day1_from_timestamp(timestamp))
    }

    pub(crate) fn month3_from_timestamp(timestamp: Timestamp) -> Month3 {
        Month3::from(Self::month1_from_timestamp(timestamp))
    }

    pub(crate) fn month6_from_timestamp(timestamp: Timestamp) -> Month6 {
        Month6::from(Self::month1_from_timestamp(timestamp))
    }

    pub(crate) fn year1_from_timestamp(timestamp: Timestamp) -> Year1 {
        Year1::from(Self::month1_from_timestamp(timestamp))
    }

    pub(crate) fn year10_from_timestamp(timestamp: Timestamp) -> Year10 {
        Year10::from(Self::year1_from_timestamp(timestamp))
    }

    fn epoch_from_height(height: Height, _: Timestamp) -> Epoch {
        Epoch::from(height)
    }

    fn halving_from_height(height: Height, _: Timestamp) -> Halving {
        Halving::from(height)
    }

    pub fn day1_read_only_boxed_clone(&self) -> ReadableBoxedVec<Height, Day1> {
        Box::new(self.day1.clone())
    }

    pub fn day1_cached_boxed_clone(&self) -> CachedBoxedVec<Height, Day1> {
        self.day1.cached_boxed_clone()
    }

    /// Invalidate mappings after monotonic timestamps are rewritten without
    /// changing length or schema version.
    pub(crate) fn invalidate_timestamp_caches(&self) {
        self.day1.clear();
    }
}

#[cfg(test)]
mod tests {
    use std::sync::Arc;

    use brk_types::Date;
    use parking_lot::RwLock;
    use vecdb::{AnyVec, CachedVec, PrintableIndex, ReadableVec, TypedVec, short_type_name};

    use super::*;

    #[derive(Clone)]
    struct TimestampVec(Arc<RwLock<Vec<Timestamp>>>);

    impl TimestampVec {
        fn new(values: impl IntoIterator<Item = Timestamp>) -> Self {
            Self(Arc::new(RwLock::new(values.into_iter().collect())))
        }

        fn replace(&self, index: usize, timestamp: Timestamp) {
            self.0.write()[index] = timestamp;
        }
    }

    impl AnyVec for TimestampVec {
        fn version(&self) -> Version {
            Version::ONE
        }

        fn name(&self) -> &str {
            "timestamps"
        }

        fn len(&self) -> usize {
            self.0.read().len()
        }

        fn index_type_to_string(&self) -> &'static str {
            <Height as PrintableIndex>::to_string()
        }

        fn region_names(&self) -> Vec<String> {
            Vec::new()
        }

        fn value_type_to_size_of(&self) -> usize {
            size_of::<Timestamp>()
        }

        fn value_type_to_string(&self) -> &'static str {
            short_type_name::<Timestamp>()
        }
    }

    impl TypedVec for TimestampVec {
        type I = Height;
        type T = Timestamp;
    }

    impl ReadableVec<Height, Timestamp> for TimestampVec {
        fn read_into_at(&self, from: usize, to: usize, buf: &mut Vec<Timestamp>) {
            let values = self.0.read();
            let to = to.min(values.len());
            if from < to {
                buf.extend_from_slice(&values[from..to]);
            }
        }

        fn for_each_range_dyn_at(&self, from: usize, to: usize, each: &mut dyn FnMut(Timestamp)) {
            let values = self.0.read();
            for &value in &values[from.min(values.len())..to.min(values.len())] {
                each(value);
            }
        }

        fn fold_range_at<B, F: FnMut(B, Timestamp) -> B>(
            &self,
            from: usize,
            to: usize,
            init: B,
            fold: F,
        ) -> B {
            let values = self.0.read();
            values[from.min(values.len())..to.min(values.len())]
                .iter()
                .copied()
                .fold(init, fold)
        }

        fn try_fold_range_at<B, E, F: FnMut(B, Timestamp) -> Result<B, E>>(
            &self,
            from: usize,
            to: usize,
            init: B,
            fold: F,
        ) -> Result<B, E> {
            let values = self.0.read();
            values[from.min(values.len())..to.min(values.len())]
                .iter()
                .copied()
                .try_fold(init, fold)
        }
    }

    #[test]
    fn timestamp_mappings_match_the_previous_conversion_chain() {
        let timestamps = [
            Timestamp::from(Date::new(2009, 1, 1)),
            Timestamp::from(Date::new(2012, 4, 17)),
            Timestamp::from(Date::new(2026, 8, 3)),
        ];
        let source: ReadableBoxedVec<Height, Timestamp> = Box::new(TimestampVec::new(timestamps));

        let minute10 = Vecs::from_timestamps("minute10", source.clone(), |_, timestamp| {
            Minute10::from_timestamp(timestamp)
        });
        let day1 = Vecs::from_timestamps("day1", source.clone(), |_, timestamp| {
            Vecs::day1_from_timestamp(timestamp)
        });
        let week1 = Vecs::from_timestamps("week1", source.clone(), |_, timestamp| {
            Week1::from(Vecs::day1_from_timestamp(timestamp))
        });
        let month3 = Vecs::from_timestamps("month3", source.clone(), |_, timestamp| {
            Month3::from(Vecs::month1_from_timestamp(timestamp))
        });
        let year10 = Vecs::from_timestamps("year10", source, |_, timestamp| {
            Year10::from(Year1::from(Vecs::month1_from_timestamp(timestamp)))
        });

        assert_eq!(minute10.collect(), timestamps.map(Minute10::from_timestamp));
        assert_eq!(day1.collect(), timestamps.map(Vecs::day1_from_timestamp));
        assert_eq!(
            week1.collect(),
            timestamps.map(|timestamp| Week1::from(Vecs::day1_from_timestamp(timestamp)))
        );
        assert_eq!(
            month3.collect(),
            timestamps.map(|timestamp| Month3::from(Vecs::month1_from_timestamp(timestamp)))
        );
        assert_eq!(
            year10.collect(),
            timestamps.map(|timestamp| {
                Year10::from(Year1::from(Vecs::month1_from_timestamp(timestamp)))
            })
        );
    }

    #[test]
    fn height_mappings_preserve_epoch_boundaries() {
        let timestamp = Timestamp::from(Date::new(2009, 1, 1));

        assert_eq!(
            Vecs::epoch_from_height(Height::from(2_015_u32), timestamp),
            Epoch::from(0_usize)
        );
        assert_eq!(
            Vecs::epoch_from_height(Height::from(2_016_u32), timestamp),
            Epoch::from(1_usize)
        );
        assert_eq!(
            Vecs::halving_from_height(Height::from(209_999_u32), timestamp),
            Halving::from(0_usize)
        );
        assert_eq!(
            Vecs::halving_from_height(Height::from(210_000_u32), timestamp),
            Halving::from(1_usize)
        );
    }

    #[test]
    fn explicit_invalidation_refreshes_same_length_timestamp_changes() {
        let first = Timestamp::from(Date::new(2009, 1, 1));
        let second = Timestamp::from(Date::new(2009, 1, 2));
        let third = Timestamp::from(Date::new(2009, 1, 3));
        let timestamps = TimestampVec::new([first, second]);
        let cached_timestamps = CachedVec::wrap(timestamps.clone());
        let day1 = CachedVec::wrap(Vecs::from_timestamps(
            "day1",
            Box::new(cached_timestamps.clone()),
            |_, timestamp| Vecs::day1_from_timestamp(timestamp),
        ));

        assert_eq!(day1.collect_one_at(1), Some(Day1::from(1_usize)));
        timestamps.replace(1, third);
        assert_eq!(day1.collect_one_at(1), Some(Day1::from(1_usize)));

        cached_timestamps.clear();
        day1.clear();
        assert_eq!(day1.collect_one_at(1), Some(Day1::from(2_usize)));
    }
}
