//! Support for admin tasks, such as managing publishers and RFC8181 clients.

use std::fmt;
use rpki::ca::idexchange;
use rpki::ca::idcert::IdCert;
use rpki::ca::idexchange::{
    CaHandle, ChildHandle, ParentHandle, PublisherHandle, RepoInfo,
    ServiceUri,
};
use rpki::ca::provisioning::ResourceClassName;
use rpki::ca::publication::Base64;
use rpki::crypto::PublicKey;
use rpki::repository::resources::ResourceSet;
use rpki::uri;
use serde::{Deserialize, Serialize};
use crate::commons::error::Error;
use crate::commons::KrillResult;
use super::ca::{IdCertInfo, Timestamp};


//------------ Token ---------------------------------------------------------

/// An authentication token.
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct Token(String);

impl From<&str> for Token {
    fn from(s: &str) -> Self {
        Token(s.to_string())
    }
}

impl From<String> for Token {
    fn from(s: String) -> Self {
        Token(s)
    }
}

impl AsRef<str> for Token {
    fn as_ref(&self) -> &str {
        &self.0
    }
}

impl fmt::Display for Token {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.0.fmt(f)
    }
}


//------------ PublicationServerUris -----------------------------------------

/// The URIs necessasry to initialise a new publication server.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct PublicationServerUris {
    /// The base URI of the RRDP server.
    pub rrdp_base_uri: uri::Https,

    /// The base URI of the rsync server.
    pub rsync_jail: uri::Rsync,
}


//------------ PublisherSummaryInfo ------------------------------------------

/// The summary of publisher information to be used in the publisher list.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct PublisherSummary {
    /// The publisher handle.
    pub handle: PublisherHandle,
}

impl PublisherSummary {
    pub fn from_handle(handle: PublisherHandle) -> Self {
        PublisherSummary { handle }
    }
}


//------------ PublisherList -------------------------------------------------

/// The list of (all) current publishers.
#[derive(Clone, Eq, Debug, Deserialize, PartialEq, Serialize)]
pub struct PublisherList {
    /// The list of publishers.
    pub publishers: Vec<PublisherSummary>,
}

impl PublisherList {
    pub fn from_slice(publishers: &[PublisherHandle]) -> PublisherList {
        PublisherList {
            publishers: publishers.iter().map(|p| {
                PublisherSummary::from_handle(p.clone())
            }).collect(),
        }
    }
}

impl fmt::Display for PublisherList {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Publishers: ")?;
        let mut first = true;
        for p in &self.publishers {
            if !first {
                write!(f, ", ")?;
            } else {
                first = false;
            }
            write!(f, "{}", p.handle.as_str())?;
        }
        Ok(())
    }
}


//------------ PublisherDetails ----------------------------------------------

/// The details of a single publisher.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct PublisherDetails {
    /// The handle of the publisher the details are for.
    pub handle: PublisherHandle,

    /// The ID certificate for this publisher.
    pub id_cert: IdCertInfo,

    /// The base rsync URI for this publisher.
    pub base_uri: uri::Rsync,

    /// The currently published files.
    pub current_files: Vec<PublishedFile>,
}

impl fmt::Display for PublisherDetails {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        writeln!(f, "handle: {}", self.handle)?;
        writeln!(f, "id: {}", self.id_cert.public_key.key_identifier())?;
        writeln!(f, "base uri: {}", self.base_uri)?;
        writeln!(f, "objects:")?;
        for e in &self.current_files {
            writeln!(f, "  {}", e.uri)?;
        }

        Ok(())
    }
}


//------------ PublishedFile -------------------------------------------------

/// Details about a published object.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct PublishedFile {
    /// The URI identifying the object to be published.
    pub uri: uri::Rsync,

    /// The Base64 encoded content of the object to be published.
    pub base64: Base64,
}


//------------ PublicationServerInfo -----------------------------------------

