//! Cluster configuration and peer management.

use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::net::SocketAddr;
use std::str::FromStr;

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

/// Address for a peer node.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub struct PeerAddress {
    /// Host address (IP or hostname).
    pub host: String,
    /// Port number.
    pub port: u16,
}

impl PeerAddress {
    /// Creates a new peer address.
    pub fn new(host: impl Into<String>, port: u16) -> Self {
        Self {
            host: host.into(),
            port,
        }
    }

    /// Parses from "host:port" format.
    pub fn parse(s: &str) -> Option<Self> {
        let parts: Vec<&str> = s.split(':').collect();
        if parts.len() == 2 {
            parts[1].parse().ok().map(|port| Self {
                host: parts[0].to_string(),
                port,
            })
        } else {
            None
        }
    }

    /// Converts to SocketAddr if possible.
    pub fn to_socket_addr(&self) -> Option<SocketAddr> {
        SocketAddr::from_str(&format!("{}:{}", self.host, self.port)).ok()
    }
}

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

/// Information about a peer node.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PeerInfo {
    /// Node identifier.
    pub id: NodeId,
    /// Network address.
    pub address: PeerAddress,
    /// Whether this peer is a voting member.
    pub voting: bool,
    /// Last known state.
    pub state: PeerState,
}

impl PeerInfo {
    /// Creates new peer info.
    pub fn new(id: NodeId, address: PeerAddress) -> Self {
        Self {
            id,
            address,
            voting: true,
            state: PeerState::Unknown,
        }
    }

    /// Creates a non-voting learner peer.
    pub fn learner(id: NodeId, address: PeerAddress) -> Self {
        Self {
            id,
            address,
            voting: false,
            state: PeerState::Unknown,
        }
    }
}

/// State of a peer from this node's perspective.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum PeerState {
    /// State unknown (initial).
    Unknown,
    /// Peer is reachable.
    Reachable,
    /// Peer is unreachable.
    Unreachable,
    /// Peer is being probed.
    Probing,
}

/// Configuration for the cluster.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ClusterConfig {
    /// This node's ID.
    pub node_id: NodeId,
    /// This node's address.
    pub address: PeerAddress,
    /// Known peers in the cluster.
    pub peers: HashMap<NodeId, PeerInfo>,
    /// Configuration index (for joint consensus).
    pub config_index: u64,
    /// Whether this is a joint configuration.
    pub joint: bool,
    /// Old configuration (for joint consensus).
    pub old_peers: Option<HashMap<NodeId, PeerInfo>>,
}

impl ClusterConfig {
    /// Creates a new single-node cluster configuration.
    pub fn new(node_id: NodeId, address: PeerAddress) -> Self {
        Self {
            node_id,
            address,
            peers: HashMap::new(),
            config_index: 0,
            joint: false,
            old_peers: None,
        }
    }

    /// Adds a peer to the cluster.
    pub fn add_peer(&mut self, peer: PeerInfo) {
        self.peers.insert(peer.id, peer);
    }

    /// Removes a peer from the cluster.
    pub fn remove_peer(&mut self, id: NodeId) -> Option<PeerInfo> {
        self.peers.remove(&id)
    }

    /// Gets a peer by ID.
    pub fn get_peer(&self, id: NodeId) -> Option<&PeerInfo> {
        self.peers.get(&id)
    }

    /// Gets a mutable reference to a peer.
    pub fn get_peer_mut(&mut self, id: NodeId) -> Option<&mut PeerInfo> {
        self.peers.get_mut(&id)
    }

    /// Returns all peer IDs.
    pub fn peer_ids(&self) -> Vec<NodeId> {
        self.peers.keys().copied().collect()
    }

    /// Returns all voting peer IDs.
    pub fn voting_peers(&self) -> Vec<NodeId> {
        self.peers
            .iter()
            .filter(|(_, p)| p.voting)
            .map(|(id, _)| *id)
            .collect()
    }

