//! Conversions between protobuf types and internal types

use crate::proto;
use chainfire_types::kv::KvEntry;
use chainfire_types::watch::{WatchEvent, WatchEventType, WatchRequest as InternalWatchRequest};
use chainfire_types::Revision;

/// Convert internal KvEntry to proto KeyValue
impl From<KvEntry> for proto::KeyValue {
    fn from(entry: KvEntry) -> Self {
        Self {
            key: entry.key,
            value: entry.value,
            create_revision: entry.create_revision as i64,
            mod_revision: entry.mod_revision as i64,
            version: entry.version as i64,
            lease: entry.lease_id.unwrap_or(0),
        }
    }
}

/// Convert proto KeyValue to internal KvEntry
impl From<proto::KeyValue> for KvEntry {
    fn from(kv: proto::KeyValue) -> Self {
        Self {
            key: kv.key,
            value: kv.value,
            create_revision: kv.create_revision as u64,
            mod_revision: kv.mod_revision as u64,
            version: kv.version as u64,
            lease_id: if kv.lease != 0 { Some(kv.lease) } else { None },
        }
    }
}

/// Convert internal WatchEvent to proto Event
impl From<WatchEvent> for proto::Event {
    fn from(event: WatchEvent) -> Self {
        Self {
            r#type: match event.event_type {
                WatchEventType::Put => proto::event::EventType::Put as i32,
                WatchEventType::Delete => proto::event::EventType::Delete as i32,
            },
            kv: Some(event.kv.into()),
            prev_kv: event.prev_kv.map(Into::into),
        }
    }
}

/// Convert proto WatchCreateRequest to internal WatchRequest
impl From<proto::WatchCreateRequest> for InternalWatchRequest {
    fn from(req: proto::WatchCreateRequest) -> Self {
        Self {
            watch_id: req.watch_id,
            key: req.key,
            range_end: if req.range_end.is_empty() {
                None
            } else {
                Some(req.range_end)
            },
            start_revision: if req.start_revision > 0 {
                Some(req.start_revision as Revision)
            } else {
                None
            },
            prev_kv: req.prev_kv,
            progress_notify: req.progress_notify,
        }
    }
}

/// Create a response header
pub fn make_header(
    cluster_id: u64,
    member_id: u64,
    revision: Revision,
    raft_term: u64,
) -> proto::ResponseHeader {
    proto::ResponseHeader {
        cluster_id,
        member_id,
        revision: revision as i64,
        raft_term,
    }
}

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

    #[test]
    fn test_kv_entry_conversion() {
        let entry = KvEntry::new(b"key".to_vec(), b"value".to_vec(), 1);
        let proto_kv: proto::KeyValue = entry.clone().into();

        assert_eq!(proto_kv.key, b"key");
        assert_eq!(proto_kv.value, b"value");
        assert_eq!(proto_kv.create_revision, 1);

        let back: KvEntry = proto_kv.into();
        assert_eq!(back.key, entry.key);
        assert_eq!(back.value, entry.value);
    }

    #[test]
    fn test_watch_event_conversion() {
        let kv = KvEntry::new(b"key".to_vec(), b"value".to_vec(), 1);
        let event = WatchEvent::put(kv, None);

        let proto_event: proto::Event = event.into();
        assert_eq!(proto_event.r#type, proto::event::EventType::Put as i32);
    }
}
