use std::collections::BTreeSet;
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;

const TRACE: bool = false;

/// 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 {
        tracer!(TRACE, "CertSynopsis::from(ValidCert)");
        t!("Creating CertSynopsis for {}", vc.fingerprint());

        let mut self_signed_userids = BTreeSet::new();

        let mut userids = Vec::with_capacity(vc.userids().count());
        for ua in vc.userids() {
            t!("  Self-signed user ID: {}",
               String::from_utf8_lossy(ua.userid().value()));

            self_signed_userids.insert(ua.userid().value().to_vec());
            userids.push(ua.into());
        }

        for ua in vc.cert().userids() {
            if self_signed_userids.contains(ua.userid().value()) {
                // Already added.
                continue;
            }

            let rs = ua.revocation_status(vc.policy(), vc.time());
            if matches!(rs, openpgp::types::RevocationStatus::Revoked(_)) {
                t!("  Adding non-self-signed, self-revoked user ID: {}",
                   String::from_utf8_lossy(ua.userid().value()));

                userids.push(UserIDSynopsis::new(
                    ua.userid().clone(),
                    None,
                    rs.into()));
            }
        }

        CertSynopsis {
            fingerprint: vc.fingerprint(),
            expiration_time: vc.primary_key().key_expiration_time(),
            revocation_status: vc.revocation_status().into(),
            userids,
        }
    }
}

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.self_signed_userids().next()
    }

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

    /// Deprecated alias for [`CertSynopsis::self_signed_userids`].
    #[deprecated(since="0.15.2", note="please use `self_signed_userids` instead")]
    pub fn userids(&self) -> impl Iterator<Item=&UserIDSynopsis> {
        self.self_signed_userids()
    }

    /// Returns an iterator over the certificate's self-signed user
    /// IDs and revoked user IDs.
    ///
    /// This returns both self-signed user IDs and user IDs that are
    /// revoked, but aren't self signed.
    ///
    /// The primary user ID is returned first.
    pub fn self_signed_and_revoked_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())
    }
}
