use chrono::{DateTime, Utc};
use std::{
    fmt::{Debug, Display},
    ops::{Deref, DerefMut},
};

/// A wrapper struct that records a timestamp for whatever data you give it. The
/// timestamp is recorded using both the system clock (via `chrono`) and a
/// monotonic clock (via `std::time::Instant`).
///
/// The monotonic timestamp is used for all comparisons of timestamps provided
/// by this crate, while the system clock timestamp is used for display,
/// serialization, etc.
pub struct Timestamped<T> {
    /// The monotonic timestamp for this instance. This is used for all comparisons
    /// of timestamps.
    monotonic_timestamp: std::time::Instant,

    /// The system clock timestamp for this instance.
    timestamp: DateTime<Utc>,

    /// The data that is timestamped. This can be any type.
    pub data: T,
}

impl<T> Timestamped<T> {
    /// Creates a new `Timestamped` instance with the given data and the current timestamp.
    pub fn new(datum: T) -> Self {
        // Grab these first so we can get the most accurate timestamp possible.
        let monotonic_timestamp = std::time::Instant::now();
        let chrono_timestamp = Utc::now();
        Self {
            data: datum,
            monotonic_timestamp,
            timestamp: chrono_timestamp,
        }
    }

    /// Updates the timestamp to the current time. Does not modify the underlying data.
    /// (Mnemonic: Like the Unix `touch` command, which updates the timestamp of a file.)
    pub fn touch(&mut self) {
        self.monotonic_timestamp = std::time::Instant::now();
        self.timestamp = Utc::now();
    }

    /// Returns the [`chrono::DateTime`] timestamp for this instance.
    pub fn timestamp(&self) -> DateTime<Utc> {
        self.timestamp
    }

    /// Returns the monotonic timestamp for this instance.
    pub fn monotonic_timestamp(&self) -> std::time::Instant {
        self.monotonic_timestamp
    }

    /// Updates the timestamp of this instance to match the timestamp of another
    /// `Timestamped` instance. Does *not* modify the underlying data.
    pub fn update_timestamp_from(&mut self, other: &Self) {
        self.monotonic_timestamp = other.monotonic_timestamp;
        self.timestamp = other.timestamp;
    }

    /// Consumes the `Timestamped` instance and returns the underlying data,
    /// discarding the timestamp.
    pub fn into_inner(self) -> T {
        self.data
    }

    /// A saturating version of `duration_since` that returns a zero duration if
    /// the other timestamp is later than this one or there are any monotonicity
    /// bugs.
    pub fn duration_since_sat(&self, other_previous_timestamp: &Self) -> std::time::Duration {
        self.monotonic_timestamp
            .duration_since(other_previous_timestamp.monotonic_timestamp)
    }

    /// Returns the duration since this time and a previous other `Timestamped`
    /// instance, or `None` if the duration comes up negative. This can hppen if
    /// the other timestamp is later than this one, or if there are any
    /// monotonicity bugs. (See
    /// ["Monotonicity"](https://doc.rust-lang.org/std/time/struct.Instant.html#monotonicity) in
    /// the documentation for [`std::time::Instant`] for more details.)
    pub fn duration_since(&self, previous_other_timestamped: &Self) -> Option<std::time::Duration> {
        self.monotonic_timestamp
            .checked_duration_since(previous_other_timestamped.monotonic_timestamp)
    }

    /// Returns the duration until some other future timestamped instance, or `None`
    /// if the duration comes up negative. See [`duration_since`] for more details.
    pub fn duration_until(&self, other_future_timestamped: &Self) -> Option<std::time::Duration> {
        other_future_timestamped
            .monotonic_timestamp
            .checked_duration_since(self.monotonic_timestamp)
    }

    /// Like [`duration_since_sat`] but for the future. Returns `None` if the
    /// duration comes up negative or there are monotonicity bugs like those
    /// described in [`duration_since`].
    pub fn duration_until_sat(
        &self,
        other_future_timestamped: &Self,
    ) -> Option<std::time::Duration> {
        other_future_timestamped
            .monotonic_timestamp
            .checked_duration_since(self.monotonic_timestamp)
    }

    pub fn is_older_than(&self, other: &Self) -> bool {
        self.monotonic_timestamp < other.monotonic_timestamp
    }

    pub fn is_newer_than(&self, other: &Self) -> bool {
        self.monotonic_timestamp > other.monotonic_timestamp
    }

    /// Returns `true` if the two timestamps are within `max_duration` of each
    /// other, barring any monotonicity bugs.
    pub fn happened_within_x_of(&self, other: &Self, max_duration: std::time::Duration) -> bool {
        let self_before_other = self.duration_until_sat(other);
        let self_after_other = other.duration_until_sat(self);
        match (self_before_other, self_after_other) {
            (Some(_), Some(_)) => unreachable!(),
            (Some(before), None) => before <= max_duration,
            (None, Some(after)) => after <= max_duration,
            (None, None) => false,
        }
    }
}

impl<T> Default for Timestamped<T>
where
    T: Default,
{
    fn default() -> Self {
        Self::new(T::default())
    }
}

impl<T> Debug for Timestamped<T>
where
    T: Debug,
{
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Timestamped")
            .field("timestamp", &self.timestamp)
            .field("monotonic_timestamp", &self.monotonic_timestamp)
            .field("data", &self.data)
            .finish()
    }
}

impl<T> Display for Timestamped<T>
where
    T: Display,
{
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "Timestamped {{ timestamp: {}, monotonic_timestamp: {:?}, data: {} }}",
            self.timestamp, self.monotonic_timestamp, self.data
        )
    }
}

impl<T> Clone for Timestamped<T>
where
    T: Clone,
{
    fn clone(&self) -> Self {
        Self {
            data: self.data.clone(),
            timestamp: self.timestamp,
            monotonic_timestamp: self.monotonic_timestamp,
        }
    }
}

impl<T> Copy for Timestamped<T> where T: Copy {}

impl<T> PartialEq for Timestamped<T>
where
    T: PartialEq,
{
    fn eq(&self, other: &Self) -> bool {
        self.data == other.data && self.monotonic_timestamp == other.monotonic_timestamp
    }
}

impl<T> Deref for Timestamped<T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        &self.data
    }
}

impl<T> AsRef<T> for Timestamped<T> {
    fn as_ref(&self) -> &T {
        &self.data
    }
}

impl<T> From<T> for Timestamped<T> {
    fn from(value: T) -> Self {
        Self::new(value)
    }
}

impl<T> DerefMut for Timestamped<T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.data
    }
}

impl<T> AsMut<T> for Timestamped<T> {
    fn as_mut(&mut self) -> &mut T {
        &mut self.data
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_timestamps_give_different_timestamps() {
        let t1 = Timestamped::new(10);
        let t2 = Timestamped::new(10);
        assert_ne!(t1.timestamp(), t2.timestamp());
        assert_ne!(t1.monotonic_timestamp(), t2.monotonic_timestamp());
        assert_ne!(t1, t2);
    }

    #[test]
    fn test_partial_eq_true_positive() {
        let value: u32 = 1;
        let t1 = Timestamped::new(value);
        let mut t2 = Timestamped::new(value);
        t2.update_timestamp_from(&t1);
        assert_eq!(t1.data, t2.data);
        assert_eq!(t1.monotonic_timestamp(), t2.monotonic_timestamp());
        assert_eq!(t1.timestamp(), t2.timestamp());
        assert_eq!(t1, t2);
    }
}
