//! Wire-protocol types for the `/crdt-sync` WebSocket protocol.

use serde::{Deserialize, Serialize};

// ── Wire protocol types ─────────────────────────────────────────────

/// Auth handshake: challenge sent by the listener to the connector.
#[derive(Serialize, Deserialize, Debug)]
pub(super) struct ChallengeMessage {
    pub(super) r#type: String,
    pub(super) nonce: String,
}

/// Auth handshake: auth reply sent by the connector to the listener.
#[derive(Serialize, Deserialize, Debug)]
pub(super) struct AuthMessage {
    pub(super) r#type: String,
    pub(super) pubkey_hex: String,
    pub(super) signature_hex: String,
}

#[derive(Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub(crate) enum SyncMessage {
    /// Bulk state dump sent on connect (v1) or delta ops after clock exchange (v2).
    Bulk { ops: Vec<String> },
    /// A single new op.
    Op { op: String },
    /// Vector clock exchanged on connect (v2 protocol).
    ///
    /// Each entry maps a node's hex-encoded Ed25519 public key to the count of
    /// ops received from that node.  The receiving side computes the delta via
    /// [`crdt_state::ops_since`] and sends only the missing ops.
    Clock {
        clock: std::collections::HashMap<String, u64>,
    },
    /// Signals that the bulk-delta phase is complete; the sender is ready for
    /// real-time op streaming.  Locally-generated ops are buffered until the
    /// peer's `Ready` is received, then flushed in-order.
    Ready,
}

/// Crate-visible re-export of `SyncMessage` for backwards-compatibility testing.
///
/// Used by `crdt_snapshot` tests to verify that snapshot messages are NOT
/// parseable as legacy `SyncMessage` variants — confirming that old peers
/// will gracefully reject them.
#[cfg(test)]
pub(crate) type SyncMessagePublic = SyncMessage;

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

    #[test]
    fn sync_message_bulk_serialization_roundtrip() {
        let msg = SyncMessage::Bulk {
            ops: vec!["op1".to_string(), "op2".to_string()],
        };
        let json = serde_json::to_string(&msg).unwrap();
        assert!(json.contains(r#""type":"bulk""#));
        let deserialized: SyncMessage = serde_json::from_str(&json).unwrap();
        match deserialized {
            SyncMessage::Bulk { ops } => {
                assert_eq!(ops.len(), 2);
                assert_eq!(ops[0], "op1");
                assert_eq!(ops[1], "op2");
            }
            _ => panic!("Expected Bulk"),
        }
    }

    #[test]
    fn sync_message_op_serialization_roundtrip() {
        let msg = SyncMessage::Op {
            op: r#"{"inner":{}}"#.to_string(),
        };
        let json = serde_json::to_string(&msg).unwrap();
        assert!(json.contains(r#""type":"op""#));
        let deserialized: SyncMessage = serde_json::from_str(&json).unwrap();
        match deserialized {
            SyncMessage::Op { op } => {
                assert_eq!(op, r#"{"inner":{}}"#);
            }
            _ => panic!("Expected Op"),
        }
    }

    #[test]
    fn sync_message_bulk_empty_ops() {
        let msg = SyncMessage::Bulk { ops: vec![] };
        let json = serde_json::to_string(&msg).unwrap();
        let deserialized: SyncMessage = serde_json::from_str(&json).unwrap();
        match deserialized {
            SyncMessage::Bulk { ops } => assert!(ops.is_empty()),
            _ => panic!("Expected Bulk"),
        }
    }

    #[test]
    fn sync_message_clock_serialization_roundtrip() {
        let mut clock = std::collections::HashMap::new();
        clock.insert("aabbcc00".to_string(), 42u64);
        clock.insert("ddeeff11".to_string(), 7u64);

        let msg = SyncMessage::Clock { clock };
        let json = serde_json::to_string(&msg).unwrap();
        assert!(json.contains(r#""type":"clock""#));

        let deserialized: SyncMessage = serde_json::from_str(&json).unwrap();
        match deserialized {
            SyncMessage::Clock { clock } => {
                assert_eq!(clock["aabbcc00"], 42);
                assert_eq!(clock["ddeeff11"], 7);
            }
            _ => panic!("Expected Clock"),
        }
    }

    #[test]
    fn sync_message_clock_empty() {
        let msg = SyncMessage::Clock {
            clock: std::collections::HashMap::new(),
        };
        let json = serde_json::to_string(&msg).unwrap();
        let deserialized: SyncMessage = serde_json::from_str(&json).unwrap();
        match deserialized {
            SyncMessage::Clock { clock } => assert!(clock.is_empty()),
            _ => panic!("Expected Clock"),
        }
    }

    #[test]
    fn sync_message_ready_serialization_roundtrip() {
        let msg = SyncMessage::Ready;
        let json = serde_json::to_string(&msg).unwrap();
        assert_eq!(json, r#"{"type":"ready"}"#);
        let deserialized: SyncMessage = serde_json::from_str(&json).unwrap();
        assert!(matches!(deserialized, SyncMessage::Ready));
    }
}
