//! Last-Writer-Wins Register CRDT.
//!
//! Resolves concurrent writes by HLC timestamp ordering. Ties broken first by
//! `node_id` (folded into [`HlcTimestamp`] ordering), then — for the exact-same
//! full timestamp but a *different value* — by the value itself (larger `T`
//! wins). Used for hard negatives (hide/mute/block) which require LWW semantics
//! across distributed nodes.
//!
//! # CRDT Properties
//!
//! `merge` is commutative, associative, and idempotent -- these properties
//! are verified by property tests using `proptest`.
//!
//! # Why the value participates in the tie-break
//!
//! The HLC timestamp alone is *not* a total order over `(timestamp, value)`
//! pairs: two replicas can carry the same `HlcTimestamp` with different values
//! (a forged/corrupt peer register, or a future HLC change that weakens per-node
//! uniqueness). If the tie-break ignored the value, `merge(a, b)` would keep
//! whichever happened to be `self`, so `merge(a, b) != merge(b, a)` and the two
//! replicas would silently diverge — fatal for a state-based CRDT whose entire
//! job is order-independent convergence. Constraining `T: Ord` and breaking the
//! exact-timestamp tie on the larger value makes the decision a *total* order,
//! so merge is commutative for **every** reachable input, not just for honest
//! HLC-stamped writes (W18). This mirrors `CrdtSignalState::merge`, which
//! appends the score to its key tuple `(last_update_ns, score)` for the same
//! reason.
//!
//! # Design Notes
//!
//! The value slot is `Option<T>` where `None` means "not yet written."
//! This is distinct from a register that has been written with a value and
//! then cleared -- clearing would require a write with a sentinel value,
//! not setting the register back to `None`.

use super::hlc::HlcTimestamp;

/// Decide whether an incoming value should overwrite the current one under
/// last-writer-wins semantics, given their ordering keys.
///
/// Returns `true` when there is no current value, or when `incoming` compares
/// strictly greater than `current`. Equality keeps the current value (a write
/// at the same key is *not* newer), which makes merge idempotent and
/// commutative when the key already encodes a total, deterministic tiebreak.
///
/// This is the single place the "keep the larger key" decision lives so the two
/// LWW call sites cannot drift:
/// - [`LWWRegister::merge`] uses `K = (HlcTimestamp, &T)`: the `HlcTimestamp`
///   ordering folds in `node_id`, and the value is appended as the final
///   tiebreak so two registers with the same timestamp but different values
///   resolve identically regardless of merge order (W18).
/// - [`super::signal_state::CrdtSignalState::merge`] uses
///   `K = (last_update_ns, score)`, where the score is the tiebreak appended
///   after the timestamp so equal-timestamp registers resolve deterministically.
pub(crate) fn lww_other_wins<K: PartialOrd>(current: Option<K>, incoming: &K) -> bool {
    current.is_none_or(|cur| *incoming > cur)
}

/// Last-Writer-Wins register with HLC timestamp.
///
/// Resolves concurrent writes by `(HlcTimestamp, value)` ordering:
/// - Higher `wall_ns` wins
/// - Same wall, higher `logical` wins
/// - Same wall + logical, higher `node_id` wins
/// - Same full timestamp, larger value wins (the total tie-break that makes
///   merge commutative for *every* input — see the module docs and W18)
///
/// `None` represents "not yet written."
///
/// `T: Ord` is required so the exact-timestamp tie-break is deterministic; the
/// derived `PartialOrd`/`Ord` on `HlcTimestamp` already folds in `node_id`.
///
/// # Properties
///
/// - **Commutative:** `merge(A, B) == merge(B, A)`
/// - **Associative:** `merge(A, merge(B, C)) == merge(merge(A, B), C)`
/// - **Idempotent:** `merge(A, A) == A`
///
/// # Examples
///
/// ```
/// use tidaldb::replication::crdt::lww_register::LWWRegister;
/// use tidaldb::replication::crdt::hlc::HlcTimestamp;
///
/// let mut reg: LWWRegister<u8> = LWWRegister::empty();
/// let ts = HlcTimestamp { wall_ns: 1000, logical: 0, node_id: 0 };
/// reg.write(42u8, ts);
/// assert_eq!(reg.get(), Some(&42u8));
/// ```
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct LWWRegister<T: Clone + Ord> {
    value: Option<T>,
    timestamp: Option<HlcTimestamp>,
}

