//! Injected clock for deterministic time in tests and consistent frame timestamps.
//!
//! All relative-time rendering (e.g., "3h ago") uses [`Clock::now()`] rather
//! than wall-clock time directly. This enables:
//! - Deterministic snapshot tests via [`FakeClock`]
//! - Consistent timestamps within a single frame render pass

use std::sync::{Arc, Mutex};

use chrono::{DateTime, TimeDelta, Utc};

/// Trait for obtaining the current time.
///
/// Inject via `Arc<dyn Clock>` to allow swapping between real and fake clocks.
pub trait Clock: Send + Sync {
    /// Returns the current time.
    fn now(&self) -> DateTime<Utc>;

    /// Returns the current time as milliseconds since the Unix epoch.
    fn now_ms(&self) -> i64 {
        self.now().timestamp_millis()
    }
}

// ---------------------------------------------------------------------------
// SystemClock
// ---------------------------------------------------------------------------

/// Real wall-clock time via `chrono::Utc::now()`.
#[derive(Debug, Clone, Copy)]
pub struct SystemClock;

impl Clock for SystemClock {
    fn now(&self) -> DateTime<Utc> {
        Utc::now()
    }
}

// ---------------------------------------------------------------------------
// FakeClock
// ---------------------------------------------------------------------------

/// A controllable clock for tests. Returns a frozen time that can be
/// advanced or set explicitly.
///
/// `FakeClock` is `Clone` (shares the inner `Arc`) and `Send + Sync`
/// for use across `Cmd::task` threads.
#[derive(Debug, Clone)]
pub struct FakeClock {
    inner: Arc<Mutex<DateTime<Utc>>>,
}

impl FakeClock {
    /// Create a fake clock frozen at the given time.
    #[must_use]
    pub fn new(time: DateTime<Utc>) -> Self {
        Self {
            inner: Arc::new(Mutex::new(time)),
        }
    }

    /// Create a fake clock frozen at the given millisecond epoch timestamp.
    ///
    /// Convenience for action tests that work with raw epoch milliseconds.
    #[must_use]
    pub fn from_ms(epoch_ms: i64) -> Self {
        let time = DateTime::from_timestamp_millis(epoch_ms).expect("valid millisecond timestamp");
        Self::new(time)
    }

    /// Advance the clock by `duration`. Uses `checked_add` to handle overflow
    /// gracefully — if the addition would overflow, the time is not changed.
    pub fn advance(&self, duration: TimeDelta) {
        let mut guard = self.inner.lock().expect("FakeClock mutex poisoned");
        if let Some(advanced) = guard.checked_add_signed(duration) {
            *guard = advanced;
        }
    }

    /// Set the clock to an exact time.
    pub fn set(&self, time: DateTime<Utc>) {
        let mut guard = self.inner.lock().expect("FakeClock mutex poisoned");
        *guard = time;
    }
}

impl Clock for FakeClock {
    fn now(&self) -> DateTime<Utc> {
        *self.inner.lock().expect("FakeClock mutex poisoned")
    }
}

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

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

    fn fixed_time() -> DateTime<Utc> {
        Utc.with_ymd_and_hms(2026, 2, 12, 12, 0, 0).unwrap()
    }

    #[test]
    fn test_fake_clock_frozen() {
        let clock = FakeClock::new(fixed_time());
        let t1 = clock.now();
        let t2 = clock.now();
        assert_eq!(t1, t2);
        assert_eq!(t1, fixed_time());
    }

    #[test]
    fn test_fake_clock_advance() {
        let clock = FakeClock::new(fixed_time());
        clock.advance(TimeDelta::hours(3));
        let expected = Utc.with_ymd_and_hms(2026, 2, 12, 15, 0, 0).unwrap();
        assert_eq!(clock.now(), expected);
    }

    #[test]
    fn test_fake_clock_set() {
        let clock = FakeClock::new(fixed_time());
        let new_time = Utc.with_ymd_and_hms(2030, 1, 1, 0, 0, 0).unwrap();
        clock.set(new_time);
        assert_eq!(clock.now(), new_time);
    }

    #[test]
    fn test_fake_clock_clone_shares_state() {
        let clock1 = FakeClock::new(fixed_time());
        let clock2 = clock1.clone();
        clock1.advance(TimeDelta::minutes(30));
        // Both clones see the advanced time.
        assert_eq!(clock1.now(), clock2.now());
    }

    #[test]
    fn test_system_clock_returns_reasonable_time() {
        let clock = SystemClock;
        let now = clock.now();
        // Sanity: time should be after 2025.
        assert!(now.year() >= 2025);
    }

    #[test]
    fn test_fake_clock_is_send_sync() {
        fn assert_send_sync<T: Send + Sync>() {}
        assert_send_sync::<FakeClock>();
        assert_send_sync::<SystemClock>();
    }

    #[test]
    fn test_clock_trait_object_works() {
        let fake: Arc<dyn Clock> = Arc::new(FakeClock::new(fixed_time()));
        assert_eq!(fake.now(), fixed_time());

        let real: Arc<dyn Clock> = Arc::new(SystemClock);
        let _ = real.now(); // Just verify it doesn't panic.
    }

    use chrono::Datelike;
}
