//! Hybrid Logical Clock (HLC) for causal ordering across distributed nodes.
//!
//! Reference: Kulkarni et al., 2014 -- "Logical Physical Clocks and Consistent
//! Snapshots in Globally Distributed Databases".
//!
//! The HLC combines a wall-clock component with a bounded logical counter.
//! On every local event the clock advances to `max(wall, prev_wall)`;
//! when the wall clock does not advance, the logical counter increments.
//! On receiving a remote timestamp the clock advances past *both* the local
//! state and the remote timestamp, preserving the causal "happens-before"
//! relation.
//!
//! # Internal representation
//!
//! To guarantee uniqueness under concurrent `now()` calls without a mutex,
//! the mutable state is packed into a single `AtomicU64`:
//!
//! ```text
//!  bits 63..16  (48 bits)  wall-clock milliseconds since UNIX epoch
//!  bits 15..0   (16 bits)  logical counter (0..65535)
//! ```
//!
//! A single `compare_exchange` on this packed word atomically advances both
//! components, eliminating the race window that would exist between two
//! separate atomics.  The public [`HlcTimestamp`] uses nanoseconds for the
//! wall clock (ms * `1_000_000`) and a `u32` logical counter for headroom.

use std::sync::atomic::{AtomicU64, Ordering};

use crate::replication::shard::ShardId;

/// Number of bits allocated to the logical counter in the packed word.
const LOGICAL_BITS: u32 = 16;
/// Mask for extracting the logical counter from the packed word.
const LOGICAL_MASK: u64 = (1u64 << LOGICAL_BITS) - 1; // 0xFFFF
/// Maximum logical counter value before saturation.
const LOGICAL_MAX: u64 = LOGICAL_MASK;

/// Largest `wall_ms` value for which `wall_ms * 1_000_000` still fits in `u64`
/// without saturating, i.e. the supported time horizon.
///
/// `u64::MAX / 1_000_000 == 18_446_744_073_709` ms ≈ **year 2554**. For any
/// `wall_ms` at or below this bound the conversion to `wall_ns` is exact and
/// total ordering on [`HlcTimestamp`] is preserved. Past it, distinct `wall_ms`
/// values would all saturate to `u64::MAX` ns and collapse their ordering —
/// well beyond any realistic deployment lifetime, but documented so the bound
/// is explicit rather than implicit. The packed format itself reserves 48 bits
/// for `wall_ms` (good to ≈ year 10889), so the year-2554 ceiling comes purely
/// from the public nanosecond representation.
const MAX_WALL_MS: u64 = u64::MAX / 1_000_000;

/// Pack `(wall_ms, logical)` into a single `u64`.
const fn pack(wall_ms: u64, logical: u16) -> u64 {
    (wall_ms << LOGICAL_BITS) | (logical as u64)
}

/// Unpack a `u64` into `(wall_ms, logical)`.
const fn unpack(packed: u64) -> (u64, u16) {
    let wall_ms = packed >> LOGICAL_BITS;
    let logical = (packed & LOGICAL_MASK) as u16;
    (wall_ms, logical)
}

/// Convert a millisecond wall time to nanoseconds, clamping at the supported
/// horizon ([`MAX_WALL_MS`], ≈ year 2554).
///
/// Below the bound this is the exact `wall_ms * 1_000_000`. At or above it the
/// product would overflow `u64`; we clamp to `u64::MAX` deterministically so the
/// conversion is total. Distinct post-2554 wall times collapse to one value, but
/// that is documented and unreachable in any realistic deployment. The clamp is
/// shared by every constructor so all code paths agree on the boundary.
const fn wall_ms_to_ns(wall_ms: u64) -> u64 {
    if wall_ms > MAX_WALL_MS {
        u64::MAX
    } else {
        wall_ms * 1_000_000
    }
}

// ---------------------------------------------------------------------------
// HlcTimestamp
// ---------------------------------------------------------------------------

