use std::fmt;
use std::time::{Duration, SystemTime, UNIX_EPOCH};

/// Nanosecond-precision timestamp stored as nanoseconds since Unix epoch.
///
/// Big-endian byte encoding preserves temporal ordering for storage scans.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Timestamp(u64);

impl Timestamp {
    #[must_use]
    pub const fn from_nanos(nanos: u64) -> Self {
        Self(nanos)
    }

    /// Current wall-clock time.
    ///
    /// # Panics
    ///
    /// Panics if the system clock is before the Unix epoch.
    #[must_use]
    pub fn now() -> Self {
        let nanos = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .expect("system clock is before Unix epoch")
            .as_nanos();
        // u128 -> u64: nanos won't overflow u64 until year 2554
        #[allow(clippy::cast_possible_truncation)]
        Self(nanos as u64)
    }

    #[must_use]
    pub const fn as_nanos(self) -> u64 {
        self.0
    }

    /// Seconds elapsed from `self` to `now`, for decay math.
    ///
    /// Uses saturating subtraction — if `self` is after `now`, returns 0.0.
    #[must_use]
    pub fn seconds_since(self, now: Self) -> f64 {
        let delta_nanos = now.0.saturating_sub(self.0);
        #[allow(clippy::cast_precision_loss)]
        let secs = delta_nanos as f64 / 1e9;
        secs
    }

    /// Duration elapsed from `self` to `now`.
    ///
    /// Uses saturating subtraction — if `self` is after `now`, returns zero duration.
    #[must_use]
    pub const fn elapsed_since(self, now: Self) -> Duration {
        let delta_nanos = now.0.saturating_sub(self.0);
        Duration::from_nanos(delta_nanos)
    }

    /// Big-endian encoding that preserves temporal ordering for storage scans.
    #[must_use]
    pub const fn to_be_bytes(self) -> [u8; 8] {
        self.0.to_be_bytes()
    }
}

impl fmt::Display for Timestamp {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let secs = self.0 / 1_000_000_000;
        let nanos = self.0 % 1_000_000_000;
        write!(f, "{secs}.{nanos:09}")
    }
}

impl fmt::Debug for Timestamp {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "Timestamp({}ns)", self.0)
    }
}

#[cfg(test)]
#[allow(clippy::float_cmp)]
mod tests {
    use super::*;

    #[test]
    fn now_returns_reasonable_value() {
        let ts = Timestamp::now();
        // 2020-01-01 in nanoseconds
        let jan_2020_nanos = 1_577_836_800_000_000_000u64;
        assert!(ts.as_nanos() > jan_2020_nanos);
    }

    #[test]
    fn seconds_since_arithmetic() {
        let t1 = Timestamp::from_nanos(1_000_000_000); // 1s
        let t2 = Timestamp::from_nanos(3_500_000_000); // 3.5s
        let delta = t1.seconds_since(t2);
        assert!((delta - 2.5).abs() < 1e-9);
    }

    #[test]
    fn seconds_since_saturates_on_future() {
        let later = Timestamp::from_nanos(5_000_000_000);
        let earlier = Timestamp::from_nanos(1_000_000_000);
        assert_eq!(later.seconds_since(earlier), 0.0);
    }

    #[test]
    fn elapsed_since_duration() {
        let t1 = Timestamp::from_nanos(1_000_000_000);
        let t2 = Timestamp::from_nanos(3_500_000_000);
        let d = t1.elapsed_since(t2);
        assert_eq!(d, Duration::from_nanos(2_500_000_000));
    }

    #[test]
    fn display_format() {
        let ts = Timestamp::from_nanos(1_234_567_890_123_456_789);
        assert_eq!(ts.to_string(), "1234567890.123456789");
    }

    #[test]
    fn debug_format() {
        let ts = Timestamp::from_nanos(42);
        assert_eq!(format!("{ts:?}"), "Timestamp(42ns)");
    }

    #[test]
    fn be_bytes_roundtrip() {
        let ts = Timestamp::from_nanos(0x0102_0304_0506_0708);
        let recovered = u64::from_be_bytes(ts.to_be_bytes());
        assert_eq!(recovered, ts.as_nanos());
    }

    mod proptests {
        use super::*;
        use proptest::prelude::*;

        proptest! {
            #[test]
            fn ordering_matches_byte_ordering(a: u64, b: u64) {
                let ts_a = Timestamp::from_nanos(a);
                let ts_b = Timestamp::from_nanos(b);
                let bytes_a = ts_a.to_be_bytes();
                let bytes_b = ts_b.to_be_bytes();
                prop_assert_eq!(a.cmp(&b), bytes_a.cmp(&bytes_b));
            }

            #[test]
            fn seconds_since_non_negative(a: u64, b: u64) {
                let ts_a = Timestamp::from_nanos(a);
                let ts_b = Timestamp::from_nanos(b);
                let delta = ts_a.seconds_since(ts_b);
                prop_assert!(delta >= 0.0);
            }
        }
    }
}
