//! Node identity for the gossip protocol

use chainfire_types::node::{NodeId, NodeRole};
use foca::Identity;
use serde::{Deserialize, Serialize};
use std::net::SocketAddr;

/// Node identity for the SWIM gossip protocol
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct GossipId {
    /// Unique node identifier
    pub node_id: NodeId,
    /// UDP address for gossip
    pub addr: SocketAddr,
    /// Incarnation number - bumped on rejoin to distinguish old/new instances
    pub incarnation: u64,
    /// Node role
    pub role: NodeRole,
}

impl GossipId {
    /// Create a new gossip identity
    pub fn new(node_id: NodeId, addr: SocketAddr, role: NodeRole) -> Self {
        Self {
            node_id,
            addr,
            incarnation: 0,
            role,
        }
    }

    /// Create a Control Plane node identity
    pub fn control_plane(node_id: NodeId, addr: SocketAddr) -> Self {
        Self::new(node_id, addr, NodeRole::ControlPlane)
    }

    /// Create a Worker node identity
    pub fn worker(node_id: NodeId, addr: SocketAddr) -> Self {
        Self::new(node_id, addr, 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
    }
}

impl Identity for GossipId {
    type Addr = SocketAddr;

    fn addr(&self) -> SocketAddr {
        self.addr
    }

    fn renew(&self) -> Option<Self> {
        // Create new identity with bumped incarnation
        Some(Self {
            incarnation: self.incarnation + 1,
            ..self.clone()
        })
    }

    fn win_addr_conflict(&self, other: &Self) -> bool {
        // Higher incarnation wins, tie-break by node_id
        match self.incarnation.cmp(&other.incarnation) {
            std::cmp::Ordering::Greater => true,
            std::cmp::Ordering::Less => false,
            std::cmp::Ordering::Equal => self.node_id > other.node_id,
        }
    }
}

impl std::fmt::Display for GossipId {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{}@{}:{}",
            self.node_id,
            self.addr,
            self.incarnation
        )
    }
}

impl PartialOrd for GossipId {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for GossipId {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        // First compare by node_id, then by incarnation
        match self.node_id.cmp(&other.node_id) {
            std::cmp::Ordering::Equal => self.incarnation.cmp(&other.incarnation),
            other => other,
        }
    }
}

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

    #[test]
    fn test_identity_creation() {
        let id = GossipId::control_plane(1, "127.0.0.1:5000".parse().unwrap());

        assert_eq!(id.node_id, 1);
        assert_eq!(id.incarnation, 0);
        assert!(id.is_control_plane());
    }

    #[test]
    fn test_identity_renew() {
        let id = GossipId::worker(1, "127.0.0.1:5000".parse().unwrap());
        let renewed = id.renew().unwrap();

        assert_eq!(renewed.node_id, id.node_id);
        assert_eq!(renewed.addr, id.addr);
        assert_eq!(renewed.incarnation, 1);
    }

    #[test]
    fn test_identity_ordering() {
        let id1 = GossipId::new(1, "127.0.0.1:5000".parse().unwrap(), NodeRole::Worker);
        let id2 = GossipId::new(2, "127.0.0.1:5001".parse().unwrap(), NodeRole::Worker);
        let id1_renewed = id1.renew().unwrap();

        assert!(id1 < id2);
        assert!(id1 < id1_renewed);
    }

    #[test]
    fn test_serialization() {
        let id = GossipId::control_plane(42, "192.168.1.1:5000".parse().unwrap());
        let bytes = bincode::serialize(&id).unwrap();
        let restored: GossipId = bincode::deserialize(&bytes).unwrap();

        assert_eq!(id, restored);
    }
}