/// A point-in-time HLC timestamp.
///
/// Ordering: `(wall_ns, logical, node_id)` -- lexicographic.
/// Same wall-time events are ordered by logical counter;
/// ties within a node (impossible in practice) are broken by `node_id`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct HlcTimestamp {
    /// Wall-clock time in nanoseconds since UNIX epoch.
    ///
    /// Internally stored at millisecond granularity (bottom 6 decimal digits
    /// are always zero), but the nanosecond unit is preserved for API
    /// compatibility with other time sources.
    ///
    /// # Supported range
    ///
    /// Ordering is exact for wall times up to [`MAX_WALL_MS`] (≈ year 2554).
    /// Beyond that, the `wall_ms * 1_000_000` conversion saturates at
    /// `u64::MAX` and distinct future times would collapse to one value. Every
    /// constructor in this module clamps `wall_ms` to `MAX_WALL_MS` before the
    /// multiply, so within the supported range there is no silent ordering
    /// collapse; the clamp only takes effect for the (unreachable in practice)
    /// post-2554 horizon.
    pub wall_ns: u64,
    /// Logical counter -- advances when wall time does not.
    pub logical: u32,
    /// The node (`ShardId.0`) that produced this timestamp.
    pub node_id: u16,
}

impl HlcTimestamp {
    /// Create a zero-valued HLC timestamp (minimum value).
    #[must_use]
    pub const fn zero() -> Self {
        Self {
            wall_ns: 0,
            logical: 0,
            node_id: 0,
        }
    }

    /// Convert `wall_ns` to milliseconds (truncating sub-ms precision).
    const fn wall_ms(&self) -> u64 {
        self.wall_ns / 1_000_000
    }
}

impl PartialOrd for HlcTimestamp {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for HlcTimestamp {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        self.wall_ns
            .cmp(&other.wall_ns)
            .then(self.logical.cmp(&other.logical))
            .then(self.node_id.cmp(&other.node_id))
    }
}

// ---------------------------------------------------------------------------
// Hlc
// ---------------------------------------------------------------------------

/// Per-node Hybrid Logical Clock.
///
/// Thread-safe: `now()` and `update()` use a single-word atomic CAS loop
/// to advance the clock without a mutex.  Multiple threads can call `now()`
/// concurrently; all returned timestamps are guaranteed unique within the
/// node.
///
/// # Examples
///
/// ```
/// use tidaldb::replication::crdt::hlc::{Hlc, HlcTimestamp};
///
/// let clock = Hlc::new(0);
/// let t1 = clock.now();
/// let t2 = clock.now();
/// assert!(t2 >= t1);
/// ```
pub struct Hlc {
    node_id: u16,
    /// Packed `(wall_ms << 16 | logical)`.  A single atomic word ensures
    /// that both components are advanced atomically via `compare_exchange`.
    packed: AtomicU64,
}

impl Hlc {
    /// Create a new clock for the given node.
    #[must_use]
    pub const fn new(node_id: u16) -> Self {
        Self {
            node_id,
            packed: AtomicU64::new(0),
        }
    }

    /// Create a clock for a [`ShardId`].
    #[must_use]
    pub const fn for_shard(shard: ShardId) -> Self {
        Self::new(shard.0)
    }

    /// Current wall-clock time in milliseconds since UNIX epoch.
    fn wall_ms_now() -> u64 {
        std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap_or_default()
            .as_millis() as u64
    }

