use std::cmp::Ordering;
use std::collections::HashMap;
use std::collections::hash_map::Entry;
use std::fmt;
use std::time::SystemTime;
use std::time::Duration;

use sequoia_openpgp as openpgp;
use openpgp::packet::UserID;
use openpgp::regex::RegexSet;
use openpgp::packet::Signature;

use crate::CertSynopsis;
use crate::Result;

/// Trust depth.
///
/// A certification may include a [trust signature subpacket], which
/// specifies that the issuer not only considers the target binding to
/// be correct, but that they are also will to rely on certifications
/// that the target certificate makes.  That is, if Alice designates
/// Bob as a trusted introducer, than if Carol is willing to rely on
/// Alice's certifications, she should also be willing to rely on
/// Bob's.
///
/// The trust depth is one of two parameters stored in the trust
/// signature subpacket, and indicates how far that capability may be
/// delegated.  A value of zero means that the certification is
/// actually a normal certification, and the target is *not* a trusted
/// introducer.  A value of one means that the target certificate
/// should be considered a trusted introducer, but it may not further
/// delegate that capability.  A value of two means that the target
/// certificate should be considered a trusted introducer, and that it
/// may delegate that capability to another certificate, but they may
/// not further delegate it.  In short, a value of `n` means that
/// there may be up to `n` intervening trusted introducers between the
/// issuer and the target binding:
///
///   - 0: Normal certification.
///   - 1: Trusted introducer.
///   - 2: Meta-introducer.
///   - etc.
///
/// A value of 255, the maximum value that can be stored in the
/// OpenPGP data structure, has a special meaning: the issuer does not
/// impose a constraint on the number of delegations.
///
/// This data structure does not automatically convert a value of 255
/// to `Depth::Unconstrained`; the caller must specify
/// `Depth::Unconstrained` explicitly.
///
///   [trust signature subpacket]: https://datatracker.ietf.org/doc/html/rfc4880#section-5.2.3.13
#[derive(Debug, Clone, Copy, Eq)]
pub enum Depth {
    Unconstrained,
    Limit(usize),
}

impl Depth {
    pub fn new<I>(depth: I) -> Self
        where I: Into<Option<usize>>
    {
        if let Some(d) = depth.into() {
            Depth::Limit(d)
        } else {
            Depth::Unconstrained
        }
    }

    /// Returns an unconstrained `Depth`.
    pub fn unconstrained() -> Self {
        Depth::Unconstrained
    }

    /// Returns whether this `Depth` is unconstrained.
    pub fn is_unconstrained(&self) -> bool {
        matches!(self, Depth::Unconstrained)
    }

    /// Converts the `Depth` to an `Option<usize>`.
    ///
    /// An unconstrained depth is converted to `None`.  A constrained
    /// depth of `d` is converted to `Some(d)`.
    pub fn limit(&self) -> Option<usize> {
        match self {
            Depth::Unconstrained => None,
            Depth::Limit(d) => Some(*d),
        }
    }

    /// Decreases the depth by `value`.
    ///
    /// The depth must be at least as large as `value`.  If the depth
    /// is unconstrained, decreasing the depth doesn't do anything.
    pub fn decrease(&self, value: usize) -> Depth {
        match self {
            Depth::Unconstrained => {
                // Still unconstrained.
                Depth::Unconstrained
            }
            Depth::Limit(d) => {
                assert!(*d >= value);
                Depth::Limit(d - value)
            }
        }
    }
}

impl fmt::Display for Depth {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Depth::Unconstrained => write!(f, "unconstrained"),
            Depth::Limit(d) => write!(f, "{}", d),
        }
    }
}

impl From<usize> for Depth {
    fn from(d: usize) -> Self {
        Depth::new(d)
    }
}

impl From<Option<usize>> for Depth {
    fn from(d: Option<usize>) -> Self {
        Depth::new(d)
    }
}

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

impl Ord for Depth {
    fn cmp(&self, other: &Self) -> Ordering {
        match (self, other) {
            (Depth::Unconstrained, Depth::Unconstrained) => Ordering::Equal,
            (Depth::Limit(_), Depth::Unconstrained) => Ordering::Less,
            (Depth::Unconstrained, Depth::Limit(_)) => Ordering::Greater,
            (Depth::Limit(x), Depth::Limit(y)) => x.cmp(&y),
        }
    }
}

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

