use std::{collections::hash_map::DefaultHasher, hash::Hasher, net::IpAddr, time::Instant};

use super::FALLBACK_VERSION;

#[async_trait::async_trait]
pub trait ServerPeer: Send + Sync {
    /// Every implementation of this trait must produce
    /// the same ID, given the same data.
    fn id(&self) -> u64 {
        let mut hasher = DefaultHasher::new();
        if let Some(p) = self.tcp_port() {
            hasher.write_u16(p)
        };
        if let Some(p) = self.ssl_port() {
            hasher.write_u16(p)
        };
        hasher.write(self.ip().to_string().as_bytes());
        hasher.finish()
    }
    fn tcp_port(&self) -> Option<u16>;
    fn ssl_port(&self) -> Option<u16>;
    fn ip(&self) -> IpAddr;
    fn hostname(&self) -> Option<String>;
    fn pretty_hostname(&self) -> String;
    /// maximum protocol version
    fn version(&self) -> String;
    fn last_candidate_request(&self) -> Option<Instant>;
}

impl std::fmt::Display for dyn ServerPeer {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        // Strip weird utf-8 characters.
        let safe_hostname = self
            .hostname()
            .unwrap_or(self.ip().to_string())
            .chars()
            .filter(|c| c.is_ascii())
            .collect::<String>();
        write!(
            f,
            "Peer {} - IP: {} TCP: {} SSL: {} version: {}",
            safe_hostname,
            self.ip(),
            self.tcp_port().unwrap_or_default(),
            self.ssl_port().unwrap_or_default(),
            self.version()
        )
    }
}

/**
 * A server that has announced itself to us, but we have not verified.
 */
#[derive(Debug, Clone)]
pub struct CandidatePeer {
    pub(crate) tcp_port: Option<u16>,
    pub(crate) ssl_port: Option<u16>,
    #[allow(dead_code)]
    pub(crate) ws_port: Option<u16>,
    #[allow(dead_code)]
    pub(crate) wss_port: Option<u16>,
    pub(crate) hostname: Option<String>,
    pub(crate) ip: IpAddr,
    pub(crate) version: Option<String>,
}

impl ServerPeer for CandidatePeer {
    fn tcp_port(&self) -> Option<u16> {
        self.tcp_port
    }

    fn ssl_port(&self) -> Option<u16> {
        self.ssl_port
    }

    fn ip(&self) -> IpAddr {
        self.ip
    }

    fn hostname(&self) -> Option<String> {
        self.hostname.clone()
    }

    fn version(&self) -> String {
        match self.version.as_ref() {
            Some(v) => v.clone(),
            None => FALLBACK_VERSION.to_owned(),
        }
    }

    fn pretty_hostname(&self) -> String {
        if let Some(h) = self.hostname() {
            format!("{} (IP: {})", h, self.ip())
        } else {
            self.ip().to_string()
        }
    }

    fn last_candidate_request(&self) -> Option<Instant> {
        None
    }
}

#[derive(Debug, Clone)]
pub struct GoodKnownPeer {
    pub tcp_port: Option<u16>,
    pub ssl_port: Option<u16>,
    pub hostname: Option<String>,
    pub ip: IpAddr,
    /// Last time we checked if this was a good peer.
    pub last_visit: Instant,
    /// Last time we asked it for server peer candidates
    pub last_candidate_request: Option<Instant>,
    pub version: String,
}

impl ServerPeer for GoodKnownPeer {
    fn tcp_port(&self) -> Option<u16> {
        self.tcp_port
    }

    fn ssl_port(&self) -> Option<u16> {
        self.ssl_port
    }

    fn ip(&self) -> IpAddr {
        self.ip
    }

    fn hostname(&self) -> Option<String> {
        self.hostname.clone()
    }

    fn version(&self) -> String {
        self.version.clone()
    }

    fn pretty_hostname(&self) -> String {
        if let Some(h) = self.hostname() {
            format!("{} (IP: {})", h, self.ip())
        } else {
            self.ip().to_string()
        }
    }

    fn last_candidate_request(&self) -> Option<Instant> {
        self.last_candidate_request
    }
}