/// Details of a publication server.
//
//  *Warning:* This type is used in stored state.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct PublicationServerInfo {
    /// The public key used by the publication server.
    pub public_key: PublicKey,

    /// The service URI of the publication server.
    pub service_uri: ServiceUri,
}


//------------ ApiRepositoryContact ------------------------------------------

/// A repository response received from a remote contact.
///
/// This type is provided so that we do not need to change the the API for
/// uploading repository responses as it was prior to 0.10.0.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct ApiRepositoryContact {
    /// The reposuitory response.
    pub repository_response: idexchange::RepositoryResponse,
}


//------------ RepositoryContact ---------------------------------------------

/// A contact with a remote repository.
//
//  *Warning:* This type is used in stored state.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct RepositoryContact {
    /// Information about the remote repository.
    pub repo_info: RepoInfo,

    /// Information about the remote publication server.
    pub server_info: PublicationServerInfo,
}

impl RepositoryContact {
    /// Tries to create a value from a remote repository response.
    pub fn try_from_response(
        repository_response: idexchange::RepositoryResponse,
    ) -> KrillResult<Self> {
        let id_cert = repository_response.validate().map_err(Error::rfc8183)?;

        Ok(RepositoryContact {
            repo_info: repository_response.repo_info().clone(),
            server_info: PublicationServerInfo {
                public_key: id_cert.public_key().clone(),
                service_uri: repository_response.service_uri().clone(),
            },
        })
    }
}

impl From<RepositoryContact> for RepoInfo {
    fn from(contact: RepositoryContact) -> Self {
        contact.repo_info
    }
}

impl fmt::Display for RepositoryContact {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "publication server at {}", self.server_info.service_uri)
    }
}

// XXX This impl violates the rule that if k1 == k2 -> hash(k1) == hash(k2).

impl std::hash::Hash for RepositoryContact {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        self.server_info.service_uri.as_str().hash(state); // unique for each
                                                           // repo contact
    }
}

impl PartialEq for RepositoryContact {
    fn eq(&self, other: &Self) -> bool {
        self.repo_info == other.repo_info
            && self.server_info == other.server_info
    }
}

impl Eq for RepositoryContact {}


//------------ ParentCaReq ---------------------------------------------------

/// All the parent CA details needed to add a parent to a CA.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ParentCaReq {
    /// The child local name for the parent.
    pub handle: ParentHandle,

    /// The parent’s up-down response.
    #[serde(alias = "contact")] // stay backward compatible to pre 0.10.0
    pub response: idexchange::ParentResponse,
}

impl fmt::Display for ParentCaReq {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "parent '{}' contact '{}'", self.handle, self.response)
    }
}


//------------ ParentServerInfo ----------------------------------------------

/// Information about the server of the parent CA.
//
//  *Warning:* This type is used in stored state.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ParentServerInfo {
    /// The URI where the CA needs to send its RFC6492 messages
    pub service_uri: ServiceUri,

    /// The handle the parent CA likes to be called by.
    pub parent_handle: ParentHandle,

    /// The handle the parent CA chose for the child CA.
    pub child_handle: ChildHandle,

    /// The parent's ID cert.
    pub id_cert: IdCertInfo,
}

impl fmt::Display for ParentServerInfo {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        writeln!(f, "service uri:    {}", self.service_uri)?;
        writeln!(f, "parent handle:  {}", self.parent_handle)?;
        writeln!(f, "child handle:   {}", self.child_handle)?;
        writeln!(f, "parent certificate:")?;
        writeln!(
            f,
            "   key identifier: {}",
            self.id_cert.public_key.key_identifier()
        )?;
        writeln!(f, "   hash (of cert): {}", self.id_cert.hash)?;
        writeln!(f, "   PEM:\n\n{}", self.id_cert.pem())
    }
}


//------------ ParentCaContact -----------------------------------------------