    /// Generate a new HLC timestamp for a local event.
    ///
    /// Advances the clock monotonically.  If the wall clock has not advanced
    /// since the last call, the logical counter increments.
    ///
    /// # Logical-counter overflow
    ///
    /// The logical counter is 16 bits, so it can record at most 65 536 distinct
    /// events within a single millisecond before it would saturate. Reaching
    /// that is practically unreachable, but a saturating bump would produce two
    /// *byte-identical* timestamps from the same node — silently breaking the
    /// per-node uniqueness guarantee that LWW conflict resolution depends on.
    /// To preserve strict monotonic uniqueness we instead carry the overflow
    /// into the wall component (standard HLC overflow handling): we advance
    /// `wall_ms` by one and reset the logical counter to zero. This nudges the
    /// clock at most 1 ms ahead of physical time per 65 536-event burst, which
    /// the next physical tick reconciles. The event is rare enough that we emit
    /// a `tracing::warn!` so it is observable if it ever fires in production.
    pub fn now(&self) -> HlcTimestamp {
        let wall = Self::wall_ms_now();
        loop {
            // Acquire: ensures we see the latest packed word written by any
            // thread that previously completed a CAS in this loop.
            let cur = self.packed.load(Ordering::Acquire);
            let (cur_wall, cur_logical) = unpack(cur);

            let (new_wall, new_logical, overflowed) = if wall > cur_wall {
                // Physical clock advanced -- reset the logical counter.
                (wall, 0u16, false)
            } else if u64::from(cur_logical) >= LOGICAL_MAX {
                // Physical clock did not advance AND the logical counter is
                // saturated. Bumping logical again would re-emit `cur`
                // verbatim. Carry into the wall component to stay strictly
                // monotonic and unique.
                (cur_wall.saturating_add(1), 0u16, true)
            } else {
                // Physical clock did not advance (same or regressed).
                // Stay at the same wall_ms, bump logical.
                (cur_wall, cur_logical + 1, false)
            };

            let new_packed = pack(new_wall, new_logical);

            // AcqRel on success: the write is visible to subsequent Acquire
            // loads.  Acquire on failure: re-read the latest value before
            // retry.
            if self
                .packed
                .compare_exchange(cur, new_packed, Ordering::AcqRel, Ordering::Acquire)
                .is_ok()
            {
                if overflowed {
                    tracing::warn!(
                        node_id = self.node_id,
                        wall_ms = new_wall,
                        "HLC logical counter saturated within a single millisecond; \
                         carried overflow into the wall component to preserve uniqueness"
                    );
                }
                return HlcTimestamp {
                    wall_ns: wall_ms_to_ns(new_wall),
                    logical: u32::from(new_logical),
                    node_id: self.node_id,
                };
            }
            // CAS failed -- another thread advanced the clock.  Retry.
        }
    }

    /// Update the clock on receiving a remote HLC timestamp.
    ///
    /// Advances the local clock to be causally after `remote`, ensuring
    /// that any subsequent `now()` call produces a timestamp strictly
    /// greater than `remote`.
    ///
    /// Returns the new local timestamp (which is strictly > `remote`).
    pub fn update(&self, remote: HlcTimestamp) -> HlcTimestamp {
        let wall = Self::wall_ms_now();
        let remote_ms = remote.wall_ms();
        // Truncate remote.logical to u16 (safe: if remote.logical > u16::MAX
        // we saturate, which still advances past it).
        let remote_logical = (remote.logical.min(u32::from(u16::MAX))) as u16;

        loop {
            let cur = self.packed.load(Ordering::Acquire);
            let (cur_wall, cur_logical) = unpack(cur);

            // pt = max(wall, remote_ms, cur_wall)
            // This ensures we never go backwards from any known timestamp.
            let pt = wall.max(remote_ms).max(cur_wall);

            // Compute the candidate (wall, logical). When `pt` does not advance
            // past the wall component the result is bound to (it equals
            // `cur_wall` and/or `remote_ms`), we must advance past the relevant
            // saturated logical counter. If that bump would overflow the 16-bit
            // counter we carry into the wall component exactly as `now()` does —
            // otherwise the saturating `.min(LOGICAL_MAX)` would re-emit a
            // byte-identical timestamp, silently breaking the per-node uniqueness
            // guarantee LWW conflict resolution depends on (CRITICAL fix).
            let (new_wall, new_logical, overflowed) = if pt > cur_wall && pt > remote_ms {
                // Physical clock strictly ahead of both -- reset logical.
                (pt, 0u16, false)
            } else {
                // pt equals cur_wall and/or remote_ms: advance past the
                // logical counter(s) at that wall_ms.
                let base = if pt == cur_wall && pt == remote_ms {
                    cur_logical.max(remote_logical)
                } else if pt == cur_wall {
                    cur_logical
                } else {
                    // pt == remote_ms (pt > cur_wall handled above)
                    remote_logical
                };
                let bumped = u64::from(base) + 1;
                if bumped > LOGICAL_MAX {
                    // Logical saturated at this wall_ms -- carry into the wall.
                    (pt.saturating_add(1), 0u16, true)
                } else {
                    (pt, bumped as u16, false)
                }
            };

            let new_packed = pack(new_wall, new_logical);

            if self
                .packed
                .compare_exchange(cur, new_packed, Ordering::AcqRel, Ordering::Acquire)
                .is_ok()
            {
                if overflowed {
                    tracing::warn!(
                        node_id = self.node_id,
                        wall_ms = new_wall,
                        "HLC logical counter saturated within a single millisecond on update(); \
                         carried overflow into the wall component to preserve uniqueness"
                    );
                }
                return HlcTimestamp {
                    wall_ns: wall_ms_to_ns(new_wall),
                    logical: u32::from(new_logical),
                    node_id: self.node_id,
                };
            }
        }
    }
}

