//! Last-Writer-Wins Register CRDT.
//!
//! Resolves concurrent writes by HLC timestamp ordering. Ties broken by
//! `node_id` (higher 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`.
//!
//! # 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;

/// Last-Writer-Wins register with HLC timestamp.
///
/// Resolves concurrent writes by [`HlcTimestamp`] ordering:
/// - Higher `wall_ns` wins
/// - Same wall, higher `logical` wins
/// - Same wall + logical, higher `node_id` wins (deterministic tie-break)
///
/// `None` represents "not yet written."
///
/// # 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 + PartialEq> {
    value: Option<T>,
    timestamp: Option<HlcTimestamp>,
}

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

    /// Write a new value with the given HLC timestamp.
    ///
    /// Only advances the register if `ts > self.timestamp`.
    /// Writes at or before the current timestamp are silently discarded
    /// (they represent causally earlier events).
    pub fn write(&mut self, value: T, ts: HlcTimestamp) {
        if self.timestamp.is_none_or(|cur| ts > cur) {
            self.value = Some(value);
            self.timestamp = Some(ts);
        }
    }

    /// Merge another register into this one.
    ///
    /// The register with the higher HLC timestamp wins.
    /// If both are empty, the result is empty.
    /// If only one has a value, that value is taken.
    pub fn merge(&mut self, other: &Self) {
        if let Some(other_ts) = other.timestamp
            && self.timestamp.is_none_or(|cur| other_ts > cur)
        {
            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 + PartialEq> 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_is_discarded() {
        let mut reg: LWWRegister<u8> = LWWRegister::empty();
        reg.write(1, ts(100, 0, 0));
        reg.write(99, ts(100, 0, 0));
        // Equal is NOT greater, so the write is discarded.
        assert_eq!(reg.get(), Some(&1));
    }

    // ------------------------------------------------------------------
    // 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 super::*;
    use proptest::prelude::*;

    fn arb_hlc_timestamp() -> impl Strategy<Value = HlcTimestamp> {
        (0..=1_000_000u64, 0..=100u32, 0..=10u16).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
            (any::<u8>(), 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.clone();
            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));
        }
    }
}