impl<T: Clone + Ord> LWWRegister<T> {
    /// Create an empty register (no value written yet).
    #[must_use]
    pub const fn empty() -> Self {
        Self {
            value: None,
            timestamp: None,
        }
    }

    /// The current `(timestamp, value)` ordering key, if a value is present.
    ///
    /// Folding the value into the key after the timestamp is what makes the LWW
    /// decision a *total* order: two registers with the same `HlcTimestamp` but
    /// different values resolve identically regardless of merge order (W18).
    const fn order_key(&self) -> Option<(HlcTimestamp, &T)> {
        match (self.timestamp, self.value.as_ref()) {
            (Some(ts), Some(v)) => Some((ts, v)),
            _ => None,
        }
    }

    /// Write a new value with the given HLC timestamp.
    ///
    /// Advances the register only if `(ts, value)` compares strictly greater
    /// than the current `(timestamp, value)` key. Writes at or before the
    /// current key are silently discarded (causally earlier, or an exact tie the
    /// total order already resolves toward the current value).
    ///
    /// Routes through [`lww_other_wins`] so the local-write and merge paths
    /// share one definition of "is the incoming key newer" and cannot drift.
    pub fn write(&mut self, value: T, ts: HlcTimestamp) {
        // Compute the decision in a scope whose borrow of `value` ends before the
        // move below, so the conditional move type-checks.
        let wins = lww_other_wins(self.order_key(), &(ts, &value));
        if wins {
            self.value = Some(value);
            self.timestamp = Some(ts);
        }
    }

    /// Merge another register into this one.
    ///
    /// The register with the higher `(HlcTimestamp, value)` key wins.
    /// If both are empty, the result is empty.
    /// If only one has a value, that value is taken. The value tie-break makes
    /// this commutative even when two registers share the exact same timestamp
    /// (W18).
    pub fn merge(&mut self, other: &Self) {
        if let Some(other_key) = other.order_key()
            && lww_other_wins(self.order_key(), &other_key)
        {
            self.value.clone_from(&other.value);
            self.timestamp = other.timestamp;
        }
    }

    /// Current value of the register.
    #[must_use]
    pub const fn get(&self) -> Option<&T> {
        self.value.as_ref()
    }

    /// The HLC timestamp of the last accepted write.
    #[must_use]
    pub const fn timestamp(&self) -> Option<HlcTimestamp> {
        self.timestamp
    }

    /// Returns `true` if no value has been written.
    #[must_use]
    pub const fn is_empty(&self) -> bool {
        self.value.is_none()
    }
}

