//! RDAP Nameserver object class.
use std::collections::HashSet;

use crate::prelude::{ttl::Ttl0Data, ContentExtensions};

use {
    crate::prelude::{Common, Extension, ObjectCommon},
    std::{net::IpAddr, str::FromStr},
};

use serde::{Deserialize, Serialize};

use super::{
    to_opt_vec, to_opt_vectorstringish, types::Link, CommonFields, Entity, Event, GetSelfLink,
    Notice, ObjectCommonFields, Port43, RdapResponseError, Remark, SelfLink, ToChild, ToResponse,
    VectorStringish,
};

/// Represents an IP address set for nameservers.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Default)]
pub struct IpAddresses {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub v6: Option<VectorStringish>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub v4: Option<VectorStringish>,
}

#[buildstructor::buildstructor]
impl IpAddresses {
    /// Builds nameserver IP address.
    #[builder(visibility = "pub")]
    fn new(addresses: Vec<String>) -> Result<Self, RdapResponseError> {
        let mut v4: Vec<String> = Vec::new();
        let mut v6: Vec<String> = Vec::new();
        for addr in addresses {
            let ip = IpAddr::from_str(&addr)?;
            match ip {
                IpAddr::V4(_) => v4.push(addr),
                IpAddr::V6(_) => v6.push(addr),
            }
        }
        Ok(Self {
            v4: to_opt_vectorstringish(v4),
            v6: to_opt_vectorstringish(v6),
        })
    }

    #[allow(dead_code)]
    #[builder(entry = "illegal", visibility = "pub(crate)")]
    fn new_illegal(v6: Option<Vec<String>>, v4: Option<Vec<String>>) -> Self {
        Self {
            v4: v4.map(VectorStringish::from),
            v6: v6.map(VectorStringish::from),
        }
    }

    /// Get the IPv6 addresses.
    pub fn v6s(&self) -> &[String] {
        self.v6
            .as_ref()
            .map(|v| v.vec().as_ref())
            .unwrap_or_default()
    }

    /// Get the IPv4 addresses.
    pub fn v4s(&self) -> &[String] {
        self.v4
            .as_ref()
            .map(|v| v.vec().as_ref())
            .unwrap_or_default()
    }
}

/// Represents an RDAP [nameserver](https://rdap.rcode3.com/protocol/object_classes.html#nameserver) response.
///
/// Using the builder is recommended to construct this structure as it
/// will fill-in many of the mandatory fields.
/// The following is an example.
///
/// ```rust
/// use icann_rdap_common::prelude::*;
///
/// let ns = Nameserver::response_obj()
///   .ldh_name("ns1.example.com")
///   .handle("ns1_example_com-1")
///   .status("active")
///   .address("10.0.0.1")
///   .address("10.0.0.2")
///   .entity(Entity::builder().handle("FOO").build())
///   .build().unwrap();
/// let c = serde_json::to_string_pretty(&ns).unwrap();
/// eprintln!("{c}");
/// ```
///
/// This will produce the following.
///
/// ```norust
///   {
///     "rdapConformance": [
///       "rdap_level_0"
///     ],
///     "objectClassName": "nameserver",
///     "handle": "ns1_example_com-1",
///     "status": [
///       "active"
///     ],
///     "entities": [
///       {
///         "rdapConformance": [
///           "rdap_level_0"
///         ],
///         "objectClassName": "entity",
///         "handle": "FOO"
///       }
///     ],
///     "ldhName": "ns1.example.com",
///     "ipAddresses": {
///       "v4": [
///         "10.0.0.1",
///         "10.0.0.2"
///       ]
///     }
///   }
/// ```
///
/// Access to the nameserver information should be done via the getter functions.
/// See [CommonFields] and [ObjectCommonFields] for common getter functions.
/// ```rust
/// # use icann_rdap_common::prelude::*;
/// # let nameserver = Nameserver::builder()
/// #   .ldh_name("foo.example.com")
/// #   .build().unwrap();
/// let handle = nameserver.handle();
/// let ldh_name = nameserver.ldh_name();
/// let unicode_name = nameserver.unicode_name();
/// let ip_addresses = nameserver.ip_addresses();
/// ```
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct Nameserver {
    #[serde(flatten)]
    pub common: Common,

    #[serde(flatten)]
    pub object_common: ObjectCommon,

    #[serde(rename = "ldhName")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub ldh_name: Option<String>,

    #[serde(rename = "unicodeName")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub unicode_name: Option<String>,

    #[serde(rename = "ipAddresses")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub ip_addresses: Option<IpAddresses>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub ttl0_data: Option<Ttl0Data>,
}