/// Information to contact the parent CA for resource provisioning requests.
///
/// Note that this used to include other, now deprecated, options.
/// It is still an enum for backward compatibility without the need for
/// a data migration of past events, and because theoretically we may
/// need other options in future if there is an alternative to RFC 6492
/// one day.
//
//  *Warning:* This type is used in stored state.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[allow(clippy::large_enum_variant)]
#[serde(rename_all = "snake_case")]
#[serde(tag = "type")]
pub enum ParentCaContact {
    /// A parent CA contact has to be made via RFC 6492.
    Rfc6492(ParentServerInfo),
}

impl ParentCaContact {
    /// Tries creating a parent CA contact from an RFC 8183 parent response.
    pub fn try_from_rfc8183_parent_response(
        response: idexchange::ParentResponse,
    ) -> Result<Self, idexchange::Error> {
        let id_cert = response.validate()?;
        let id_cert = IdCertInfo::from(&id_cert);

        let service_uri = response.service_uri().clone();
        let parent_handle = response.parent_handle().clone();
        let child_handle = response.child_handle().clone();

        Ok(ParentCaContact::Rfc6492(ParentServerInfo {
            service_uri,
            parent_handle,
            child_handle,
            id_cert,
        }))
    }

    /// Returns a reference to the parent server information.
    pub fn parent_server_info(&self) -> &ParentServerInfo {
        match &self {
            ParentCaContact::Rfc6492(info) => info,
        }
    }

    /// Returns a reference to the parent server’s service URI.
    pub fn parent_uri(&self) -> &idexchange::ServiceUri {
        match self {
            ParentCaContact::Rfc6492(parent) => &parent.service_uri,
        }
    }
}

impl fmt::Display for ParentCaContact {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            ParentCaContact::Rfc6492(response) => response.fmt(f),
        }
    }
}


//------------ StorableParentContact -----------------------------------------

/// The protocol to use when contacting a parent.
///
/// This type is used when saving and presenting the command history.
//
//  *Warning:* This type is used in stored state.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum StorableParentContact {
    Rfc6492,
}

impl fmt::Display for StorableParentContact {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            StorableParentContact::Rfc6492 => write!(f, "RFC 6492 Parent"),
        }
    }
}

impl From<ParentCaContact> for StorableParentContact {
    fn from(parent: ParentCaContact) -> Self {
        match parent {
            ParentCaContact::Rfc6492(_) => StorableParentContact::Rfc6492,
        }
    }
}


//------------ CertAuthInit --------------------------------------------------

/// Information to initialize a CA.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct CertAuthInit {
    /// The local handle identifying the CA.
    pub handle: CaHandle,
}

impl fmt::Display for CertAuthInit {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.handle)
    }
}


//------------ AddChildRequest -----------------------------------------------

/// Information necessary to request adding a child CA.
//
//  *Warning:* This type is used in stored state.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct AddChildRequest {
    /// The handle to identify the child with.
    pub handle: ChildHandle,

    /// The resources the child should have.
    pub resources: ResourceSet,

    /// The ID certificate the child will use for communication.
    pub id_cert: IdCert,
}

impl fmt::Display for AddChildRequest {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "handle '{}' resources '{}'", self.handle, self.resources,)
    }
}


//------------ UpdateChildRequest --------------------------------------------

/// Information for a request to update a child.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct UpdateChildRequest {
    /// The new ID certificate of the child.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub id_cert: Option<IdCert>,

    /// The new resources of the child.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub resources: Option<ResourceSet>,

    /// Whether to (un)suspend a child.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub suspend: Option<bool>,

    /// Changes to the names of a resource class.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub resource_class_name_mapping: Option<ResourceClassNameMapping>,
}

impl UpdateChildRequest {
    /// Creates a child update request that only changes the ID certificate.
    pub fn id_cert(id_cert: IdCert) -> Self {
        UpdateChildRequest {
            id_cert: Some(id_cert),
            resources: None,
            suspend: None,
            resource_class_name_mapping: None,
        }
    }