impl<T: Clone + Ord> Default for LWWRegister<T> {
    fn default() -> Self {
        Self::empty()
    }
}

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

    fn ts(wall_ns: u64, logical: u32, node_id: u16) -> HlcTimestamp {
        HlcTimestamp {
            wall_ns,
            logical,
            node_id,
        }
    }

    // ------------------------------------------------------------------
    // Basic write semantics
    // ------------------------------------------------------------------

    #[test]
    fn empty_register_has_no_value() {
        let reg: LWWRegister<u8> = LWWRegister::empty();
        assert!(reg.is_empty());
        assert_eq!(reg.get(), None);
        assert_eq!(reg.timestamp(), None);
    }

    #[test]
    fn write_to_empty_register_succeeds() {
        let mut reg: LWWRegister<u8> = LWWRegister::empty();
        reg.write(42, ts(100, 0, 0));
        assert_eq!(reg.get(), Some(&42));
        assert_eq!(reg.timestamp(), Some(ts(100, 0, 0)));
        assert!(!reg.is_empty());
    }

    #[test]
    fn write_with_higher_wall_overwrites() {
        let mut reg: LWWRegister<u8> = LWWRegister::empty();
        reg.write(1, ts(100, 0, 0));
        reg.write(2, ts(200, 0, 0));
        assert_eq!(reg.get(), Some(&2));
    }

    #[test]
    fn write_with_lower_wall_is_discarded() {
        let mut reg: LWWRegister<u8> = LWWRegister::empty();
        reg.write(2, ts(200, 0, 0));
        reg.write(1, ts(100, 0, 0));
        assert_eq!(reg.get(), Some(&2));
    }

    #[test]
    fn write_with_same_wall_higher_logical_overwrites() {
        let mut reg: LWWRegister<u8> = LWWRegister::empty();
        reg.write(1, ts(100, 0, 0));
        reg.write(2, ts(100, 1, 0));
        assert_eq!(reg.get(), Some(&2));
    }

    #[test]
    fn write_with_same_wall_lower_logical_is_discarded() {
        let mut reg: LWWRegister<u8> = LWWRegister::empty();
        reg.write(2, ts(100, 5, 0));
        reg.write(1, ts(100, 3, 0));
        assert_eq!(reg.get(), Some(&2));
    }

    #[test]
    fn write_with_same_wall_same_logical_higher_node_overwrites() {
        let mut reg: LWWRegister<u8> = LWWRegister::empty();
        reg.write(1, ts(100, 0, 0));
        reg.write(2, ts(100, 0, 1));
        assert_eq!(reg.get(), Some(&2));
    }

    #[test]
    fn write_with_equal_timestamp_keeps_larger_value() {
        // The exact-timestamp tie now breaks on the value (W18): the larger
        // value wins so the decision is a total order and merge is commutative
        // even for same-timestamp / different-value inputs.
        let mut reg: LWWRegister<u8> = LWWRegister::empty();
        reg.write(1, ts(100, 0, 0));
        reg.write(99, ts(100, 0, 0)); // same ts, larger value -> wins
        assert_eq!(reg.get(), Some(&99));

        // And a same-timestamp SMALLER value is discarded (current value larger).
        let mut reg2: LWWRegister<u8> = LWWRegister::empty();
        reg2.write(99, ts(100, 0, 0));
        reg2.write(1, ts(100, 0, 0)); // same ts, smaller value -> discarded
        assert_eq!(reg2.get(), Some(&99));

        // A same-timestamp, same-value write is a true no-op (idempotent).
        let mut reg3: LWWRegister<u8> = LWWRegister::empty();
        reg3.write(42, ts(100, 0, 0));
        reg3.write(42, ts(100, 0, 0));
        assert_eq!(reg3.get(), Some(&42));
    }

    /// W18: merge must be commutative even when two registers carry the SAME
    /// full `HlcTimestamp` but DIFFERENT values — the case a node_id-only
    /// tie-break left order-dependent. The value tie-break (larger wins) makes
    /// `merge(a, b) == merge(b, a)` for this input.
    #[test]
    fn merge_commutative_on_equal_timestamp_differing_values() {
        let same_ts = ts(100, 0, 0);
        let mut a: LWWRegister<u8> = LWWRegister::empty();
        a.write(1, same_ts);
        let mut b: LWWRegister<u8> = LWWRegister::empty();
        b.write(2, same_ts);

        let mut ab = a.clone();
        ab.merge(&b);
        let mut ba = b.clone();
        ba.merge(&a);

        assert_eq!(
            ab.get(),
            ba.get(),
            "merge must be commutative on a ts collision"
        );
        assert_eq!(
            ab.get(),
            Some(&2),
            "larger value wins the exact-timestamp tie"
        );
        assert_eq!(ab.timestamp(), ba.timestamp());
    }

    /// `write` and `merge` must make the identical accept/reject decision for
    /// the same incoming timestamp: both now route through `lww_other_wins`, so
    /// a direct write and a merge from a one-value register cannot diverge.
    #[test]
    fn write_and_merge_agree_on_lww_decision() {
        for (cur, incoming) in [
            (ts(100, 0, 0), ts(200, 0, 0)), // incoming newer -> accept
            (ts(200, 0, 0), ts(100, 0, 0)), // incoming older -> reject
            // equal timestamp: resolved by the value tie-break (incoming value 2
            // > current value 1 -> accept); write and merge must agree either way.
            (ts(100, 0, 0), ts(100, 0, 0)),
            (ts(100, 0, 0), ts(100, 0, 1)), // node-id tiebreak -> accept
        ] {
            let mut via_write: LWWRegister<u8> = LWWRegister::empty();
            via_write.write(1, cur);
            via_write.write(2, incoming);

            let mut via_merge: LWWRegister<u8> = LWWRegister::empty();
            via_merge.write(1, cur);
            let mut incoming_reg: LWWRegister<u8> = LWWRegister::empty();
            incoming_reg.write(2, incoming);
            via_merge.merge(&incoming_reg);

            assert_eq!(
                via_write.get(),
                via_merge.get(),
                "write and merge disagreed for cur={cur:?} incoming={incoming:?}"
            );
            assert_eq!(via_write.timestamp(), via_merge.timestamp());
        }
    }

    // ------------------------------------------------------------------
    // Merge semantics
    // ------------------------------------------------------------------

    #[test]
    fn merge_empty_into_empty() {
        let mut a: LWWRegister<u8> = LWWRegister::empty();
        let b: LWWRegister<u8> = LWWRegister::empty();
        a.merge(&b);
        assert!(a.is_empty());
    }

    #[test]
    fn merge_value_into_empty() {
        let mut a: LWWRegister<u8> = LWWRegister::empty();
        let mut b: LWWRegister<u8> = LWWRegister::empty();
        b.write(42, ts(500, 0, 0));
        a.merge(&b);
        assert_eq!(a.get(), Some(&42));
    }

    #[test]
    fn merge_empty_into_value() {
        let mut a: LWWRegister<u8> = LWWRegister::empty();
        a.write(42, ts(500, 0, 0));
        let b: LWWRegister<u8> = LWWRegister::empty();
        a.merge(&b);
        assert_eq!(a.get(), Some(&42));
    }

    #[test]
    fn merge_takes_higher_timestamp() {
        let mut a: LWWRegister<u8> = LWWRegister::empty();
        a.write(1, ts(100, 0, 0));
        let mut b: LWWRegister<u8> = LWWRegister::empty();
        b.write(2, ts(200, 0, 0));
        a.merge(&b);
        assert_eq!(a.get(), Some(&2));
    }

    #[test]
    fn merge_keeps_self_when_higher() {
        let mut a: LWWRegister<u8> = LWWRegister::empty();
        a.write(1, ts(200, 0, 0));
        let mut b: LWWRegister<u8> = LWWRegister::empty();
        b.write(2, ts(100, 0, 0));
        a.merge(&b);
        assert_eq!(a.get(), Some(&1));
    }

    #[test]
    fn merge_tie_break_by_node_id() {
        // Same wall + logical, higher node_id wins.
        let mut a: LWWRegister<u8> = LWWRegister::empty();
        a.write(1, ts(100, 0, 0));
        let mut b: LWWRegister<u8> = LWWRegister::empty();
        b.write(2, ts(100, 0, 1)); // higher node_id

        let mut merged = a.clone();
        merged.merge(&b);
        assert_eq!(merged.get(), Some(&2));
    }

    // ------------------------------------------------------------------
    // CRDT properties (manual verification)
    // ------------------------------------------------------------------

    #[test]
    fn merge_commutative() {
        let mut a: LWWRegister<u8> = LWWRegister::empty();
        a.write(1, ts(100, 0, 0));
        let mut b: LWWRegister<u8> = LWWRegister::empty();
        b.write(2, ts(200, 0, 1));

        let mut ab = a.clone();
        ab.merge(&b);
        let mut ba = b.clone();
        ba.merge(&a);

        assert_eq!(ab.get(), ba.get());
        assert_eq!(ab.timestamp(), ba.timestamp());
    }

    #[test]
    fn merge_associative() {
        let mut a: LWWRegister<u8> = LWWRegister::empty();
        a.write(1, ts(100, 0, 0));
        let mut b: LWWRegister<u8> = LWWRegister::empty();
        b.write(2, ts(200, 0, 1));
        let mut c: LWWRegister<u8> = LWWRegister::empty();
        c.write(3, ts(150, 0, 2));

        // (a merge b) merge c
        let mut ab_c = a.clone();
        ab_c.merge(&b);
        ab_c.merge(&c);

        // a merge (b merge c)
        let mut bc = b.clone();
        bc.merge(&c);
        let mut a_bc = a.clone();
        a_bc.merge(&bc);

        assert_eq!(ab_c.get(), a_bc.get());
        assert_eq!(ab_c.timestamp(), a_bc.timestamp());
    }

    #[test]
    fn merge_idempotent() {
        let mut reg: LWWRegister<u8> = LWWRegister::empty();
        reg.write(42, ts(100, 0, 0));
        let snapshot = reg.clone();
        reg.merge(&snapshot);
        assert_eq!(reg.get(), Some(&42));
        assert_eq!(reg.timestamp(), Some(ts(100, 0, 0)));
    }

    // ------------------------------------------------------------------
    // Domain-specific: hard negatives
    // ------------------------------------------------------------------

    /// Simulates the hard negative use case: hide has higher HLC, should
    /// survive merge against a concurrent unhide with lower HLC.
    #[test]
    fn hard_negative_hide_wins_with_higher_hlc() {
        let ts_hide = ts(1000, 0, 0);
        let ts_unhide = ts(500, 0, 1);

        let mut local: LWWRegister<&str> = LWWRegister::empty();
        local.write("hide", ts_hide);

        let mut remote: LWWRegister<&str> = LWWRegister::empty();
        remote.write("unhide", ts_unhide);

        local.merge(&remote);
        assert_eq!(local.get(), Some(&"hide"));
    }

    /// Simulates the reverse: unhide at later HLC overrides an earlier hide.
    #[test]
    fn hard_negative_unhide_wins_with_higher_hlc() {
        let ts_hide = ts(500, 0, 0);
        let ts_unhide = ts(1000, 0, 1);

        let mut local: LWWRegister<&str> = LWWRegister::empty();
        local.write("hide", ts_hide);

        let mut remote: LWWRegister<&str> = LWWRegister::empty();
        remote.write("unhide", ts_unhide);

        local.merge(&remote);
        assert_eq!(local.get(), Some(&"unhide"));
    }

    /// When hide and unhide happen at the same wall time + logical, the
    /// higher `node_id` wins deterministically.
    #[test]
    fn hard_negative_concurrent_same_wall_resolved_by_node_id() {
        let ts_hide = ts(1000, 0, 0);
        let ts_unhide = ts(1000, 0, 5); // higher node_id

        let mut local: LWWRegister<&str> = LWWRegister::empty();
        local.write("hide", ts_hide);

        let mut remote: LWWRegister<&str> = LWWRegister::empty();
        remote.write("unhide", ts_unhide);

        local.merge(&remote);
        assert_eq!(
            local.get(),
            Some(&"unhide"),
            "higher node_id (5 > 0) should win at same wall+logical"
        );
    }

    // ------------------------------------------------------------------
    // Default trait
    // ------------------------------------------------------------------

    #[test]
    fn default_is_empty() {
        let reg: LWWRegister<u8> = LWWRegister::default();
        assert!(reg.is_empty());
    }
}