#[buildstructor::buildstructor]
impl Nameserver {
    /// Builds a basic nameserver object for use with embedding into response objects.
    ///
    /// ```rust
    /// use icann_rdap_common::prelude::*;
    ///
    /// let ns = Nameserver::builder()
    ///   .ldh_name("ns1.example.com") //required for this builder
    ///   .handle("ns1_example_com-1")
    ///   .status("active")
    ///   .address("10.0.0.1")
    ///   .address("10.0.0.2")
    ///   .entity(Entity::builder().handle("FOO").build())
    ///   .remark(Remark::builder().title("hidden nameserver").build())
    ///   .build().unwrap();
    /// ```
    #[builder(visibility = "pub")]
    fn new<T: Into<String>>(
        ldh_name: T,
        addresses: Vec<String>,
        handle: Option<String>,
        remarks: Vec<Remark>,
        links: Vec<Link>,
        events: Vec<Event>,
        statuses: Vec<String>,
        port_43: Option<Port43>,
        entities: Vec<Entity>,
        ttl0_data: Option<Ttl0Data>,
        redacted: Option<Vec<crate::response::redacted::Redacted>>,
    ) -> Result<Self, RdapResponseError> {
        let ip_addresses = if !addresses.is_empty() {
            Some(IpAddresses::builder().addresses(addresses).build()?)
        } else {
            None
        };
        Ok(Self {
            common: Common::builder().build(),
            object_common: ObjectCommon::nameserver()
                .and_handle(handle)
                .and_remarks(to_opt_vec(remarks))
                .and_links(to_opt_vec(links))
                .and_events(to_opt_vec(events))
                .status(statuses)
                .and_port_43(port_43)
                .and_entities(to_opt_vec(entities))
                .and_redacted(redacted)
                .build(),
            ldh_name: Some(ldh_name.into()),
            unicode_name: None,
            ip_addresses,
            ttl0_data,
        })
    }

    /// Builds a nameserver object for a response.
    ///
    /// ```rust
    /// use icann_rdap_common::prelude::*;
    ///
    /// let ns = Nameserver::response_obj()
    ///   .ldh_name("ns1.example.com") //required for this builder
    ///   .handle("ns1_example_com-1")
    ///   .status("active")
    ///   .address("10.0.0.1")
    ///   .address("10.0.0.2")
    ///   .entity(Entity::builder().handle("FOO").build())
    ///   .extension(ExtensionId::NroRdapProfile0.as_ref())
    ///   .notice(Notice::builder().title("test").build())
    ///   .build().unwrap();
    /// ```
    #[builder(entry = "response_obj", visibility = "pub")]
    fn new_response_obj<T: Into<String>>(
        ldh_name: T,
        addresses: Vec<String>,
        handle: Option<String>,
        remarks: Vec<Remark>,
        links: Vec<Link>,
        events: Vec<Event>,
        statuses: Vec<String>,
        port_43: Option<Port43>,
        entities: Vec<Entity>,
        notices: Vec<Notice>,
        extensions: Vec<Extension>,
        ttl0_data: Option<Ttl0Data>,
        redacted: Option<Vec<crate::response::redacted::Redacted>>,
    ) -> Result<Self, RdapResponseError> {
        let common = Common::level0()
            .extensions(extensions)
            .and_notices(to_opt_vec(notices))
            .build();
        let mut nameserver = Nameserver::builder()
            .ldh_name(ldh_name)
            .addresses(addresses)
            .and_handle(handle)
            .remarks(remarks)
            .links(links)
            .events(events)
            .statuses(statuses)
            .and_port_43(port_43)
            .entities(entities)
            .and_ttl0_data(ttl0_data)
            .and_redacted(redacted)
            .build()?;
        nameserver.common = common;
        Ok(nameserver)
    }