/// Encapsulates a certification.
///
/// This data structure holds the information about a certification
/// that is relevant to web of trust calculations.  Note: this data
/// struct includes the certification's context (the issuer and the
/// certified binding), which is not included in an OpenPGP signature.
///
/// If the User ID is None, then this is a delegation.
#[derive(Clone)]
pub struct Certification {
    issuer: CertSynopsis,
    target: CertSynopsis,
    // If None, it's a delegation.
    userid: Option<UserID>,

    creation_time: SystemTime,
    expiration_time: Option<SystemTime>,

    exportable: bool,

    /// 60: partial trust.
    /// 120: complete trust.
    amount: usize,

    /// Trust depth.
    depth: Depth,

    /// Scope.
    re_set: RegexSet,
}

impl fmt::Debug for Certification {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("Certification")
            .field("issuer", &self.issuer.fingerprint())
            .field("target", &self.target)
            .field("userid",
                   &self.userid.as_ref().map(|uid| {
                       String::from_utf8_lossy(uid.value()).into_owned()
                   })
                   .unwrap_or_else(|| "<None>".into()))
            .field("creation time",
                   &self.creation_time
                       .duration_since(SystemTime::UNIX_EPOCH)
                       .unwrap_or_else(|_| Duration::new(0, 0)))
            .field("expiration time",
                   &if let Some(e) = self.expiration_time {
                       format!("{:?}",
                                e
                                .duration_since(SystemTime::UNIX_EPOCH)
                                .unwrap_or_else(|_| Duration::new(0, 0)))
                   } else {
                       "never".to_string()
                   })
            .field("amount", &self.amount)
            .field("depth", &self.depth)
            .field("regexes",
                   &if self.re_set.matches_everything() {
                       String::from("*")
                   } else {
                       format!("{:?}", &self.re_set)
                   })
            .finish()
    }
}

impl Certification {
    /// Returns a `Certification`.
    ///
    /// The returned certification's amount is set to 120 (fully
    /// trusted), its depth to 0 (it's not a trusted introducer), and
    /// no regular expression is set.
    ///
    /// # Examples
    ///
    /// `0xAA` (bob@example.org) certifies the binding `<0xBB,
    /// bob@example.org>`.
    ///
    /// ```
    /// use std::iter;
    /// use std::time::SystemTime;
    ///
    /// use sequoia_openpgp as openpgp;
    /// use openpgp::Fingerprint;
    /// use openpgp::parse::Parse;
    ///
    /// use sequoia_wot::CertSynopsis;
    /// use sequoia_wot::UserIDSynopsis;
    /// use sequoia_wot::Certification;
    /// use sequoia_wot::RevocationStatus;
    ///
    /// let alice_fpr: Fingerprint =
    ///     "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
    ///    .parse().expect("valid fingerprint");
    /// let alice_uid
    ///     = UserIDSynopsis::from("<alice@example.org>");
    ///
    /// let bob_fpr: Fingerprint =
    ///     "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"
    ///    .parse().expect("valid fingerprint");
    /// let bob_uid
    ///     = UserIDSynopsis::from("<bob@example.org>");
    ///
    /// let alice = CertSynopsis::new(
    ///     alice_fpr, None, RevocationStatus::NotAsFarAsWeKnow,
    ///     iter::once(alice_uid));
    /// let bob = CertSynopsis::new(
    ///     bob_fpr, None, RevocationStatus::NotAsFarAsWeKnow,
    ///     iter::once(bob_uid.clone()));
    ///
    /// let c = Certification::new(
    ///     alice, Some(bob_uid.userid().clone()), bob,
    ///     SystemTime::now());
    /// ```
    pub fn new<C1, U, C2>(issuer: C1,
                          userid: Option<U>,
                          target: C2,
                          creation_time: SystemTime)
        -> Result<Self>
        where C1: Into<CertSynopsis>,
              U: Into<UserID>,
              C2: Into<CertSynopsis>,
    {
        let issuer = issuer.into();
        let target = target.into();

        Ok(Certification {
            issuer: issuer,
            userid: userid.map(Into::into),
            target: target,
            creation_time: creation_time,
            expiration_time: None,
            exportable: true,
            depth: Depth::new(0),
            amount: 120,
            re_set: RegexSet::everything()?,
        })
    }