    /// Returns the total number of voting members (including self).
    pub fn voting_members(&self) -> usize {
        self.peers.values().filter(|p| p.voting).count() + 1
    }

    /// Returns the quorum size needed for consensus.
    pub fn quorum_size(&self) -> usize {
        self.voting_members() / 2 + 1
    }

    /// Checks if we have quorum with the given votes.
    pub fn has_quorum(&self, votes: usize) -> bool {
        votes >= self.quorum_size()
    }

    /// Starts a configuration change (joint consensus).
    pub fn begin_config_change(&mut self, new_peers: HashMap<NodeId, PeerInfo>, index: u64) {
        self.old_peers = Some(self.peers.clone());
        self.peers = new_peers;
        self.config_index = index;
        self.joint = true;
    }

    /// Completes a configuration change.
    pub fn complete_config_change(&mut self) {
        self.old_peers = None;
        self.joint = false;
    }

    /// Aborts a configuration change.
    pub fn abort_config_change(&mut self) {
        if let Some(old) = self.old_peers.take() {
            self.peers = old;
        }
        self.joint = false;
    }

    /// Checks if node is in joint consensus mode.
    pub fn is_joint(&self) -> bool {
        self.joint
    }

    /// For joint consensus: checks if we have quorum in BOTH configurations.
    pub fn has_joint_quorum(&self, new_votes: usize, old_votes: usize) -> bool {
        if !self.joint {
            return self.has_quorum(new_votes);
        }

        let new_quorum = self.quorum_size();
        let old_quorum = self
            .old_peers
            .as_ref()
            .map(|p| p.values().filter(|peer| peer.voting).count() / 2 + 1)
            .unwrap_or(1);

        new_votes >= new_quorum && old_votes >= old_quorum
    }

    /// Updates peer state.
    pub fn update_peer_state(&mut self, id: NodeId, state: PeerState) {
        if let Some(peer) = self.peers.get_mut(&id) {
            peer.state = state;
        }
    }

    /// Returns all reachable peers.
    pub fn reachable_peers(&self) -> Vec<NodeId> {
        self.peers
            .iter()
            .filter(|(_, p)| p.state == PeerState::Reachable)
            .map(|(id, _)| *id)
            .collect()
    }

    /// Serializes the configuration.
    pub fn to_bytes(&self) -> Vec<u8> {
        bincode::serialize(self).unwrap_or_default()
    }

    /// Deserializes the configuration.
    pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
        bincode::deserialize(bytes).ok()
    }
}

impl Default for ClusterConfig {
    fn default() -> Self {
        Self::new(1, PeerAddress::new("127.0.0.1", 9000))
    }
}

/// Builder for cluster configurations.
pub struct ClusterBuilder {
    config: ClusterConfig,
}

impl ClusterBuilder {
    /// Creates a new builder.
    pub fn new(node_id: NodeId, address: PeerAddress) -> Self {
        Self {
            config: ClusterConfig::new(node_id, address),
        }
    }

    /// Adds a peer.
    pub fn with_peer(mut self, id: NodeId, address: PeerAddress) -> Self {
        self.config.add_peer(PeerInfo::new(id, address));
        self
    }

    /// Adds a learner (non-voting) peer.
    pub fn with_learner(mut self, id: NodeId, address: PeerAddress) -> Self {
        self.config.add_peer(PeerInfo::learner(id, address));
        self
    }

