//! Node identification and metadata types

use serde::{Deserialize, Serialize};
use std::net::SocketAddr;

/// Unique identifier for each node in the cluster
pub type NodeId = u64;

/// Role of a node in the cluster
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[derive(Default)]
pub enum NodeRole {
    /// Control Plane node - participates in Raft consensus
    ControlPlane,
    /// Worker node - only participates in gossip, watches Control Plane
    #[default]
    Worker,
}


/// Raft participation role for a node.
///
/// This determines whether and how a node participates in the Raft consensus protocol.
/// The RaftRole is separate from NodeRole (gossip role) - a node can be a ControlPlane
/// gossip participant without being a Raft voter.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum RaftRole {
    /// Full voting member in Raft consensus.
    /// Participates in leader election and log replication.
    #[default]
    Voter,
    /// Non-voting replica that receives log replication.
    /// Can be promoted to Voter via cluster membership change.
    Learner,
    /// No Raft participation.
    /// Node only uses gossip and acts as a client proxy.
    None,
}

impl RaftRole {
    /// Check if this role participates in Raft at all.
    ///
    /// Returns `true` for Voter and Learner, `false` for None.
    pub fn participates_in_raft(&self) -> bool {
        !matches!(self, RaftRole::None)
    }

    /// Check if this role is a voting member.
    pub fn is_voter(&self) -> bool {
        matches!(self, RaftRole::Voter)
    }

    /// Check if this role is a learner (non-voting replica).
    pub fn is_learner(&self) -> bool {
        matches!(self, RaftRole::Learner)
    }

    /// Convert to string representation.
    pub fn as_str(&self) -> &'static str {
        match self {
            RaftRole::Voter => "voter",
            RaftRole::Learner => "learner",
            RaftRole::None => "none",
        }
    }
}

impl std::fmt::Display for RaftRole {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.as_str())
    }
}

impl std::str::FromStr for RaftRole {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_lowercase().as_str() {
            "voter" => Ok(RaftRole::Voter),
            "learner" => Ok(RaftRole::Learner),
            "none" => Ok(RaftRole::None),
            _ => Err(format!(
                "invalid raft role '{}', expected 'voter', 'learner', or 'none'",
                s
            )),
        }
    }
}

/// Node metadata stored in cluster membership
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct NodeInfo {
    /// Unique node identifier
    pub id: NodeId,
    /// Human-readable node name
    pub name: String,
    /// Address for Raft RPCs (Control Plane nodes only)
    pub raft_addr: Option<SocketAddr>,
    /// Address for client API (gRPC)
    pub api_addr: SocketAddr,
    /// Address for gossip protocol (UDP)
    pub gossip_addr: SocketAddr,
    /// Node role in the cluster
    pub role: NodeRole,
}

impl NodeInfo {
    /// Create a new Control Plane node info
    pub fn control_plane(
        id: NodeId,
        name: impl Into<String>,
        raft_addr: SocketAddr,
        api_addr: SocketAddr,
        gossip_addr: SocketAddr,
    ) -> Self {
        Self {
            id,
            name: name.into(),
            raft_addr: Some(raft_addr),
            api_addr,
            gossip_addr,
            role: NodeRole::ControlPlane,
        }
    }

    /// Create a new Worker node info
    pub fn worker(
        id: NodeId,
        name: impl Into<String>,
        api_addr: SocketAddr,
        gossip_addr: SocketAddr,
    ) -> Self {
        Self {
            id,
            name: name.into(),
            raft_addr: None,
            api_addr,
            gossip_addr,
            role: NodeRole::Worker,
        }
    }

    /// Check if this node is a Control Plane node
    pub fn is_control_plane(&self) -> bool {
        self.role == NodeRole::ControlPlane
    }

    /// Check if this node is a Worker node
    pub fn is_worker(&self) -> bool {
        self.role == NodeRole::Worker
    }
}

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

    #[test]
    fn test_control_plane_node() {
        let node = NodeInfo::control_plane(
            1,
            "cp-1",
            "127.0.0.1:5000".parse().unwrap(),
            "127.0.0.1:5001".parse().unwrap(),
            "127.0.0.1:5002".parse().unwrap(),
        );

        assert_eq!(node.id, 1);
        assert_eq!(node.name, "cp-1");
        assert!(node.is_control_plane());
        assert!(!node.is_worker());
        assert!(node.raft_addr.is_some());
    }

    #[test]
    fn test_worker_node() {
        let node = NodeInfo::worker(
            100,
            "worker-1",
            "127.0.0.1:6001".parse().unwrap(),
            "127.0.0.1:6002".parse().unwrap(),
        );

        assert_eq!(node.id, 100);
        assert!(node.is_worker());
        assert!(!node.is_control_plane());
        assert!(node.raft_addr.is_none());
    }

    #[test]
    fn test_node_serialization() {
        let node = NodeInfo::control_plane(
            1,
            "test",
            "127.0.0.1:5000".parse().unwrap(),
            "127.0.0.1:5001".parse().unwrap(),
            "127.0.0.1:5002".parse().unwrap(),
        );

        let serialized = bincode::serialize(&node).unwrap();
        let deserialized: NodeInfo = bincode::deserialize(&serialized).unwrap();

        assert_eq!(node, deserialized);
    }

    #[test]
    fn test_raft_role_default() {
        let role = RaftRole::default();
        assert_eq!(role, RaftRole::Voter);
        assert!(role.participates_in_raft());
        assert!(role.is_voter());
    }

    #[test]
    fn test_raft_role_participates() {
        assert!(RaftRole::Voter.participates_in_raft());
        assert!(RaftRole::Learner.participates_in_raft());
        assert!(!RaftRole::None.participates_in_raft());
    }

    #[test]
    fn test_raft_role_from_str() {
        assert_eq!("voter".parse::<RaftRole>().unwrap(), RaftRole::Voter);
        assert_eq!("learner".parse::<RaftRole>().unwrap(), RaftRole::Learner);
        assert_eq!("none".parse::<RaftRole>().unwrap(), RaftRole::None);
        assert_eq!("VOTER".parse::<RaftRole>().unwrap(), RaftRole::Voter);
        assert!("invalid".parse::<RaftRole>().is_err());
    }

    #[test]
    fn test_raft_role_display() {
        assert_eq!(RaftRole::Voter.to_string(), "voter");
        assert_eq!(RaftRole::Learner.to_string(), "learner");
        assert_eq!(RaftRole::None.to_string(), "none");
    }

    #[test]
    fn test_raft_role_serialization() {
        // Test binary serialization
        let serialized = bincode::serialize(&RaftRole::Voter).unwrap();
        let deserialized: RaftRole = bincode::deserialize(&serialized).unwrap();
        assert_eq!(deserialized, RaftRole::Voter);

        // Test all variants
        for role in [RaftRole::Voter, RaftRole::Learner, RaftRole::None] {
            let serialized = bincode::serialize(&role).unwrap();
            let deserialized: RaftRole = bincode::deserialize(&serialized).unwrap();
            assert_eq!(deserialized, role);
        }
    }
}