    /// Creates a `Certification` from a `Signature`.
    ///
    /// `userid` and `target` are the certified binding.  If no User
    /// ID is supplied, this is interpreted as a delegation.
    ///
    /// The signature is assumed to be valid.
    ///
    /// If the signature does not have a signature creation time
    /// (which technically makes the signature [invalid]), it defaults
    /// to the [Unix epoch].
    ///
    ///   [invalid]: https://datatracker.ietf.org/doc/html/rfc4880#section-5.2.3.4
    ///   [Unix epoch]: https://en.wikipedia.org/wiki/Unix_time
    pub fn from_signature<C1, U, C2>(issuer: C1,
                                     userid: Option<U>,
                                     target: C2,
                                     sig: &Signature)
        -> Result<Self>
        where C1: Into<CertSynopsis>,
              U: Into<UserID>,
              C2: Into<CertSynopsis>,
    {
        let (d, a, r) = if let Some((d, a)) = sig.trust_signature()
        {
            (d as usize, a as usize, Some(RegexSet::from_signature(sig)?))
        } else {
            (0, 120, None)
        };

        let mut c = Self::new(issuer, userid, target,
                              sig.signature_creation_time()
                                  .unwrap_or(std::time::UNIX_EPOCH))?
            .set_amount(a)
            .set_depth(Depth::new(if (d == 255) { None } else { Some(d) }));
        if let Some(r) = r {
            c = c.set_regular_expressions(r);
        }
        if let Some(e) = sig.signature_expiration_time() {
            c = c.set_expiration_time(Some(e));
        }
        c = c.set_exportable(sig.exportable_certification().unwrap_or(true));

        Ok(c)
    }

    /// Returns the certification's issuer.
    pub fn issuer(&self) -> &CertSynopsis {
        &self.issuer
    }

    /// Returns the certification's target certificate.
    pub fn target(&self) -> &CertSynopsis {
        &self.target
    }

    /// Returns the certification's target UserID, if any.
    pub fn userid(&self) -> Option<&UserID> {
        self.userid.as_ref()
    }

    /// Returns the certification's creation time.
    pub fn creation_time(&self) -> SystemTime {
        self.creation_time
    }

    /// Returns the certification's expiration time, if any.
    pub fn expiration_time(&self) -> Option<SystemTime> {
        self.expiration_time
    }

    /// Sets the certification's expiration time.
    pub fn set_expiration_time<I>(mut self, expiration_time: I) -> Self
        where I: Into<Option<SystemTime>>
    {
        self.expiration_time = expiration_time.into();
        self
    }

    /// Returns whether the certification is marked as exportable (i.e.,
    /// not a so-called local signature).
    pub fn exportable(&self) -> bool {
        self.exportable
    }

    /// Sets whether the certification is marked as exportable (i.e.,
    /// not a so-called local signature).
    pub fn set_exportable(mut self, exportable: bool) -> Self {
        self.exportable = exportable;
        self
    }

    /// Returns the certification's trust amount.
    pub fn amount(&self) -> usize {
        self.amount
    }

    /// Sets the certification's trust amount.
    pub fn set_amount(mut self, amount: usize) -> Self {
        self.amount = amount;
        self
    }

    /// Returns the certification's trust depth.
    pub fn depth(&self) -> Depth {
        self.depth
    }

    /// Sets the certification's trust depth.
    ///
    /// Note: this function does not automatically convert the value
    /// `255` to `Depth::Unconstrained`.
    pub fn set_depth<I>(mut self, depth: I) -> Self
    where I: Into<Depth>
    {
        self.depth = depth.into();
        self
    }

    /// Returns the certification's regular expressions.
    ///
    /// If the signature has none, this returns a regular expression
    /// that matches everything.
    pub fn regular_expressions(&self) -> &RegexSet {
        &self.re_set
    }

    /// Sets the certification's regular expressions.
    pub fn set_regular_expressions(mut self, re_set: RegexSet) -> Self {
        self.re_set = re_set;
        self
    }
}

/// All active certifications that one certificate made on another.
///
/// Encapsulates all active certifications that a certificate made on
/// another certificate.  For instance, if the certificate 0xB has two
/// User IDs: B and B' and 0xA signed both, then this contains the
/// latest certification for <0xA, B:0xB> and the latest certification
/// for <0xA, B':0xB>.
pub struct CertificationSet {
    // The certificate that issued the certifications.
    issuer: CertSynopsis,
    // The certificate that was signed.
    target: CertSynopsis,

    // The certifications, keyed by the certified (target) User ID.
    // It is reasonable to have multiple certifications over the same
    // User ID if they all have the same timestamp.
    certifications: HashMap<Option<UserID>, Vec<Certification>>,
}