// Compile-time assertion: Hlc is Send + Sync (all fields are atomics or
// Copy primitives).
#[allow(dead_code)]
const fn _assert_hlc_send_sync() {
    const fn assert_send_sync<T: Send + Sync>() {}
    assert_send_sync::<Hlc>();
}

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

#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
    // -- Pack/unpack round-trip (#11/#43: proptest over the bit-packing identity) --
    use proptest::prelude::*;

    use super::*;

    proptest! {
        // #43: a 5-case hand table cannot falsify a mid-range LOGICAL_BITS
        // shift/mask bug — every fixed case still round-trips. Quantify over the
        // whole packable domain instead. `wall_ms` must fit after a left-shift of
        // LOGICAL_BITS, so its valid range is `0..=(u64::MAX >> LOGICAL_BITS)`;
        // `logical` is any u16. unpack(pack(w, l)) == (w, l) is the law.
        #[test]
        fn pack_unpack_roundtrip(
            wall_ms in 0u64..=(u64::MAX >> LOGICAL_BITS),
            logical in any::<u16>(),
        ) {
            let packed = pack(wall_ms, logical);
            let (w, l) = unpack(packed);
            prop_assert_eq!(w, wall_ms, "wall_ms mismatch");
            prop_assert_eq!(l, logical, "logical mismatch");
        }
    }

    // -- HlcTimestamp ordering --

    #[test]
    fn hlc_timestamp_ordering_wall_ns() {
        let a = HlcTimestamp {
            wall_ns: 100,
            logical: 0,
            node_id: 0,
        };
        let b = HlcTimestamp {
            wall_ns: 200,
            logical: 0,
            node_id: 0,
        };
        assert!(a < b);
    }

    #[test]
    fn hlc_timestamp_ordering_logical() {
        let a = HlcTimestamp {
            wall_ns: 100,
            logical: 0,
            node_id: 0,
        };
        let b = HlcTimestamp {
            wall_ns: 100,
            logical: 1,
            node_id: 0,
        };
        assert!(a < b);
    }

    #[test]
    fn hlc_timestamp_ordering_node_id() {
        let a = HlcTimestamp {
            wall_ns: 100,
            logical: 0,
            node_id: 0,
        };
        let b = HlcTimestamp {
            wall_ns: 100,
            logical: 0,
            node_id: 1,
        };
        assert!(a < b);
    }

    // -- Hlc monotonicity --

    #[test]
    fn hlc_monotone_sequential() {
        let clock = Hlc::new(0);
        let mut prev = clock.now();
        for _ in 0..10_000 {
            let next = clock.now();
            assert!(next >= prev, "clock went backwards: {prev:?} > {next:?}");
            prev = next;
        }
    }

    // -- Hlc::update --

    #[test]
    fn hlc_update_advances_past_remote() {
        let clock = Hlc::new(0);
        // ~year 2096 in ms -- far enough in the future to exceed wall clock,
        // small enough that wall_ms * 1_000_000 does not overflow u64.
        let far_future_ms: u64 = 4_000_000_000_000;
        let remote = HlcTimestamp {
            wall_ns: far_future_ms * 1_000_000,
            logical: 99,
            node_id: 1,
        };
        let updated = clock.update(remote);
        assert!(
            updated > remote,
            "updated {updated:?} should be > remote {remote:?}"
        );
    }

    #[test]
    fn hlc_update_advances_past_local() {
        let clock = Hlc::new(0);
        // Advance the local clock first.
        let local = clock.now();
        // Remote is far in the past -- update should still advance past local.
        let remote = HlcTimestamp {
            wall_ns: 1_000_000, // 1 ms
            logical: 0,
            node_id: 1,
        };
        let updated = clock.update(remote);
        assert!(
            updated > local,
            "updated {updated:?} should be > local {local:?}"
        );
    }

    #[test]
    fn hlc_update_same_wall_as_remote_and_local() {
        let clock = Hlc::new(0);
        // ~year 2096 in ms -- far enough in the future to exceed wall clock.
        let far_future_ms: u64 = 4_000_000_000_000;
        let far_future_ns = far_future_ms * 1_000_000;
        let remote1 = HlcTimestamp {
            wall_ns: far_future_ns,
            logical: 10,
            node_id: 1,
        };
        let t1 = clock.update(remote1);
        assert!(t1 > remote1);

        // Now send another remote with the same wall_ns but lower logical.
        let remote2 = HlcTimestamp {
            wall_ns: far_future_ns,
            logical: 5,
            node_id: 2,
        };
        let t2 = clock.update(remote2);
        // t2 must be > both remote2 and t1.
        assert!(t2 > remote2);
        assert!(t2 > t1);
    }

    // -- Concurrent uniqueness --

    #[test]
    fn hlc_concurrent_uniqueness() {
        use std::sync::Arc;

        let clock = Arc::new(Hlc::new(0));
        let mut handles = vec![];
        for _ in 0..4 {
            let c = Arc::clone(&clock);
            handles.push(std::thread::spawn(move || {
                (0..250).map(|_| c.now()).collect::<Vec<_>>()
            }));
        }
        let mut all: Vec<HlcTimestamp> = handles
            .into_iter()
            .flat_map(|h| h.join().unwrap())
            .collect();
        all.sort();
        // All 1000 timestamps must be unique.
        for w in all.windows(2) {
            assert_ne!(w[0], w[1], "duplicate timestamp: {:?}", w[0]);
        }
    }

    // -- Logical-counter overflow (CRITICAL) --

    /// CRITICAL: when the logical counter is saturated at `LOGICAL_MAX` and the
    /// wall clock has not advanced, the next `now()` must be *strictly greater*
    /// than the saturated state — not a byte-identical duplicate. A duplicate
    /// would silently break the per-node uniqueness guarantee that LWW relies on.
    #[test]
    fn now_strictly_advances_when_logical_saturated() {
        let clock = Hlc::new(7);
        // Pin the packed state to a wall far in the future (so the real wall
        // clock cannot advance past it) with the logical counter already
        // saturated. This forces the no-advance + saturated branch in `now()`.
        let frozen_wall_ms: u64 = MAX_WALL_MS - 10; // well past real time, below the ns clamp
        let saturated = pack(frozen_wall_ms, LOGICAL_MAX as u16);
        clock.packed.store(saturated, Ordering::Release);

        let before = HlcTimestamp {
            wall_ns: wall_ms_to_ns(frozen_wall_ms),
            logical: LOGICAL_MAX as u32,
            node_id: 7,
        };

        let next = clock.now();
        assert!(
            next > before,
            "saturated-logical now() must strictly advance: before={before:?} next={next:?}"
        );
        // The overflow must have carried into the wall component and reset the
        // logical counter (standard HLC overflow handling).
        assert_eq!(next.logical, 0, "logical resets to 0 after carry");
        assert_eq!(
            next.wall_ns,
            wall_ms_to_ns(frozen_wall_ms + 1),
            "wall must advance by exactly one millisecond"
        );

        // A subsequent call must again be strictly greater (logical bumps from 0).
        let after = clock.now();
        assert!(
            after > next,
            "clock stays strictly monotonic after the carry"
        );
    }

    /// CRITICAL: `update()` must carry logical-counter overflow into the wall
    /// component exactly as `now()` does. When the local logical counter is
    /// saturated and the wall does not advance, a remote at the same `wall_ms`
    /// must not make `update()` return a byte-identical timestamp.
    #[test]
    fn update_strictly_advances_when_logical_saturated() {
        let clock = Hlc::new(7);
        let frozen_wall_ms: u64 = MAX_WALL_MS - 10; // past real time, below the ns clamp
        let saturated = pack(frozen_wall_ms, LOGICAL_MAX as u16);
        clock.packed.store(saturated, Ordering::Release);

        let before = HlcTimestamp {
            wall_ns: wall_ms_to_ns(frozen_wall_ms),
            logical: LOGICAL_MAX as u32,
            node_id: 7,
        };

        // A remote at the same (frozen) wall_ms with a small logical: pt resolves
        // to cur_wall, and advancing past the saturated local logical overflows.
        let remote = HlcTimestamp {
            wall_ns: wall_ms_to_ns(frozen_wall_ms),
            logical: 3,
            node_id: 99,
        };

        let next = clock.update(remote);
        assert!(
            next > before,
            "saturated-logical update() must strictly advance: before={before:?} next={next:?}"
        );
        assert!(
            next > remote,
            "update() must return a timestamp strictly greater than the remote"
        );
        // Overflow carried into the wall component, logical reset to 0.
        assert_eq!(next.logical, 0, "logical resets to 0 after carry");
        assert_eq!(
            next.wall_ns,
            wall_ms_to_ns(frozen_wall_ms + 1),
            "wall must advance by exactly one millisecond on update() overflow"
        );

        // The next update against the same remote must again be strictly greater.
        let after = clock.update(remote);
        assert!(
            after > next,
            "clock stays strictly monotonic after the carry"
        );
    }

    /// The supported-range bound: `wall_ms` at `MAX_WALL_MS` converts exactly,
    /// and anything beyond clamps to `u64::MAX` (documented horizon, no panic).
    #[test]
    fn wall_ms_to_ns_clamps_at_supported_horizon() {
        assert_eq!(wall_ms_to_ns(0), 0);
        assert_eq!(wall_ms_to_ns(1), 1_000_000);
        assert_eq!(wall_ms_to_ns(MAX_WALL_MS), MAX_WALL_MS * 1_000_000);
        // One past the bound would overflow the exact product → clamp.
        assert_eq!(wall_ms_to_ns(MAX_WALL_MS + 1), u64::MAX);
        assert_eq!(wall_ms_to_ns(u64::MAX), u64::MAX);
    }

    // -- Zero --

    #[test]
    fn hlc_zero_is_minimum() {
        let zero = HlcTimestamp::zero();
        let clock = Hlc::new(0);
        let t = clock.now();
        assert!(t > zero);
    }

    // -- for_shard --

    #[test]
    fn hlc_for_shard() {
        let shard = ShardId(42);
        let clock = Hlc::for_shard(shard);
        let t = clock.now();
        assert_eq!(t.node_id, 42);
    }

    // -- wall_ns is at ms granularity --

    #[test]
    fn wall_ns_is_ms_granularity() {
        let clock = Hlc::new(0);
        let t = clock.now();
        assert_eq!(
            t.wall_ns % 1_000_000,
            0,
            "wall_ns should be a multiple of 1_000_000 (ms precision)"
        );
    }
}