    /// Builds the configuration.
    pub fn build(self) -> ClusterConfig {
        self.config
    }
}

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

    #[test]
    fn test_peer_address() {
        let addr = PeerAddress::new("192.168.1.1", 9000);
        assert_eq!(addr.to_string(), "192.168.1.1:9000");

        let parsed = PeerAddress::parse("10.0.0.1:8080").unwrap();
        assert_eq!(parsed.host, "10.0.0.1");
        assert_eq!(parsed.port, 8080);
    }

    #[test]
    fn test_cluster_config() {
        let mut config = ClusterConfig::new(1, PeerAddress::new("127.0.0.1", 9000));

        config.add_peer(PeerInfo::new(2, PeerAddress::new("127.0.0.1", 9001)));
        config.add_peer(PeerInfo::new(3, PeerAddress::new("127.0.0.1", 9002)));

        assert_eq!(config.voting_members(), 3);
        assert_eq!(config.quorum_size(), 2);
        assert!(config.has_quorum(2));
        assert!(!config.has_quorum(1));
    }

    #[test]
    fn test_quorum_sizes() {
        // 1 node: quorum = 1
        let config1 = ClusterConfig::new(1, PeerAddress::new("127.0.0.1", 9000));
        assert_eq!(config1.quorum_size(), 1);

        // 3 nodes: quorum = 2
        let mut config3 = ClusterConfig::new(1, PeerAddress::new("127.0.0.1", 9000));
        config3.add_peer(PeerInfo::new(2, PeerAddress::new("127.0.0.1", 9001)));
        config3.add_peer(PeerInfo::new(3, PeerAddress::new("127.0.0.1", 9002)));
        assert_eq!(config3.quorum_size(), 2);

        // 5 nodes: quorum = 3
        let mut config5 = config3.clone();
        config5.add_peer(PeerInfo::new(4, PeerAddress::new("127.0.0.1", 9003)));
        config5.add_peer(PeerInfo::new(5, PeerAddress::new("127.0.0.1", 9004)));
        assert_eq!(config5.quorum_size(), 3);
    }

    #[test]
    fn test_learner_peers() {
        let mut config = ClusterConfig::new(1, PeerAddress::new("127.0.0.1", 9000));
        config.add_peer(PeerInfo::new(2, PeerAddress::new("127.0.0.1", 9001)));
        config.add_peer(PeerInfo::learner(3, PeerAddress::new("127.0.0.1", 9002)));

        // Learners don't count toward quorum
        assert_eq!(config.voting_members(), 2); // self + node 2
        assert_eq!(config.quorum_size(), 2);
        assert_eq!(config.voting_peers().len(), 1); // only node 2
    }

    #[test]
    fn test_cluster_builder() {
        let config = ClusterBuilder::new(1, PeerAddress::new("127.0.0.1", 9000))
            .with_peer(2, PeerAddress::new("127.0.0.1", 9001))
            .with_peer(3, PeerAddress::new("127.0.0.1", 9002))
            .with_learner(4, PeerAddress::new("127.0.0.1", 9003))
            .build();

        assert_eq!(config.peer_ids().len(), 3);
        assert_eq!(config.voting_members(), 3);
    }

    #[test]
    fn test_joint_consensus() {
        let mut config = ClusterConfig::new(1, PeerAddress::new("127.0.0.1", 9000));
        config.add_peer(PeerInfo::new(2, PeerAddress::new("127.0.0.1", 9001)));
        config.add_peer(PeerInfo::new(3, PeerAddress::new("127.0.0.1", 9002)));

        // Start config change: add node 4
        let mut new_peers = config.peers.clone();
        new_peers.insert(4, PeerInfo::new(4, PeerAddress::new("127.0.0.1", 9003)));

        config.begin_config_change(new_peers, 100);
        assert!(config.is_joint());

        // Need quorum in both old (2 of 3) and new (3 of 4) configs
        assert!(config.has_joint_quorum(3, 2));
        assert!(!config.has_joint_quorum(2, 2)); // Not enough in new config
        assert!(!config.has_joint_quorum(3, 1)); // Not enough in old config

        config.complete_config_change();
        assert!(!config.is_joint());
        assert_eq!(config.voting_members(), 4);
    }

    #[test]
    fn test_serialization() {
        let config = ClusterBuilder::new(1, PeerAddress::new("127.0.0.1", 9000))
            .with_peer(2, PeerAddress::new("127.0.0.1", 9001))
            .build();

        let bytes = config.to_bytes();
        let decoded = ClusterConfig::from_bytes(&bytes).unwrap();

        assert_eq!(decoded.node_id, 1);
        assert_eq!(decoded.peers.len(), 1);
    }
}
