use std::fmt;
use std::time::SystemTime;

use sequoia_openpgp as openpgp;
use openpgp::cert::ValidCert;
use openpgp::Fingerprint;
use openpgp::KeyHandle;
use openpgp::KeyID;

use crate::UserIDSynopsis;
use crate::RevocationStatus;

/// Encapsulates an OpenPGP certificate.
///
/// This holds the information about a certificate that is relevant
/// to web of trust calculations.
#[derive(Debug, Clone)]
pub struct CertSynopsis {
    fingerprint: Fingerprint,
    // The certificate's expiration time as of the reference time.
    // This is only used as a boolean, but preserving the actual time
    // is useful for debugging.
    expiration_time: Option<SystemTime>,
    revocation_status: RevocationStatus,
    userids: Vec<UserIDSynopsis>,
}

impl<'a> From<&ValidCert<'a>> for CertSynopsis {
    fn from(vc: &ValidCert<'a>) -> Self {
        CertSynopsis {
            fingerprint: vc.fingerprint(),
            expiration_time: vc.primary_key().key_expiration_time(),
            revocation_status: vc.revocation_status().into(),
            userids: vc.userids().map(|ua| ua.into()).collect(),
        }
    }
}

impl<'a> From<ValidCert<'a>> for CertSynopsis {
    fn from(vc: ValidCert<'a>) -> Self {
        (&vc).into()
    }
}

impl fmt::Display for CertSynopsis {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_fmt(
            format_args!(
                "{} ({})",
                self.fingerprint,
                self.primary_userid()
                    .map(|userid| {
                        format!("{}{}",
                                String::from_utf8_lossy(userid.value()),
                                match userid.revocation_status() {
                                    RevocationStatus::NotAsFarAsWeKnow => "",
                                    RevocationStatus::Hard =>
                                        " (hard revoked)",
                                    RevocationStatus::Soft(_t) =>
                                        " (soft revoked)",
                                })
                    })
                    .unwrap_or_else(|| "<No User IDs>".into())))
    }
}

impl CertSynopsis {
    /// Returns a new CertSynopsis.
    ///
    /// User IDs are the certificate's valid (not revoked),
    /// self-signed User IDs.
    ///
    /// The first User ID must be the primary User ID.
    ///
    /// # Examples
    ///
    /// ```
    /// use std::iter;
    ///
    /// use sequoia_openpgp as openpgp;
    /// use openpgp::Fingerprint;
    /// use openpgp::parse::Parse;
    ///
    /// use sequoia_wot::CertSynopsis;
    /// use sequoia_wot::UserIDSynopsis;
    /// use sequoia_wot::RevocationStatus;
    ///
    /// let alice_fpr: Fingerprint =
    ///     "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
    ///    .parse().expect("valid fingerprint");
    /// let alice_uid
    ///     = UserIDSynopsis::from("<alice@example.org>");
    ///
    /// CertSynopsis::new(
    ///     alice_fpr, None, RevocationStatus::NotAsFarAsWeKnow,
    ///     iter::once(alice_uid));
    /// ```
    pub fn new<I, U>(fingerprint: Fingerprint,
                     expiration_time: Option<SystemTime>,
                     revocation_status: RevocationStatus,
                     userids: I)
        -> Self
        where I: Iterator<Item=U>,
              U: Into<UserIDSynopsis>,
    {
        Self {
            fingerprint,
            expiration_time,
            revocation_status,
            userids: userids.map(Into::into).collect()
        }
    }

    /// Returns the certificate's fingerprint.
    pub fn fingerprint(&self) -> Fingerprint {
        self.fingerprint.clone()
    }

    /// Returns the certificate's Key ID.
    pub fn keyid(&self) -> KeyID {
        KeyID::from(&self.fingerprint)
    }

    /// Returns the certificate's key handle.
    pub fn key_handle(&self) -> KeyHandle {
        KeyHandle::from(&self.fingerprint)
    }

    /// Returns the expiration time.
    pub fn expiration_time(&self) -> Option<SystemTime> {
        self.expiration_time.clone()
    }

    /// Returns the certificate's revocation status.
    pub fn revocation_status(&self) -> RevocationStatus {
        self.revocation_status.clone()
    }

    /// Returns the certificate's primary User ID, if any.
    pub fn primary_userid(&self) -> Option<&UserIDSynopsis> {
        self.userids().next()
    }

    /// Returns an iterator over the certificate's User IDs.
    ///
    /// Only valid, self-signed User IDs are returned.
    ///
    /// The primary User ID is returned first.
    pub fn userids(&self) -> impl Iterator<Item=&UserIDSynopsis> {
        self.userids.iter()
    }

    /// Return a human readable identifier that may not uniquely
    /// identify the certificate.
    ///
    /// This is useful for debugging.
    pub(crate) fn display(&self) -> String {
        self.primary_userid()
            .map(|userid| {
                String::from_utf8_lossy(userid.value()).into_owned()
            })
            .unwrap_or_else(|| "<No User IDs>".into())
    }
}