#[cfg(test)]
mod property_tests {
    use proptest::prelude::*;

    use super::*;

    /// Deliberately tiny timestamp domain so the generator collides on the exact
    /// `(wall, logical, node)` tuple often — that collision, paired with the
    /// independent value below, is what exercises the equal-timestamp /
    /// different-value tie-break the commutativity law depends on (W18). A wide
    /// domain would essentially never hit a collision and the law would pass by
    /// sampling luck rather than by the code being commutative.
    fn arb_hlc_timestamp() -> impl Strategy<Value = HlcTimestamp> {
        (0..=3u64, 0..=2u32, 0..=2u16).prop_map(|(w, l, n)| HlcTimestamp {
            wall_ns: w,
            logical: l,
            node_id: n,
        })
    }

    fn arb_register() -> impl Strategy<Value = LWWRegister<u8>> {
        prop_oneof![
            // Empty register
            Just(LWWRegister::empty()),
            // Register with a value. The value domain is intentionally tiny (and
            // overlapping the timestamp domain) so two registers frequently carry
            // the SAME timestamp with a DIFFERENT value, the exact input that
            // falsifies a non-total tie-break.
            (0..=3u8, arb_hlc_timestamp()).prop_map(|(v, ts)| {
                let mut r = LWWRegister::empty();
                r.write(v, ts);
                r
            }),
        ]
    }

