use libp2p::{multiaddr::Protocol, Multiaddr, PeerId};
pub trait MultiaddrExt {
    fn peer_id(&self) -> Option<PeerId>;
    fn extract_peer_id(&mut self) -> Option<PeerId>;
    fn relay_peer_id(&self) -> Option<PeerId>;
    fn address(&self) -> Multiaddr;
    fn is_relay(&self) -> bool;
    fn is_relayed(&self) -> bool;
    fn is_loopback(&self) -> bool;
    fn is_private(&self) -> bool;
}
impl MultiaddrExt for Multiaddr {
    fn peer_id(&self) -> Option<PeerId> {
        if let Some(Protocol::P2p(peer)) = self.iter().last() {
            return Some(peer);
        }
        None
    }
    fn extract_peer_id(&mut self) -> Option<PeerId> {
        match self.pop() {
            Some(Protocol::P2p(peer)) => Some(peer),
            _ => None,
        }
    }
    fn relay_peer_id(&self) -> Option<PeerId> {
        if !self.is_relay() {
            return None;
        }
        while let Some(protocol) = self.iter().next() {
            if let Protocol::P2p(peer) = protocol {
                return Some(peer);
            }
        }
        None
    }
    fn address(&self) -> Multiaddr {
        let mut addr = Multiaddr::empty();
        for proto in self.iter() {
            if matches!(
                proto,
                Protocol::Ip4(_)
                    | Protocol::Ip6(_)
                    | Protocol::Tcp(_)
                    | Protocol::Udp(_)
                    | Protocol::Quic
                    | Protocol::QuicV1
                    | Protocol::Dnsaddr(_)
            ) {
                addr.push(proto);
            }
        }
        addr
    }
    fn is_relay(&self) -> bool {
        self.iter()
            .any(|proto| matches!(proto, Protocol::P2pCircuit))
    }
    fn is_relayed(&self) -> bool {
        if !self.is_relay() {
            return false;
        }
        if self.peer_id().is_none() {
            return false;
        }
        true
    }
    fn is_loopback(&self) -> bool {
        self.iter().any(|proto| match proto {
            Protocol::Ip4(ip) => ip.is_loopback(),
            Protocol::Ip6(ip) => ip.is_loopback(),
            _ => false,
        })
    }
    fn is_private(&self) -> bool {
        self.iter().any(|proto| match proto {
            Protocol::Ip4(ip) => ip.is_private(),
            Protocol::Ip6(ip) => {
                (ip.segments()[0] & 0xffc0) != 0xfe80 && (ip.segments()[0] & 0xfe00) != 0xfc00
            }
            _ => false,
        })
    }
}
#[cfg(test)]
mod tests {
    use std::str::FromStr;
    use super::*;
    #[test]
    fn connection_targets() {
        let peer_id = PeerId::from_str("QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ")
            .expect("Valid peer id");
        let multiaddr_wo_peer =
            Multiaddr::from_str("/ip4/104.131.131.82/tcp/4001").expect("Valid multiaddr");
        let multiaddr_with_peer =
            Multiaddr::from_str(&format!("{multiaddr_wo_peer}/p2p/{peer_id}"))
                .expect("valid multiaddr");
        let p2p_peer = Multiaddr::from_str(&format!("/p2p/{peer_id}")).expect("Valid multiaddr");
        assert!(multiaddr_wo_peer.peer_id().is_none());
        assert!(multiaddr_with_peer.peer_id().is_some());
        assert!(p2p_peer.peer_id().is_some());
        let peer_id_target = multiaddr_with_peer.peer_id().expect("Valid peer id");
        assert_eq!(peer_id_target, peer_id);
    }
}