    /// Creates a child update request that only changes the resources.
    pub fn resources(resources: ResourceSet) -> Self {
        UpdateChildRequest {
            id_cert: None,
            resources: Some(resources),
            suspend: None,
            resource_class_name_mapping: None,
        }
    }

    /// Creates a child update request that suspends the client.
    pub fn suspend() -> Self {
        UpdateChildRequest {
            id_cert: None,
            resources: None,
            suspend: Some(true),
            resource_class_name_mapping: None,
        }
    }

    /// Creates a child update request that unsuspends the client.
    pub fn unsuspend() -> Self {
        UpdateChildRequest {
            id_cert: None,
            resources: None,
            suspend: Some(false),
            resource_class_name_mapping: None,
        }
    }

    /// Creates a child update request that changes resource name mapping.
    pub fn resource_class_name_mapping(
        mapping: ResourceClassNameMapping,
    ) -> Self {
        UpdateChildRequest {
            id_cert: None,
            resources: None,
            suspend: None,
            resource_class_name_mapping: Some(mapping),
        }
    }
}

impl fmt::Display for UpdateChildRequest {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        if self.id_cert.is_some() {
            write!(f, "new id cert ")?;
        }
        if let Some(resources) = &self.resources {
            write!(f, "new resources: {resources} ")?;
        }
        if let Some(suspend) = self.suspend {
            write!(f, "change suspend status to: {suspend}")?;
        }
        Ok(())
    }
}

//------------ ResourceClassNameMapping --------------------------------------

/// A mapping from the name of a resource class in parent and child.
//
//  *Warning:* This type is used in stored state.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ResourceClassNameMapping {
    /// The name of the resource class at the parent.
    pub name_in_parent: ResourceClassName,

    /// The name of the resource class at the child.
    pub name_for_child: ResourceClassName,
}


//------------ ServerInfo ----------------------------------------------------

/// Information about this Krill server.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ServerInfo {
    /// The server software version.
    pub version: String,

    /// The time currently running server was started.
    pub started: Timestamp,
}

impl fmt::Display for ServerInfo {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "Version: {}\nStarted: {}",
            self.version,
            self.started.into_rfc3339()
        )
    }
}


//------------ RepoFileDeleteCriteria ----------------------------------------

/// Criteria for selectively deleting repository files.
///
/// This type is used to send criteria for purging matching files from the
/// publication server. Currently only needs to support `base_uri` but it
/// could be extended in future and therefore we introduce a type for it now.
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct RepoFileDeleteCriteria {
    /// The base rsync URI of the file to be deleted.
    pub base_uri: uri::Rsync,
}


//============ Tests =========================================================

#[cfg(test)]
mod tests {
    use std::path::PathBuf;
    use std::str::FromStr;
    use super::*;

    #[test]
    fn should_accept_rfc8183_handle() {
        // See appendix A of RFC8183
        // handle  = xsd:string { maxLength="255" pattern="[\-_A-Za-z0-9/]*" }
        CaHandle::from_str("abcDEF012/\\-_").unwrap();
    }

    #[test]
    fn should_reject_invalid_handle() {
        // See appendix A of RFC8183
        // handle  = xsd:string { maxLength="255" pattern="[\-_A-Za-z0-9/]*" }
        assert!(CaHandle::from_str("&").is_err());
    }

    #[test]
    fn should_make_file_system_safe() {
        let handle = CaHandle::from_str("abcDEF012/\\-_").unwrap();
        let expected_path_buf = PathBuf::from("abcDEF012+=-_");
        assert_eq!(handle.to_path_buf(), expected_path_buf);
    }

    #[test]
    fn should_make_handle_from_dir() {
        let path = PathBuf::from("a/b/abcDEF012+=-_");
        let handle = CaHandle::try_from(&path).unwrap();
        let expected_handle = CaHandle::from_str("abcDEF012/\\-_").unwrap();
        assert_eq!(handle, expected_handle);
    }
}