    proptest! {
        /// merge(A, B) == merge(B, A) for all registers.
        #[test]
        fn merge_is_commutative(a in arb_register(), b in arb_register()) {
            let mut ab = a.clone();
            ab.merge(&b);
            let mut ba = b.clone();
            ba.merge(&a);
            prop_assert_eq!(ab.get(), ba.get());
            prop_assert_eq!(ab.timestamp(), ba.timestamp());
        }

        /// merge(merge(A, B), C) == merge(A, merge(B, C)) for all registers.
        #[test]
        fn merge_is_associative(
            a in arb_register(),
            b in arb_register(),
            c in arb_register(),
        ) {
            let mut ab_c = a.clone();
            ab_c.merge(&b);
            ab_c.merge(&c);

            let mut bc = b.clone();
            bc.merge(&c);
            let mut a_bc = a;
            a_bc.merge(&bc);

            prop_assert_eq!(ab_c.get(), a_bc.get());
            prop_assert_eq!(ab_c.timestamp(), a_bc.timestamp());
        }

        /// merge(A, A) == A for all registers.
        #[test]
        fn merge_is_idempotent(a in arb_register()) {
            let mut merged = a.clone();
            merged.merge(&a);
            prop_assert_eq!(merged.get(), a.get());
            prop_assert_eq!(merged.timestamp(), a.timestamp());
        }

        /// write with strictly higher timestamp always advances the register.
        #[test]
        fn write_with_higher_ts_always_wins(
            v1 in any::<u8>(),
            v2 in any::<u8>(),
            ts1 in arb_hlc_timestamp(),
        ) {
            // Construct ts2 strictly greater than ts1.
            let ts2 = HlcTimestamp {
                wall_ns: ts1.wall_ns + 1,
                logical: ts1.logical,
                node_id: ts1.node_id,
            };
            let mut reg = LWWRegister::empty();
            reg.write(v1, ts1);
            reg.write(v2, ts2);
            prop_assert_eq!(reg.get(), Some(&v2));
            prop_assert_eq!(reg.timestamp(), Some(ts2));
        }

        /// write with strictly lower timestamp never changes the register.
        #[test]
        fn write_with_lower_ts_never_wins(
            v1 in any::<u8>(),
            v2 in any::<u8>(),
            ts2 in arb_hlc_timestamp(),
        ) {
            // Construct ts1 strictly greater than ts2.
            let ts1 = HlcTimestamp {
                wall_ns: ts2.wall_ns + 1,
                logical: ts2.logical,
                node_id: ts2.node_id,
            };
            let mut reg = LWWRegister::empty();
            reg.write(v1, ts1);
            reg.write(v2, ts2);
            prop_assert_eq!(reg.get(), Some(&v1));
            prop_assert_eq!(reg.timestamp(), Some(ts1));
        }
    }
}