    #[builder(entry = "illegal", visibility = "pub(crate)")]
    #[allow(dead_code)]
    fn new_illegal(ldh_name: Option<String>, ip_addresses: Option<IpAddresses>) -> Self {
        Self {
            common: Common::level0().build(),
            object_common: ObjectCommon::nameserver().build(),
            ldh_name,
            unicode_name: None,
            ip_addresses,
            ttl0_data: None,
        }
    }

    /// Get the LDH name.
    pub fn ldh_name(&self) -> Option<&str> {
        self.ldh_name.as_deref()
    }

    /// Get the Unicode name.
    pub fn unicode_name(&self) -> Option<&str> {
        self.unicode_name.as_deref()
    }

    /// Get the IP addresses.
    pub fn ip_addresses(&self) -> Option<&IpAddresses> {
        self.ip_addresses.as_ref()
    }

    /// Getter for the ttl0 data.
    pub fn ttl0_data(&self) -> Option<&Ttl0Data> {
        self.ttl0_data.as_ref()
    }
}

impl ToResponse for Nameserver {
    fn to_response(self) -> super::RdapResponse {
        super::RdapResponse::Nameserver(Box::new(self))
    }
}

impl GetSelfLink for Nameserver {
    fn self_link(&self) -> Option<&Link> {
        self.object_common.self_link()
    }
}

impl SelfLink for Nameserver {
    fn with_self_link(mut self, link: Link) -> Self {
        self.object_common = self.object_common.with_self_link(link);
        self
    }
}

impl ToChild for Nameserver {
    fn to_child(mut self) -> Self {
        self.common = Common {
            rdap_conformance: None,
            notices: None,
        };
        self
    }
}

impl CommonFields for Nameserver {
    fn common(&self) -> &Common {
        &self.common
    }
}

impl ObjectCommonFields for Nameserver {
    fn object_common(&self) -> &ObjectCommon {
        &self.object_common
    }
}

impl ContentExtensions for Nameserver {
    fn content_extensions(&self) -> std::collections::HashSet<super::ExtensionId> {
        let mut exts = HashSet::new();
        exts.extend(self.common().content_extensions());
        exts.extend(self.object_common().content_extensions());
        exts
    }
}

#[cfg(test)]
#[allow(non_snake_case)]
mod tests {
    use super::Nameserver;

    #[test]
    fn GIVEN_nameserver_WHEN_deserialize_THEN_success() {
        // GIVEN
        let expected = r#"
        {
            "objectClassName" : "nameserver",
            "handle" : "XXXX",
            "ldhName" : "ns1.xn--fo-5ja.example",
            "unicodeName" : "ns.fóo.example",
            "status" : [ "active" ],
            "ipAddresses" :
            {
              "v4": [ "192.0.2.1", "192.0.2.2" ],
              "v6": [ "2001:db8::123" ]
            },
            "remarks" :
            [
              {
                "description" :
                [
                  "She sells sea shells down by the sea shore.",
                  "Originally written by Terry Sullivan."
                ]
              }
            ],
            "links" :
            [
              {
                "value" : "https://example.net/nameserver/ns1.xn--fo-5ja.example",
                "rel" : "self",
                "href" : "https://example.net/nameserver/ns1.xn--fo-5ja.example",
                "type" : "application/rdap+json"
              }
            ],
            "port43" : "whois.example.net",
            "events" :
            [
              {
                "eventAction" : "registration",
                "eventDate" : "1990-12-31T23:59:59Z"
              },
              {
                "eventAction" : "last changed",
                "eventDate" : "1991-12-31T23:59:59Z",
                "eventActor" : "joe@example.com"
              }
            ]
        }
        "#;

        // WHEN
        let actual = serde_json::from_str::<Nameserver>(expected);

        // THEN
        let actual = actual.unwrap();
        assert_eq!(actual.object_common.object_class_name, "nameserver");
        assert!(actual.object_common.handle.is_some());
        assert!(actual.ldh_name.is_some());
        assert!(actual.unicode_name.is_some());
        assert!(actual.ip_addresses.is_some());
        assert!(actual.object_common.remarks.is_some());
        assert!(actual.object_common.status.is_some());
        assert!(actual.object_common.links.is_some());
        assert!(actual.object_common.events.is_some());
    }
}