impl CertificationSet {
    /// Returns an empty CertificationSet.
    pub(crate) fn empty<I, T>(issuer: I, target: T) -> Self
    where I: Into<CertSynopsis>,
          T: Into<CertSynopsis>,
    {
        Self {
            issuer: issuer.into(),
            target: target.into(),
            certifications: HashMap::new(),
        }
    }

    /// Returns a new CertificationSet with the supplied
    /// certification.
    pub fn from_certification(certification: Certification) -> Self {
        let mut cs = CertificationSet::empty(
            certification.issuer.clone(),
            certification.target.clone());
        cs.add(certification);
        cs
    }

    pub fn issuer(&self) -> &CertSynopsis {
        &self.issuer
    }

    pub fn target(&self) -> &CertSynopsis {
        &self.target
    }

    /// Adds a certification to the CertificationSet.
    ///
    /// All certifications in a `CertificationSet` must be issued by
    /// the same certificate.
    ///
    /// Note: if there are multiple certifications for the same User
    /// ID, all are considered.  Normally only the newest
    /// certification should be considered.  But, there may be
    /// multiple such certifications.
    pub(crate) fn add(&mut self, certification: Certification) {
        // certification must be over the same certificate.
        if let Some((_, cs)) = self.certifications.iter().next() {
            for c in cs {
                assert_eq!(certification.issuer.fingerprint(),
                           c.issuer.fingerprint());
                assert_eq!(certification.target.fingerprint(),
                           c.target.fingerprint());
            }
        }

        match self.certifications.entry(certification.userid.clone()) {
            e @ Entry::Occupied(_) => {
                e.and_modify(|e| e.push(certification));
            }
            e @ Entry::Vacant(_) => {
                e.or_insert([ certification ].into());
            }
        }
    }

    /// Merges other into self.
    ///
    /// This function asserts that `self` and `other` are for the same
    /// issuer and target certificates.
    ///
    /// Note: if there are multiple certifications for the same User
    /// ID, all are considered.  Normally only the newest
    /// certification should be considered.  But, there may be
    /// multiple such certifications.
    pub(crate) fn merge(&mut self, other: Self) {
        assert_eq!(self.issuer.fingerprint(), other.issuer.fingerprint());
        assert_eq!(self.target.fingerprint(), other.target.fingerprint());

        for (_, cs) in other.certifications.into_iter() {
            for c in cs {
                self.add(c);
            }
        }
    }

    pub(crate) fn certifications(&self)
        -> impl Iterator<Item=(Option<&UserID>, &[Certification])>
    {
        self.certifications.iter().map(|(userid, c)| (userid.as_ref(), &c[..]))
    }
}

#[cfg(test)]
mod test {
    use sequoia_openpgp as openpgp;
    use openpgp::Result;

    use crate::Depth;

    #[test]
    fn depth() -> Result<()> {
        assert_eq!(Depth::new(0), Depth::new(0));
        assert_eq!(Depth::new(10), Depth::new(10));
        assert_eq!(Depth::new(None), Depth::new(None));

        assert!(Depth::new(0) < Depth::new(1));
        assert!(Depth::new(1) < Depth::new(10));
        assert!(Depth::new(10) < Depth::new(None));
        assert!(Depth::new(255) < Depth::new(None));
        assert!(Depth::new(1000) < Depth::new(None));

        assert!(Depth::new(1) > Depth::new(0));
        assert!(Depth::new(10) > Depth::new(1));
        assert!(Depth::new(None) > Depth::new(10));
        assert!(Depth::new(None) > Depth::new(255));
        assert!(Depth::new(None) > Depth::new(1000));

        assert_eq!(std::cmp::min(Depth::new(0), Depth::new(10)),
                   Depth::new(0));
        assert_eq!(std::cmp::min(Depth::new(0), Depth::new(None)),
                   Depth::new(0));
        assert_eq!(std::cmp::min(Depth::new(1000), Depth::new(None)),
                   Depth::new(1000));

        assert_eq!(std::cmp::min(Depth::new(10), Depth::new(0)),
                   Depth::new(0));
        assert_eq!(std::cmp::min(Depth::new(None), Depth::new(0)),
                   Depth::new(0));
        assert_eq!(std::cmp::min(Depth::new(None), Depth::new(1000)),
                   Depth::new(1000));

        assert_eq!(std::cmp::min(Depth::new(None), Depth::new(None)),
                   Depth::new(None));

        Ok(())
    }
}
