use std::borrow::Cow;
use std::time::SystemTime;
use std::sync::OnceLock;

use sequoia_openpgp as openpgp;
use openpgp::Cert;
use openpgp::Fingerprint;
use openpgp::KeyHandle;
use openpgp::KeyID;
use openpgp::Result;
use openpgp::cert::raw::RawCert;
use openpgp::cert::ValidCert;
use openpgp::packet::Key;
use openpgp::packet::UserID;
use openpgp::packet::key;
use openpgp::policy::Policy;
use openpgp::serialize::SerializeInto;

use super::TRACE;

// Needed for the doc comments.
#[allow(unused_imports)]
use crate::store::Store;

/// Stores either a `RawCert` or a parsed `Cert`.
///
/// This allows the implementation to defer fully parsing and
/// validating the certificate until it is actually needed.
///
/// # Examples
///
/// Functions like [`Store::lookup_by_cert`] return an
/// `Arc<LazyCert>`.  Due to the [orphan rule], it is not possible for
/// this library to provide an implementation of
/// `TryFrom<Arc<LazyCert>> for Cert`.  So instead of using `TryFrom`,
/// you have to do the conversion manually as follows:
///
///   [orphan rule]: https://doc.rust-lang.org/book/ch10-02-traits.html#implementing-a-trait-on-a-type
///
/// ```
/// # use std::sync::Arc;
/// #
/// # use sequoia_openpgp as openpgp;
/// # use openpgp::cert::prelude::*;
/// # use openpgp::Result;
/// #
/// # use sequoia_cert_store as cert_store;
/// # use cert_store::LazyCert;
/// #
/// # fn main() -> Result<()> {
/// #
/// # let (cert, _) =
/// #     CertBuilder::general_purpose(Some("alice@example.org"))
/// #         .generate()?;
/// // A LazyCert as returned by, e.g., `Store::lookup_by_cert`:
/// let lc: Arc<LazyCert> = Arc::new(LazyCert::from(cert));
///
/// // If you only need a reference:
/// let cert: &Cert = lc.to_cert()?;
///
/// // If you need an owned value:
/// let cert: Cert = lc.to_cert()?.clone();
/// # Ok(()) }
/// ```
#[derive(Clone)]
pub struct LazyCert<'a> {
    // At least one of raw and cert are ever alive.  Ideally, we'd put
    // them in an enum.  To do that, the enum would have to be behind
    // a `RefCell`, but then we couldn't return bare references to the
    // `Cert`.
    raw: OnceLock<RawCert<'a>>,
    cert: OnceLock<Cow<'a, Cert>>,
}
assert_send_and_sync!(LazyCert<'_>);

impl<'a> std::fmt::Debug for LazyCert<'a> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("LazyCert")
            .field("fingerprint", &self.fingerprint())
            .field("subkeys",
                   &self.subkeys().map(|k| k.fingerprint())
                       .collect::<Vec<Fingerprint>>())
            .field("userids",
                   &self.userids().collect::<Vec<UserID>>())
            .finish()
    }
}

impl<'a> LazyCert<'a> {
    /// Returns whether the cert has been parsed.
    pub(crate) fn is_parsed(&self) -> bool {
        self.cert.get().is_some()
    }

    /// Creates a `LazyCert` from a `Cert`.
    pub fn from_cert(cert: Cert) -> Self {
        tracer!(TRACE, "LazyCert::from_cert");
        t!("Adding a parsed cert: {}", cert.fingerprint());

        Self {
            raw: OnceLock::new(),
            cert: OnceLock::from(Cow::Owned(cert)),
        }
    }

    /// Creates a `LazyCert` from a `&Cert`.
    pub fn from_cert_ref(cert: &'a Cert) -> Self {
        tracer!(TRACE, "LazyCert::from_cert_ref");
        t!("Adding a parsed cert: {}", cert.fingerprint());

        Self {
            raw: OnceLock::new(),
            cert: OnceLock::from(Cow::Borrowed(cert)),
        }
    }

    /// Creates a `LazyCert` from a `RawCert`.
    pub fn from_raw_cert(raw: RawCert<'a>) -> Self {
        Self {
            raw: OnceLock::from(raw),
            cert: OnceLock::new(),
        }
    }

