//! PaxosServiceMessage base trait and utilities
//!
//! This module provides a unified encoding/decoding framework for messages
//! that inherit from PaxosServiceMessage in Ceph. These messages include
//! common paxos fields that must be encoded/decoded consistently.

use crate::Denc;
use crate::monclient::error::Result;
use bytes::{Bytes, BytesMut};

/// Common fields for all PaxosServiceMessage types
///
/// These fields are encoded at the beginning of every PaxosServiceMessage
/// payload using the paxos_encode() method in C++.
#[derive(Debug, Clone)]
pub struct PaxosFields {
    /// Version number for the paxos service
    pub version: u64,
    /// Deprecated session monitor ID (always -1)
    pub deprecated_session_mon: i16,
    /// Deprecated session monitor transaction ID (always 0)
    pub deprecated_session_mon_tid: u64,
}

impl Default for PaxosFields {
    fn default() -> Self {
        Self::new()
    }
}

impl PaxosFields {
    /// Create new PaxosFields with default values
    pub fn new() -> Self {
        Self {
            version: 0,
            deprecated_session_mon: -1,
            deprecated_session_mon_tid: 0,
        }
    }

    /// Create PaxosFields with a specific version
    pub fn with_version(version: u64) -> Self {
        Self {
            version,
            deprecated_session_mon: -1,
            deprecated_session_mon_tid: 0,
        }
    }

    /// Encode paxos fields to buffer (paxos_encode in C++)
    pub fn encode(&self, buf: &mut BytesMut) -> crate::monclient::error::Result<()> {
        self.version.encode(buf, 0)?;
        self.deprecated_session_mon.encode(buf, 0)?;
        self.deprecated_session_mon_tid.encode(buf, 0)?;
        Ok(())
    }

    /// Decode paxos fields from buffer (paxos_decode in C++)
    pub fn decode(data: &mut &[u8]) -> Result<Self> {
        let version = u64::decode(data, 0)?;
        let deprecated_session_mon = i16::decode(data, 0)?;
        let deprecated_session_mon_tid = u64::decode(data, 0)?;

        Ok(Self {
            version,
            deprecated_session_mon,
            deprecated_session_mon_tid,
        })
    }

    /// Size of encoded paxos fields in bytes: version(u64) + deprecated_session_mon(i16) + deprecated_session_mon_tid(u64)
    pub const fn encoded_size() -> usize {
        std::mem::size_of::<u64>() + std::mem::size_of::<i16>() + std::mem::size_of::<u64>()
    }
}

/// Trait for messages that inherit from PaxosServiceMessage
///
/// This trait provides a unified interface for encoding/decoding messages
/// that include the common paxos fields. Implementations should:
/// 1. Encode paxos fields first using encode_paxos()
/// 2. Encode message-specific fields
/// 3. Decode paxos fields first using decode_paxos()
/// 4. Decode message-specific fields
pub trait PaxosServiceMessage: Sized {
    /// Get the paxos fields for this message
    fn paxos_fields(&self) -> &PaxosFields;

    /// Get mutable paxos fields for this message
    #[allow(dead_code)]
    fn paxos_fields_mut(&mut self) -> &mut PaxosFields;

    /// Encode the message-specific fields (after paxos fields)
    fn encode_message(&self, buf: &mut BytesMut) -> Result<()>;

    /// Decode the message-specific fields (after paxos fields)
    fn decode_message(paxos: PaxosFields, data: &mut &[u8]) -> Result<Self>;

    /// Provide a size hint for message-specific fields (optional optimization)
    fn encoded_size_hint(&self) -> Option<usize> {
        None
    }

    /// Encode the complete message (paxos fields + message fields)
    fn encode(&self) -> Result<Bytes> {
        // Pre-allocate buffer with estimated size
        let paxos_size = PaxosFields::encoded_size();
        let message_size = self.encoded_size_hint().unwrap_or(256);
        let mut buf = BytesMut::with_capacity(paxos_size + message_size);

        // Encode paxos fields first
        self.paxos_fields().encode(&mut buf)?;

        // Encode message-specific fields
        self.encode_message(&mut buf)?;

        Ok(buf.freeze())
    }

    /// Decode the complete message (paxos fields + message fields)
    fn decode(mut data: &[u8]) -> Result<Self> {
        // Decode paxos fields first
        let paxos = PaxosFields::decode(&mut data)?;

        // Decode message-specific fields
        Self::decode_message(paxos, &mut data)
    }
}

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

    #[test]
    fn test_paxos_fields_encode_decode() {
        let fields = PaxosFields {
            version: 42,
            deprecated_session_mon: -1,
            deprecated_session_mon_tid: 0,
        };

        let mut buf = BytesMut::new();
        fields.encode(&mut buf).unwrap();

        assert_eq!(buf.len(), PaxosFields::encoded_size());

        let mut data = buf.as_ref();
        let decoded = PaxosFields::decode(&mut data).unwrap();

        assert_eq!(decoded.version, 42);
        assert_eq!(decoded.deprecated_session_mon, -1);
        assert_eq!(decoded.deprecated_session_mon_tid, 0);
    }

    #[test]
    fn test_paxos_fields_default() {
        let fields = PaxosFields::new();
        assert_eq!(fields.version, 0);
        assert_eq!(fields.deprecated_session_mon, -1);
        assert_eq!(fields.deprecated_session_mon_tid, 0);
    }
}
