use crate::authentication::Mechanism;
use crate::error::Error;
use crate::response::Response;
use crate::util::XText;
use hostname;
use std::collections::HashSet;
use std::fmt::{self, Display, Formatter};
use std::net::{Ipv4Addr, Ipv6Addr};
use std::result::Result;
const DEFAULT_DOMAIN_CLIENT_ID: &str = "localhost.localdomain";
#[derive(PartialEq, Eq, Clone, Debug)]
pub enum ClientId {
    Domain(String),
    Ipv4(Ipv4Addr),
    Ipv6(Ipv6Addr),
}
impl Default for ClientId {
    fn default() -> Self {
        Self::Ipv4(Ipv4Addr::new(127, 0, 0, 1))
    }
}
impl Display for ClientId {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        match *self {
            ClientId::Domain(ref value) => f.write_str(value),
            ClientId::Ipv4(ref value) => write!(f, "[{value}]"),
            ClientId::Ipv6(ref value) => write!(f, "[IPv6:{value}]"),
        }
    }
}
impl ClientId {
    pub fn new(domain: String) -> ClientId {
        ClientId::Domain(domain)
    }
    pub fn hostname() -> ClientId {
        ClientId::Domain(
            hostname::get()
                .ok()
                .and_then(|s| s.into_string().ok())
                .unwrap_or_else(|| DEFAULT_DOMAIN_CLIENT_ID.to_string()),
        )
    }
}
#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
pub enum Extension {
    Pipelining,
    EightBitMime,
    SmtpUtfEight,
    StartTls,
    Authentication(Mechanism),
}
impl Display for Extension {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        match *self {
            Extension::Pipelining => write!(f, "PIPELINING"),
            Extension::EightBitMime => write!(f, "8BITMIME"),
            Extension::SmtpUtfEight => write!(f, "SMTPUTF8"),
            Extension::StartTls => write!(f, "STARTTLS"),
            Extension::Authentication(ref mechanism) => write!(f, "AUTH {mechanism}"),
        }
    }
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ServerInfo {
    pub name: String,
    pub features: HashSet<Extension>,
}
impl Display for ServerInfo {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        write!(
            f,
            "{} with {}",
            self.name,
            if self.features.is_empty() {
                "no supported features".to_string()
            } else {
                format!("{:?}", self.features)
            }
        )
    }
}
impl ServerInfo {
    pub fn from_response(response: &Response) -> Result<ServerInfo, Error> {
        let name = match response.first_word() {
            Some(name) => name,
            None => return Err(Error::ResponseParsing("Could not read server name")),
        };
        let mut features: HashSet<Extension> = HashSet::new();
        for line in response.message.as_slice() {
            if line.is_empty() {
                continue;
            }
            let split: Vec<&str> = line.split_whitespace().collect();
            match split.first().copied() {
                Some("PIPELINING") => {
                    features.insert(Extension::Pipelining);
                }
                Some("8BITMIME") => {
                    features.insert(Extension::EightBitMime);
                }
                Some("SMTPUTF8") => {
                    features.insert(Extension::SmtpUtfEight);
                }
                Some("STARTTLS") => {
                    features.insert(Extension::StartTls);
                }
                Some("AUTH") => {
                    for &mechanism in &split[1..] {
                        match mechanism {
                            "PLAIN" => {
                                features.insert(Extension::Authentication(Mechanism::Plain));
                            }
                            "LOGIN" => {
                                features.insert(Extension::Authentication(Mechanism::Login));
                            }
                            "XOAUTH2" => {
                                features.insert(Extension::Authentication(Mechanism::Xoauth2));
                            }
                            _ => (),
                        }
                    }
                }
                _ => (),
            };
        }
        Ok(ServerInfo {
            name: name.to_string(),
            features,
        })
    }
    pub fn supports_feature(&self, keyword: Extension) -> bool {
        self.features.contains(&keyword)
    }
    pub fn supports_auth_mechanism(&self, mechanism: Mechanism) -> bool {
        self.features
            .contains(&Extension::Authentication(mechanism))
    }
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub enum MailParameter {
    Body(MailBodyParameter),
    Size(usize),
    SmtpUtfEight,
    Other {
        keyword: String,
        value: Option<String>,
    },
}
impl Display for MailParameter {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        match *self {
            MailParameter::Body(ref value) => write!(f, "BODY={value}"),
            MailParameter::Size(size) => write!(f, "SIZE={size}"),
            MailParameter::SmtpUtfEight => f.write_str("SMTPUTF8"),
            MailParameter::Other {
                ref keyword,
                value: Some(ref value),
            } => write!(f, "{}={}", keyword, XText(value)),
            MailParameter::Other {
                ref keyword,
                value: None,
            } => f.write_str(keyword),
        }
    }
}
#[derive(PartialEq, Eq, Clone, Debug, Copy)]
pub enum MailBodyParameter {
    SevenBit,
    EightBitMime,
}
impl Display for MailBodyParameter {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        match *self {
            MailBodyParameter::SevenBit => f.write_str("7BIT"),
            MailBodyParameter::EightBitMime => f.write_str("8BITMIME"),
        }
    }
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub enum RcptParameter {
    Other {
        keyword: String,
        value: Option<String>,
    },
}
impl Display for RcptParameter {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        match *self {
            RcptParameter::Other {
                ref keyword,
                value: Some(ref value),
            } => write!(f, "{}={}", keyword, XText(value)),
            RcptParameter::Other {
                ref keyword,
                value: None,
            } => f.write_str(keyword),
        }
    }
}
#[cfg(test)]
mod test {
    use super::{ClientId, Extension, ServerInfo};
    use crate::authentication::Mechanism;
    use crate::response::{Category, Code, Detail, Response, Severity};
    use std::collections::HashSet;
    #[test]
    fn test_clientid_fmt() {
        assert_eq!(
            format!("{}", ClientId::new("test".to_string())),
            "test".to_string()
        );
    }
    #[test]
    fn test_extension_fmt() {
        assert_eq!(
            format!("{}", Extension::Pipelining),
            "PIPELINING".to_string()
        );
        assert_eq!(
            format!("{}", Extension::EightBitMime),
            "8BITMIME".to_string()
        );
        assert_eq!(
            format!("{}", Extension::Authentication(Mechanism::Plain)),
            "AUTH PLAIN".to_string()
        );
    }
    #[test]
    fn test_serverinfo_fmt() {
        let mut eightbitmime = HashSet::new();
        assert!(eightbitmime.insert(Extension::EightBitMime));
        assert_eq!(
            format!(
                "{}",
                ServerInfo {
                    name: "name".to_string(),
                    features: eightbitmime.clone(),
                }
            ),
            "name with {EightBitMime}".to_string()
        );
        let empty = HashSet::new();
        assert_eq!(
            format!(
                "{}",
                ServerInfo {
                    name: "name".to_string(),
                    features: empty,
                }
            ),
            "name with no supported features".to_string()
        );
        let mut plain = HashSet::new();
        assert!(plain.insert(Extension::Authentication(Mechanism::Plain)));
        assert_eq!(
            format!(
                "{}",
                ServerInfo {
                    name: "name".to_string(),
                    features: plain.clone(),
                }
            ),
            "name with {Authentication(Plain)}".to_string()
        );
    }
    #[test]
    fn test_serverinfo() {
        let response = Response::new(
            Code::new(
                Severity::PositiveCompletion,
                Category::Unspecified4,
                Detail::One,
            ),
            vec![
                "me".to_string(),
                "8BITMIME".to_string(),
                "SIZE 42".to_string(),
            ],
        );
        let mut features = HashSet::new();
        assert!(features.insert(Extension::EightBitMime));
        let server_info = ServerInfo {
            name: "me".to_string(),
            features,
        };
        assert_eq!(ServerInfo::from_response(&response).unwrap(), server_info);
        assert!(server_info.supports_feature(Extension::EightBitMime));
        assert!(!server_info.supports_feature(Extension::StartTls));
        let response2 = Response::new(
            Code::new(
                Severity::PositiveCompletion,
                Category::Unspecified4,
                Detail::One,
            ),
            vec![
                "me".to_string(),
                "AUTH PLAIN CRAM-MD5 XOAUTH2 OTHER".to_string(),
                "8BITMIME".to_string(),
                "SIZE 42".to_string(),
            ],
        );
        let mut features2 = HashSet::new();
        assert!(features2.insert(Extension::EightBitMime));
        assert!(features2.insert(Extension::Authentication(Mechanism::Plain),));
        assert!(features2.insert(Extension::Authentication(Mechanism::Xoauth2),));
        let server_info2 = ServerInfo {
            name: "me".to_string(),
            features: features2,
        };
        assert_eq!(ServerInfo::from_response(&response2).unwrap(), server_info2);
        assert!(server_info2.supports_feature(Extension::EightBitMime));
        assert!(server_info2.supports_auth_mechanism(Mechanism::Plain));
        assert!(!server_info2.supports_feature(Extension::StartTls));
    }
}