    /// Returns a reference to the raw cert, if any.
    ///
    /// If the cert only exists in parsed form, returns `None`.
    pub fn raw_cert(&self) -> Option<&RawCert<'a>> {
        self.raw.get()
    }

    /// Returns the RawCert, if any.
    ///
    /// If the cert exists only in parsed form, returns `Err(self)`.
    #[expect(clippy::result_large_err)]
    pub fn into_raw_cert(mut self) -> std::result::Result<RawCert<'a>, Self> {
        match self.raw.take() {
            Some(raw) => Ok(raw),
            None => Err(self),
        }
    }

    /// Returns the certificate's fingerprint.
    pub fn fingerprint(&self) -> Fingerprint {
        if let Some(cert) = self.cert.get() {
            cert.fingerprint()
        } else if let Some(raw) = self.raw.get() {
            raw.fingerprint()
        } else {
            unreachable!("cert or raw must be set")
        }
    }

    /// 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 user ids.
    pub fn userids(&self)
        -> impl Iterator<Item=UserID> + '_
    {
        if let Some(cert) = self.cert.get() {
            Box::new(cert.userids().map(|ua| ua.userid().clone()))
                as Box<dyn Iterator<Item=UserID> + '_>
        } else if let Some(raw) = self.raw.get() {
            Box::new(raw.userids())
                as Box<dyn Iterator<Item=UserID> + '_>
        } else {
            unreachable!("cert or raw must be set")
        }
    }

    /// Returns the keys.
    pub fn keys(&self)
        -> impl Iterator<Item=Key<key::PublicParts, key::UnspecifiedRole>> + '_
    {
        if let Some(cert) = self.cert.get() {
            Box::new(cert.keys().map(|ka| ka.key().clone()))
                as Box<dyn Iterator<Item=Key<_, _>> + '_>
        } else if let Some(raw) = self.raw.get() {
            Box::new(
                raw
                    .keys()
                    // This is rather unsatisfying, but due to
                    // lifetimes...
                    .collect::<Vec<Key<_, _>>>()
                    .into_iter())
                as Box<dyn Iterator<Item=Key<_, _>> + '_>
        } else {
            unreachable!("cert or raw must be set")
        }
    }

    /// Returns the primary key.
    pub fn primary_key(&self) -> Key<key::PublicParts, key::PrimaryRole> {
        if let Some(cert) = self.cert.get() {
            cert.primary_key().key().clone()
        } else if let Some(raw) = self.raw.get() {
            raw.primary_key()
        } else {
            unreachable!("cert or raw must be set")
        }
    }

    /// Returns the subkeys.
    pub fn subkeys<'b>(&'b self)
        -> impl Iterator<Item=Key<key::PublicParts,
                                  key::UnspecifiedRole>> + 'b
    {
        self.keys().skip(1)
    }

    /// Returns a reference to the parsed certificate.
    ///
    /// If the `LazyCert` is not yet parsed, parses now.
    pub fn to_cert(&self) -> Result<&Cert> {
        tracer!(TRACE, "LazyCert::to_cert");

        if let Some(cert) = self.cert.get() {
            return Ok(cert);
        }

        if let Some(raw) = self.raw.get() {
            t!("Resolving {}", raw.fingerprint());
            match Cert::try_from(raw) {
                Ok(cert) => {
                    // The following will fail, if we lost a race to
                    // parse the raw certificate.  That's okay,
                    // because parsing is deterministic.
                    let _ = self.cert.set(Cow::Owned(cert));
                }
                Err(err) => {
                    return Err(err);
                }
            }
        }

        if let Some(cert) = self.cert.get() {
            return Ok(cert);
        } else {
            unreachable!("cert or raw must be set")
        }
    }

    /// Returns the parsed certificate.
    ///
    /// If the `LazyCert` is not yet parsed, parses now.
    pub fn into_cert(self) -> Result<Cert> {
        let _ = self.to_cert()?;
        Ok(self.cert.into_inner().expect("valid").into_owned())
    }

    /// Associates a policy and a reference time with the certificate.
    ///
    /// See [`Cert::with_policy`].
    pub fn with_policy<'b, T>(&'b self, policy: &'b dyn Policy, time: T)
        -> Result<ValidCert<'b>>
    where
        T: Into<Option<SystemTime>>,
    {
        let cert = self.to_cert()?;
        cert.with_policy(policy, time)
    }

    /// Returns whether the certificate contains any secret key
    /// material.
    pub fn is_tsk(&self) -> bool {
        if let Some(cert) = self.cert.get() {
            cert.is_tsk()
        } else if let Some(raw) = self.raw.get() {
            raw.keys().any(|key| key.has_secret())
        } else {
            unreachable!("cert or raw must be set")
        }
    }
}

impl<'a> From<Cert> for LazyCert<'a> {
    fn from(cert: Cert) -> Self {
        LazyCert::from_cert(cert)
    }
}

impl<'a> From<&'a Cert> for LazyCert<'a> {
    fn from(cert: &'a Cert) -> Self {
        LazyCert::from_cert_ref(cert)
    }
}

impl<'a> From<RawCert<'a>> for LazyCert<'a> {
    fn from(cert: RawCert<'a>) -> Self {
        LazyCert::from_raw_cert(cert)
    }
}

impl<'a> TryFrom<LazyCert<'a>> for Cert {
    type Error = anyhow::Error;

    fn try_from(lc: LazyCert<'a>) -> Result<Self> {
        lc.into_cert()
    }
}

impl<'a, 'b> TryFrom<&'b LazyCert<'a>> for &'b Cert {
    type Error = anyhow::Error;

    fn try_from(lc: &'b LazyCert<'a>) -> Result<Self> {
        lc.to_cert()
    }
}

// We can't implement openpgp::serialize::Marshal, because it is
// sealed.  So we fake what is used :/.
impl<'a> LazyCert<'a> {
    /// Serializes the cert to a vector.
    pub fn to_vec(&self) -> Result<Vec<u8>> {
        if let Some(raw) = self.raw.get() {
            Ok(raw.as_bytes().to_vec())
        } else if let Some(cert) = self.cert.get() {
            Ok(cert.to_vec()?)
        } else {
            unreachable!("raw or cert must be set");
        }
    }

    /// Exports a serialized version of the cert to `o`.
    ///
    /// Non-exportable signatures are not exported, and any component
    /// bound merely by non-exportable signatures is not exported.
    pub fn export(&self, o: &mut dyn std::io::Write) -> Result<()> {
        use openpgp::serialize::Marshal;

        // We need to strip any local signatures.  If we have a
        // RawCert, we could try to figure out if there are any local
        // signatures to avoid parsing and reserializing, but that is
        // error prone.
        let cert = self.to_cert()?;
        Ok(cert.export(o)?)
    }
}
