use std::cmp;
use std::time;

use sequoia_openpgp as openpgp;
use openpgp::types::RevocationStatus as OpenPgpRevocationStatus;
use openpgp::packet::Signature;
use openpgp::types::ReasonForRevocation;

use crate::Result;
use crate::Error;

/// A summary type for OpenPGP's RevocationStatus.
///
/// This type is a summary of OpenPGP's [`RevocationStatus`] type that
/// holds the information that is relevant to web of trust
/// calculations.
///
///   [`RevocationStatus`]: https://docs.rs/sequoia-openpgp/latest/sequoia_openpgp/types/enum.RevocationStatus.html
///
/// `Soft` and `Hard` refer to the two different types of revocations.
/// This mapping is according to
/// [`ReasonForRevocation::revocation_type`].  The `Soft` variant
/// includes the revocation's creation time.  Note: a revocation's
/// expiration time is ignored.
///
///   [`ReasonForRevocation::revocation_type`]: https://docs.rs/sequoia-openpgp/latest/sequoia_openpgp/types/enum.ReasonForRevocation.html#method.revocation_type
///
/// This type implements `PartialEq` in the following way: the
/// stronger a revocation is, the later it sorts.  Thus,
/// `RevocationStatus::NotAsFarAsWeKnow` sorts first and
/// `RevocationStatus::Hard` sorts last.  Two `RevocationStatus::Soft`
/// are ordered by the reverse of their creation time.  Thus, for `t1
/// < t2`, `Soft(t1) > Soft(t2)`.  This is what we want, since
/// `Soft(t1)` invalidates at least as much as `Soft(t2)`.  A
/// consequence of this is that it is possible to use
/// [`std::cmp::max`] to find the most restrictive revocation.
///
/// This type also implements `Default` (it returns
/// `RevocationStatus::NotAsFarAsWeKnow`), `From<[RevocationStatus]>`
/// and `TryFrom<[Signature]>`.
#[derive(Debug, Clone, Eq)]
pub enum RevocationStatus {
    NotAsFarAsWeKnow,
    Soft(time::SystemTime),
    Hard,
}

impl RevocationStatus {
    /// Returns whether the revocation is active as of the reference
    /// time.
    ///
    /// Returns false if this is `RevocationStatus::NotAsFarAsWeKnow`.
    pub fn in_effect(&self, t: time::SystemTime) -> bool {
        match self {
            RevocationStatus::NotAsFarAsWeKnow => false,
            RevocationStatus::Soft(rev_t) => t >= *rev_t,
            RevocationStatus::Hard => true,
        }
    }
}

impl Default for RevocationStatus {
    fn default() -> Self {
        RevocationStatus::NotAsFarAsWeKnow
    }
}

impl Ord for RevocationStatus {
    /// Order so that strong revocations come later.  This means that a
    /// soft revocation with an earlier time sorts after a soft
    /// revocation with a later time.
    fn cmp(&self, other: &Self) -> cmp::Ordering {
        use cmp::Ordering::*;
        use RevocationStatus::*;

        match (self, other) {
            (NotAsFarAsWeKnow, NotAsFarAsWeKnow) => Equal,
            (NotAsFarAsWeKnow, Soft(_)) => Less,
            (NotAsFarAsWeKnow, Hard) => Less,

            (Soft(_), NotAsFarAsWeKnow) => Greater,
            (Soft(t1), Soft(t2)) => t1.cmp(t2).reverse(),
            (Soft(_), Hard) => Less,

            (Hard, NotAsFarAsWeKnow) => Greater,
            (Hard, Soft(_)) => Greater,
            (Hard, Hard) => Equal,
        }
    }
}

impl PartialOrd for RevocationStatus {
    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl PartialEq for RevocationStatus {
    fn eq(&self, other: &Self) -> bool {
        self.cmp(other) == cmp::Ordering::Equal
    }
}

impl<'a> From<&OpenPgpRevocationStatus<'a>> for RevocationStatus {
    fn from(rs: &OpenPgpRevocationStatus<'a>) -> Self {
        match rs {
            OpenPgpRevocationStatus::Revoked(sigs) => {
                sigs.into_iter()
                    .map(|sig| {
                        RevocationStatus::try_from(*sig).expect("revocation")
                    })
                    .max()
                    .expect("revoked, but no revocation certificates")
            }
            OpenPgpRevocationStatus::CouldBe(_) => {
                RevocationStatus::NotAsFarAsWeKnow
            }
            OpenPgpRevocationStatus::NotAsFarAsWeKnow => {
                RevocationStatus::NotAsFarAsWeKnow
            }
        }
    }
}

impl<'a> From<OpenPgpRevocationStatus<'a>> for RevocationStatus {
    fn from(rs: OpenPgpRevocationStatus<'a>) -> Self {
        RevocationStatus::from(&rs)
    }
}

impl TryFrom<&Signature> for RevocationStatus {
    type Error = anyhow::Error;

    fn try_from(sig: &Signature) -> Result<Self> {
        use openpgp::types::SignatureType;
        use openpgp::types::RevocationType;

        let rev_type = match sig.typ() {
            SignatureType::KeyRevocation
            | SignatureType::SubkeyRevocation
            | SignatureType::CertificationRevocation => {
                let r: Option<ReasonForRevocation>
                    = sig.reason_for_revocation().map(|(r, _msg)| r);
                match r {
                    None => RevocationType::Hard,
                    Some(reason) => reason.revocation_type(),
                }
            }

            // Not a revocation.
            _ => return Err(Error::NotARevocationCertificate.into()),
        };

        let rs = match rev_type {
            RevocationType::Hard => RevocationStatus::Hard,
            RevocationType::Soft =>
                RevocationStatus::Soft(
                    sig.signature_creation_time()
                        .unwrap_or(time::UNIX_EPOCH)),
        };

        Ok(rs)
    }
}
