use crate::peer::Peer;
use libp2p::{Multiaddr, PeerId};
use parking_lot::RwLock;
use std::collections::HashMap;
use std::sync::Arc;

pub struct PeerStore {
    peers: RwLock<HashMap<PeerId, Peer>>,
}

impl PeerStore {
    pub fn new() -> Self {
        Self {
            peers: RwLock::new(HashMap::new()),
        }
    }

    pub fn add_peer(&self, peer: Peer) {
        let mut peers = self.peers.write();
        if let Some(existing) = peers.get_mut(&peer.peer_id) {
            for addr in peer.addresses.iter() {
                existing.add_address(addr.clone());
            }
            if let Some(ref ip) = peer.public_ip {
                existing.set_public_ip(ip.clone());
            }
            for port in peer.listen_ports.iter() {
                existing.add_listen_port(*port);
            }
            existing.update_last_seen();
        } else {
            peers.insert(peer.peer_id, peer);
        }
    }

    pub fn remove_peer(&self, peer_id: &PeerId) {
        let mut peers = self.peers.write();
        peers.remove(peer_id);
    }

    pub fn get_peer(&self, peer_id: &PeerId) -> Option<Peer> {
        let peers = self.peers.read();
        peers.get(peer_id).cloned()
    }

    pub fn get_all_peers(&self) -> Vec<Peer> {
        let peers = self.peers.read();
        peers.values().cloned().collect()
    }

    pub fn get_peers_count(&self) -> usize {
        let peers = self.peers.read();
        peers.len()
    }

    pub fn update_peer_addresses(&self, peer_id: &PeerId, addresses: impl Iterator<Item = Multiaddr>) {
        let mut peers = self.peers.write();
        if let Some(peer) = peers.get_mut(peer_id) {
            for addr in addresses {
                peer.add_address(addr);
            }
        }
    }

    pub fn update_peer_public_ip(&self, peer_id: &PeerId, ip: String) {
        let mut peers = self.peers.write();
        if let Some(peer) = peers.get_mut(peer_id) {
            peer.set_public_ip(ip);
        }
    }
}

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

impl Clone for PeerStore {
    fn clone(&self) -> Self {
        Self {
            peers: RwLock::new(self.peers.read().clone()),
        }
    }
}

impl From<HashMap<PeerId, Peer>> for PeerStore {
    fn from(peers: HashMap<PeerId, Peer>) -> Self {
        Self {
            peers: RwLock::new(peers),
        }
    }
}

pub type SharedPeerStore = Arc<PeerStore>;

pub fn shared_peer_store() -> SharedPeerStore {
    Arc::new(PeerStore::new())
}