use std::borrow::Cow;
use std::collections::btree_map;
use std::iter;
use std::str;
use std::sync::Arc;
use std::time::{Duration, SystemTime};

use anyhow::Context;

use sequoia_openpgp as openpgp;
use openpgp::Packet;
use openpgp::Result;
use openpgp::cert::prelude::*;
use openpgp::packet::UserID;
use openpgp::packet::signature::SignatureBuilder;
use openpgp::parse::Parse;
use openpgp::serialize::SerializeInto;
use openpgp::types::KeyFlags;
use openpgp::types::SignatureType;

use openpgp_cert_d as cert_d;

use crate::LazyCert;
use crate::store::Store;
use crate::store::StoreUpdate;

use crate::TRACE;

use super::CertD;

/// The creation time for the trust root and intermediate CAs.
///
/// We use a creation time in the past (Feb 2002) so that it is still
/// possible to use the CA when the reference time is in the past.
fn ca_creation_time() -> SystemTime {
    SystemTime::UNIX_EPOCH + Duration::new(1014235320, 0)
}

pub(super) struct CA<'a> {
    // The tag of the file stored under the special name.
    special_tag: cert_d::Tag,

    // The cert as stored under the special name.
    special: Cert,

    // The cert as stored under its fingerprint.
    public: Arc<LazyCert<'a>>,

    // The merged certificate.  If special and public are up to date,
    // then we can avoid the merge.
    merged: Arc<LazyCert<'a>>,
}
assert_send_and_sync!(CA<'_>);

impl<'a> CertD<'a> {
    /// Returns the certificate directory's local trust root, and
    /// whether it was just created.
    ///
    /// If the local trust root does not yet exist, it is created.
    pub fn trust_root(&self) -> Result<(Arc<LazyCert<'a>>, bool)> {
        self.get_ca(cert_d::TRUST_ROOT,
                    true,
                    &UserID::from_static_bytes(b"Local Trust Root"),
                    None)
    }

    /// Converts a shadow CA's name to a certificate directory's
    /// special name.
    ///
    /// For instance, this converts "keys.openpgp.org" to its special
    /// name.
    fn shadow_ca_special(name: &'_ str) -> Cow<'_, str> {
        if name == cert_d::TRUST_ROOT {
            Cow::Borrowed(name)
        } else {
            Cow::Owned(format!("_sequoia_ca_{}.pgp", name))
        }
    }

    // '/', '\\', ':', and whitespace are invalid.
    fn valid_name(name: &str) -> bool {
        name.chars().all(Self::valid_char)
    }

    /// Returns whether `c` may be used in names.
    fn valid_char(c: char) -> bool {
        // The list of reserved characters on Windows are documented
        // here:
        //
        // https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file
        ! (c.is_whitespace()
           || c == '<'
           || c == '>'
           || c == ':'
           || c == '"'
           || c == '/'
           || c == '\\'
           || c == '|'
           || c == '?'
           || c == '*')
    }

    /// Returns a shadow CA's key, and whether it was just created.
    ///
    /// # Introduction
    ///
    /// Shadow CAs are used to encode evidence of a binding's veracity
    /// in standard web of trust data structures so that the
    /// information can be automatically incorporated into web of
    /// trust calculations.
    ///
    /// Consider [`keys.openpgp.org`], which is a verifying keyserver.
    /// `keys.openpgp.org` checks whether a user ID should be
    /// associated with a certificate using a challenge-response
    /// authentication scheme.  When someone uploads a certificate,
    /// `keys.openpgp.org` iterates over the user IDs, and if a user
    /// ID contains an email address, sends an email to it containing
    /// a secret link.  The owner of the email address can visit the
    /// link to confirm to `keys.openpgp.org` that the email address
    /// should be associated with the certificate.
    ///
    /// Although `keys.openpgp.org` verifies User IDs, it does not
    /// issue third-party certifications attesting to that fact.  But,
    /// clients can infer this from `keys.openpgp.org` behavior: when
    /// `keys.openpgp.org` returns a certificate, it only returns user
    /// IDs that it has verified as belonging to the certificate.
    ///
    /// An OpenPGP client could record the fact that
    /// `keys.openpgp.org` returned a user ID in a database.  The main
    /// question is then how to combine that information with other
    /// information in a coherent manner.  Alternatively, we can
    /// encode the assertion as a certification using a so-called
    /// shadow CA.  By encoding this information as a normal OpenPGP
    /// artifact, it can be directly integrated into web of trust
    /// calculations; another, parallel system to combine evidence is
    /// not needed.
    ///
    ///   [`keys.openpgp.org`]: https://keys.openpgp.org
    ///
    /// # Usage
    ///
    /// `name` is the shadow CA's name.  This is converted to the
    /// special name `_sequoia_ca_ + name + .pgp`.  Names must not
    /// contain white space (` ` or `\n`), path separators (`\` or
    /// `/`), or colons (`:`).
    ///
    /// If the shadow CA doesn't exist and `create` is true, it is
    /// created.  In this case, the user ID is set to `userid`, and if
    /// `trust_amount` is greater than 0, it is designed a trusted
    /// introducer by the last `CA` listed in `intermediaries`, or the
    /// local trust root, if the list is empty.  The certification's
    /// trust amount is set to `trust_amount`.
    ///
    /// `intermediaries` is a list of tuples consisting of a name, a
    /// user ID, and a trust amount.  Each tuple corresponds to an
    /// intermediary shadow CA.  It may be empty; the local trust root
    /// is always the trust anchor and shouldn't be listed explicitly.
    ///
    /// This function works from the trust root towards the shadow CA.
    /// If an intermediate shadow CA does not exist and `create` is
    /// true, it is created, and certified by its parent CA.
    ///
    /// On success, the shadow CA is returned, and whether the shadow
    /// CA was just created.
    ///
    /// # Examples
    ///
    /// The following code looks up the shadow CA for
    /// `keys.openpgp.org`.  It is not directly certified by the trust
    /// root, but by a partially trusted intermediary named
    /// `public_directories`, which acts as a type of resistor for all
    /// public directories.  Note: if you are actually looking up the
    /// shadow CA for `keys.openpgp.org`, you should use
    /// [`CertD::shadow_ca_keys_openpgp_org`] instead.
    ///
    /// The local key for `keys.openpgp.org` is then used to certify
    /// the user IDs on a certificate, which was presumably returned
    /// by `keys.openpgp.org`.
    ///
    /// ```rust
    /// use std::sync::Arc;
    ///
    /// use anyhow::Context;
    ///
    /// use sequoia_openpgp as openpgp;
    /// use openpgp::cert::CertBuilder;
    /// use openpgp::Cert;
    /// use openpgp::packet::Packet;
    /// use openpgp::packet::UserID;
    /// use openpgp::packet::signature::SignatureBuilder;
    /// use openpgp::types::SignatureType;
    ///
    /// use sequoia_cert_store as cert_store;
    /// use cert_store::LazyCert;
    /// use cert_store::store::certd::CertD;
    /// use cert_store::store::StoreUpdate;
    ///
    /// # fn main() -> openpgp::Result<()> {
    /// # let certd_path = tempfile::tempdir()?;
    /// let mut certd = CertD::open(&certd_path)?;
    ///
    /// let (koo, _created) = certd.shadow_ca(
    ///     "keys.openpgp.org",
    ///     true,
    ///     "Downloaded from keys.openpgp.org",
    ///     1,
    ///     &[
    ///         ( "public_directories", UserID::from("Public Directories"), 40),
    ///     ])?;
    ///
    /// // Pretend the following certificate was fetched from keys.openpgp.org.
    /// let alice_userid = "<alice@example.org>";
    /// let (alice, _rev) = CertBuilder::general_purpose(Some(alice_userid))
    ///     .generate()?;
    ///
    /// // Certify the binding using the shadow CA.
    /// let mut koo_signer = koo.to_cert()?.primary_key().key().parts_as_secret()
    ///     .context("CA is not certification capable.")?
    ///     .clone().into_keypair()
    ///     .context("CA is not certification capable.")?;
    ///
    /// let sig = SignatureBuilder::new(SignatureType::GenericCertification)
    ///     .set_exportable_certification(false)?
    ///     .sign_userid_binding(
    ///         &mut koo_signer,
    ///         alice.primary_key().key(),
    ///         &UserID::from(alice_userid))
    ///     .context("Signing binding")?;
    ///
    /// let alice = alice.insert_packets([
    ///     Packet::from(UserID::from(alice_userid)),
    ///     Packet::from(sig),
    /// ])?.0;
    ///
    /// certd.update(Arc::new(LazyCert::from(alice)));
    /// # Ok(()) }
    /// ```
    pub fn shadow_ca<U>(&self, name: &str,
                        create: bool, userid: U, trust_amount: u8,
                        intermediaries: &[(&str, UserID, u8)], )
        -> Result<(Arc<LazyCert<'a>>, bool)>
        where U: Into<UserID>,
    {
        let userid = userid.into();

        tracer!(TRACE, "CertD::shadow_ca");
        t!("{}, create: {}, userid: {:?}, {}, {} intermediaries",
           name, create, String::from_utf8_lossy(userid.value()), trust_amount,
           intermediaries.len());

        let name = CertD::shadow_ca_special(name);

        // If the CA is cached in memory, short-circuit everything.
        if let Ok(result) = self.get_ca(&name, false, &userid, None) {
            t!("Returning cached certificate {} for {}",
               result.0.fingerprint(), name);
            return Ok(result);
        }

        // Iterate from the trust root towards the shadow CA.
        let mut ca: (Arc<LazyCert>, bool) = self.get_ca(
            cert_d::TRUST_ROOT,
            create,
            &UserID::from_static_bytes(b"Local Trust Root"),
            None)?;

        for (name, userid, trust_amount) in
            intermediaries
                .into_iter()
                .map(|(name, userid, amount)| {
                    (CertD::shadow_ca_special(name), userid, *amount)
                })
                .chain(iter::once((name, &userid, trust_amount)))
        {
            if ! Self::valid_name(&name) {
                return Err(cert_d::Error::BadName.into());
            }

            t!("Getting {} (create: {}, userid: {:?}, {})",
               name, create, String::from_utf8_lossy(userid.value()),
               trust_amount);

            ca = self.get_ca(&name, create, &userid,
                             Some((ca.0, trust_amount)))?;

            t!("{} -> {}", name, ca.0.fingerprint());
        }

        Ok(ca)
    }

    /// Returns the specified CA, optionally creating and certifying
    /// it if it doesn't exist.
    ///
    /// See [`CertD::shadow_ca`] for details about shadow CAs.
    ///
    /// This function first checks if the specified CA is in the
    /// in-memory cache.  If so, it checks that the cached version is
    /// up to date, and then returns the result.
    ///
    /// If the CA is not in the in-memory cache, it is loaded from
    /// disk.  If it doesn't exist on disk and `create` is `true`, the
    /// shadow CA is created.  See [`CertD::load_ca`] for details of
    /// what is exactly done.
    fn get_ca(&self, special_name: &str,
              create: bool, userid: &UserID,
              parent: Option<(Arc<LazyCert<'_>>, u8)>)
        -> Result<(Arc<LazyCert<'a>>, bool)>
    {
        tracer!(TRACE, "CertD::get_ca");

        let mut cas = self.cas.lock().unwrap();

        let get_if_changed = |tag, name| -> Result<Option<_>> {
            match self.certd.get_if_changed(tag, name) {
                Ok(None) => {
                    // The certificate on disk did not change.
                    return Ok(None);
                },
                Ok(Some((new_tag, bytes))) => {
                    // The certificate on disk changed.  Reparse it.
                    t!("{} changed on disk, reloading.", name);
                    match Cert::from_bytes(&bytes) {
                        Ok(cert) => {
                            return Ok(Some((cert, new_tag)));
                        }
                        Err(err) => {
                            // An error occurred while parsing the
                            // data.  That's not great.  We just keep
                            // using what we have.
                            t!("While reparsing {}: {}", name, err);
                            return Ok(None);
                        }
                    }
                }
                // An error occurred while reading the file.  That's
                // not great.  We just keep using what we have.
                Err(err) => {
                    t!("While rereading {}: {}", name, err);
                    return Ok(None);
                }
            }
        };

        if let Some(CA { special_tag, special, public, merged })
            = cas.get_mut(special_name)
        {
            // The certificate is cached in memory.  Make sure the
            // in-memory version is up to date.
            t!("{} -> {} is cached in memory",
               special_name, special.fingerprint());

            let mut changed = false;
            if let Some((cert, tag))
                = get_if_changed(*special_tag, special_name)?
            {
                *special = cert;
                *special_tag = tag;
                changed = true;
            }

            if let Ok(certs) = self.lookup_by_cert(&special.key_handle()) {
                // Since we looked up by fingerprint, certs contains 0
                // or 1 elements.
                if let Some(cert) = certs.into_iter().next() {
                    // Did it change?
                    if ! Arc::ptr_eq(&cert, &public) {
                        // Yup, it changed.
                        *public = cert;
                        changed = true;
                    }
                }
            }

            if changed {
                *merged = Arc::new(LazyCert::from(
                    public.to_cert()?
                        .clone()
                        .merge_public_and_secret(special.clone())?));
            }

            Ok((Arc::clone(merged), false))
        } else {
            // The certificate is not cached in memory; we need to
            // read it from disk, or generate it.

            let (ca, created)
                = self.load_ca(special_name, create, userid, parent)?;

            let merged = Arc::clone(&ca.merged);

            t!("Loaded {} ({}) from disk, caching in memory",
               special_name, merged.fingerprint());

            match cas.entry(special_name.to_string()) {
                btree_map::Entry::Occupied(mut oe) => {
                    oe.insert(ca);
                }
                btree_map::Entry::Vacant(ve) => {
                    ve.insert(ca);
                }
            }

            Ok((merged, created))
        }
    }

    /// Loads a CA's certificate from the certificate directory, or
    /// optionally generates one if it does not exist.
    ///
    /// Loads the certificate stored under the specified special name
    /// from the certificate directory.  If the special name exists,
    /// this also reads and merges in the certificate data stored
    /// under the fingerprint.
    ///
    /// If `create` is `false` and the certificate does not exist,
    /// returns `std::io::ErrorKind::NotFound`.
    ///
    /// If `create` is `true`, and we need to generate a certificate,
    /// we are careful to so while we hold the certificate directory's
    /// lock to avoid races.
    ///
    /// If we create a certificate, and `parent` is not `None`, then
    /// we certify the certificate using `parent` as a trusted
    /// introducer for the specified trust amount.  Note: if the CA is
    /// not created, this function does **not** check that the parent
    /// has certified the CA.
    ///
    /// If we create a certificate, we write it to disk under both its
    /// special name, and its fingerprint.
    fn load_ca(&self, name: &str, create: bool, userid: &UserID,
               parent: Option<(Arc<LazyCert<'_>>, u8)>)
        -> Result<(CA<'a>, bool)>
    {
        tracer!(TRACE, "CertD::load_ca");

        let certd = self.certd();

        // Exclusively lock the certificate directory by inserting the
        // trust root.  We may discover that the trust already exists.
        // In that case, we won't generate a new one, but just load
        // the existing one.
        let mut ca = Err(anyhow::anyhow!("merge callback not invoked"));
        let mut created = false;
        let (special_tag, _) = certd.insert_special(
            name,
            (), false,
            |(), disk| {
                if let Some(disk) = disk {
                    // We have one.
                    ca = Cert::from_bytes(&disk);
                    Ok(cert_d::MergeResult::Keep)
                } else if create {
                    // We don't have one, and we should create one.
                    let key = Self::generate_ca_key(userid)?;
                    t!("Created {} for {}", key.fingerprint(), name);
                    let bytes = key.as_tsk().to_vec()?;
                    ca = Ok(key);
                    created = true;
                    Ok(cert_d::MergeResult::Data(bytes))
                } else {
                    // We don't have one, and we shouldn't create one.
                    Err(cert_d::Error::Other(
                        std::io::Error::new(
                            std::io::ErrorKind::NotFound,
                            format!("{} not found", name)).into()))
                }
            })?;
        let mut ca = ca?;

        let mut certify = |parent_cert: Arc<LazyCert>, trust_amount|
                              -> Result<()>
        {
            t!("Using {} to make {} a trusted introducer",
               parent_cert.fingerprint(), ca.fingerprint());

            let mut signer = parent_cert
                .to_cert()?
                .primary_key()
                .key()
                .parts_as_secret()
                .context("Trust root can't be used for signing.")?
                .clone()
                .into_keypair()
                .context("Trust root can't be used for signing.")?;

            let sig = SignatureBuilder::new(SignatureType::GenericCertification)
                .set_exportable_certification(false)?
                .set_trust_signature(255, trust_amount)?
                .set_signature_creation_time(ca_creation_time())?
                .sign_userid_binding(
                    &mut signer,
                    ca.primary_key().key(),
                    &userid)
                .with_context(|| {
                    format!("Creating certification for {} {:?}",
                            ca.fingerprint(),
                            String::from_utf8_lossy(userid.value()))
                })?;

            ca = ca.clone().insert_packets([
                Packet::from(userid.clone()),
                Packet::from(sig),
            ])?.0;

            Ok(())
        };

        let (public, merged) = if created {
            // We just created the CA.  Make it a trusted introducer.
            if let Some((parent_cert, trust_amount)) = parent {
                if let Err(err) = certify(parent_cert, trust_amount) {
                    // XXX: What should we do with the error?
                    t!("Failed to authorize CA, {:?}: {}",
                       userid, err);
                }
            }

            // Insert the public bits under the fingerprint.
            let merged = Arc::new(LazyCert::from(ca.clone()));
            let public = self.update_by(
                Arc::clone(&merged), &mut ())?;

            (public, merged)
        } else {
            // We didn't create it.  Read in the public version and
            // merge it.
            let certs = self.lookup_by_cert(&ca.key_handle())?;

            // Since we looked up by fingerprint, certs contains 0 or
            // 1 elements.
            if let Some(public) = certs.into_iter().next() {
                let merged = Arc::new(LazyCert::from(
                    public.to_cert()?
                        .clone()
                        .merge_public_and_secret(ca.clone())?));
                (public, merged)
            } else {
                // The certificate is saved under the special name,
                // but not its fingerprint.  It seems something went
                // wrong in the past, but we can fix it by writing out
                // the public version now.
                let merged = Arc::new(LazyCert::from(ca.clone()));

                let public = self.update_by(
                    Arc::clone(&merged), &mut ())?;

                (public, merged)
            }
        };

        let ca = CA {
            special: ca,
            special_tag,
            public,
            merged,
        };

        Ok((ca, created))
    }

    /// Returns a new key, which is appropriate for a CA.
    ///
    /// That is, it has just a certification capable primary and no
    /// subkeys.
    fn generate_ca_key(userid: &UserID) -> Result<Cert> {
        let (root, _) = CertBuilder::new()
            .set_primary_key_flags(KeyFlags::empty().set_certification())
            .set_exportable(false)
            .set_creation_time(ca_creation_time())
            // CAs should *not* expire.
            .set_validity_period(None)
            .add_userid(userid.clone())
            .generate()?;

        Ok(root)
    }

    const PUBLIC_DIRECTORY_NAME: &'static str = "public_directories";
    #[expect(clippy::declare_interior_mutable_const)]
    const PUBLIC_DIRECTORY_USERID: UserID
        = UserID::from_static_bytes(b"Public Directories");
    const PUBLIC_DIRECTORY_TRUST_AMOUNT: u8 = 40;

    /// A local, intermediate CA used to authorize the shadow CAs for
    /// public directories.
    ///
    /// Shadow CAs for public directories like
    /// `hkps://keys.openpgp.org` and WKD are not directly certified
    /// by the local trust root, but by a local, intermediate CA.
    /// This CA acts as a resistor, which limits the combined trust
    /// amount of public directories.
    pub fn public_directory_ca(&self)
        -> Result<(Arc<LazyCert<'a>>, bool)>
    {
        self.shadow_ca(
            Self::PUBLIC_DIRECTORY_NAME,
            true,
            Self::PUBLIC_DIRECTORY_USERID,
            Self::PUBLIC_DIRECTORY_TRUST_AMOUNT,
            &[])
    }

    fn via_public_directory_ca(&self, name: &str, userid: &str)
        -> Result<(Arc<LazyCert<'a>>, bool)>
    {
        self.shadow_ca(
            name,
            true,
            userid,
            // A public directory has the smallest, positive trust
            // amount: 1 out of 120.
            1,
            &[
                // The public directory CA acts as a resistor and
                // limits the combined trust amount of public
                // directories.
                (Self::PUBLIC_DIRECTORY_NAME,
                 Self::PUBLIC_DIRECTORY_USERID,
                 Self::PUBLIC_DIRECTORY_TRUST_AMOUNT),
            ])
    }

    /// Returns the shadow CA for keys.openpgp.org.
    ///
    /// See [`CertD::shadow_ca`] for more information about shadow
    /// CAs.
    pub fn shadow_ca_keys_openpgp_org(&self)
        -> Result<(Arc<LazyCert<'a>>, bool)>
    {
        self.via_public_directory_ca(
            "keys.openpgp.org",
            "Downloaded from keys.openpgp.org")
    }

    /// Returns the shadow CA for Proton.
    ///
    /// See [`CertD::shadow_ca`] for more information about shadow
    /// CAs.
    pub fn shadow_ca_proton_me(&self)
        -> Result<(Arc<LazyCert<'a>>, bool)>
    {
        self.via_public_directory_ca(
            "proton.me",
            "Downloaded from Proton Mail")
    }

    /// Returns the shadow CA for keys.mailvelope.com.
    ///
    /// See [`CertD::shadow_ca`] for more information about shadow
    /// CAs.
    pub fn shadow_ca_keys_mailvelope_com(&self)
        -> Result<(Arc<LazyCert<'a>>, bool)>
    {
        self.via_public_directory_ca(
            "keys.mailvelope.com",
            "Downloaded from keys.mailvelope.com")
    }

    /// Returns the shadow CA for WKDs.
    ///
    /// See [`CertD::shadow_ca`] for more information about shadow
    /// CAs.
    pub fn shadow_ca_wkd(&self) -> Result<(Arc<LazyCert<'a>>, bool)> {
        self.via_public_directory_ca(
            "wkd",
            "Downloaded from a Web Key Directory (WKD)")
    }

    /// Returns the shadow CA for DANE.
    ///
    /// See [`CertD::shadow_ca`] for more information about shadow
    /// CAs.
    pub fn shadow_ca_dane(&self) -> Result<(Arc<LazyCert<'a>>, bool)> {
        self.via_public_directory_ca(
            "dane",
            "Downloaded from DANE")
    }

    const WEB_NAME: &'static str = "web";
    const WEB_USERID_STR: &'static str = "Downloaded from the Web";
    #[expect(clippy::declare_interior_mutable_const)]
    const WEB_USERID: UserID
        = UserID::from_static_bytes(b"Downloaded from the Web");
    const WEB_TRUST_AMOUNT: u8 = 1;

    /// Returns the shadow CA for the Web.
    ///
    /// See [`CertD::shadow_ca`] for more information about shadow
    /// CAs.
    pub fn shadow_ca_web(&self) -> Result<(Arc<LazyCert<'a>>, bool)> {
        debug_assert_eq!(Self::WEB_USERID_STR.as_bytes(),
                         Self::WEB_USERID.value());

        self.via_public_directory_ca(
            Self::WEB_NAME,
            Self::WEB_USERID_STR)
    }

    /// Returns the shadow CA for the give URL.
    ///
    /// See [`CertD::shadow_ca`] for more information about shadow
    /// CAs.
    pub fn shadow_ca_for_url(&self, url: &str)
                             -> Result<Option<(Arc<LazyCert<'a>>, bool)>>
    {
        debug_assert_eq!(Self::WEB_USERID_STR.as_bytes(),
                         Self::WEB_USERID.value());

        // First, parse and sanitize the URL.
        let mut url = url::Url::parse(url)?;
        // We only certify the certificate if the transport was
        // encrypted and authenticated.
        if url.scheme() != "https" {
            return Ok(None);
        }
        // Drop sensitive and irrelevant information.
        url.set_username("").expect("https? supports authentication");
        url.set_password(None).expect("https? supports authentication");
        url.set_fragment(None);

        // Percent-encode characters not valid in names.
        let mut name = String::from("shadow_of_url_");
        for c in url.as_str().chars() {
            if Self::valid_char(c) && c != '%' {
                name.push(c);
            } else {
                // Note: all invalid chars are ASCII characters, so
                // u8::try_from will succeed.
                debug_assert_eq!(c.len_utf8(), 1);
                name.push_str(
                    &format!("%{:02X}", u8::try_from(c).unwrap_or(0)));
            }
        }

        self.shadow_ca(
            &name,
            true,
            format!("Downloaded from {}", url.as_str()),
            1,
            &[
                (Self::PUBLIC_DIRECTORY_NAME,
                 Self::PUBLIC_DIRECTORY_USERID,
                 Self::PUBLIC_DIRECTORY_TRUST_AMOUNT),
                (Self::WEB_NAME,
                 Self::WEB_USERID,
                 Self::WEB_TRUST_AMOUNT),
            ])
            .map(Some)
    }

    /// Returns a shadow CA for the specified keyserver.
    ///
    /// If a keyserver is not known to be a verifying keyserver, then
    /// this returns `Ok(None)`.
    ///
    /// The URI should be of the form `hkps://server.example.com`.
    /// Other protocols are not supported.  The server name is matched
    /// case insensitively.
    ///
    /// See [`CertD::shadow_ca`] for more information about shadow
    /// CAs.
    pub fn shadow_ca_keyserver(&self, uri: &str)
        -> Result<Option<(Arc<LazyCert<'a>>, bool)>>
    {
        let uri = uri.to_ascii_lowercase();

        // We only certify the certificate if the transport was
        // encrypted and authenticated.
        let server = if let Some(server) = uri.strip_prefix("hkps://") {
            server
        } else {
            return Ok(None);
        };

        let server = server.strip_suffix("/").unwrap_or(server);

        // A basic sanity check on the name, which we are about to use
        // as a filename: it can't start with a dot, and no
        // whitespace, no slashes, and no colons are allowed.
        if server.chars().next() == Some('.') || ! Self::valid_name(server) {
            return Ok(None);
        }

        // The known verifying key servers.
        match &server[..] {
            "keys.openpgp.org" | "keys.openpgp.io" =>
                self.shadow_ca_keys_openpgp_org().map(Some),
            "keys.mailvelope.com" =>
                self.shadow_ca_keys_mailvelope_com().map(Some),
            "mail-api.proton.me" | "api.protonmail.ch" =>
                self.shadow_ca_proton_me().map(Some),
            _ => Ok(None),
        }
    }

    const AUTOCRYPT_NAME: &'static str = "autocrypt";
    #[expect(clippy::declare_interior_mutable_const)]
    const AUTOCRYPT_USERID: UserID
        = UserID::from_static_bytes(b"Imported from Autocrypt");
    const AUTOCRYPT_TRUST_AMOUNT: u8 = 40;

    /// Returns the shadow CA for Autocrypt.
    ///
    /// See [`CertD::shadow_ca`] for more information about shadow
    /// CAs.
    pub fn shadow_ca_autocrypt(&self) -> Result<(Arc<LazyCert<'a>>, bool)> {
        self.shadow_ca(
            Self::AUTOCRYPT_NAME,
            true,
            Self::AUTOCRYPT_USERID,
            Self::AUTOCRYPT_TRUST_AMOUNT,
            &[])
    }

    const AUTOCRYPT_GOSSIP_NAME: &'static str = "autocrypt-gossip";
    #[expect(clippy::declare_interior_mutable_const)]
    const AUTOCRYPT_GOSSIP_USERID: UserID
        = UserID::from_static_bytes(b"Imported from Autocrypt Gossip");
    const AUTOCRYPT_GOSSIP_TRUST_AMOUNT: u8 = 40;

    /// Returns the shadow CA for Autocrypt Gossip.
    ///
    /// See [`CertD::shadow_ca`] for more information about shadow
    /// CAs.
    pub fn shadow_ca_autocrypt_gossip(&self)
                                      -> Result<(Arc<LazyCert<'a>>, bool)>
    {
        self.shadow_ca(
            Self::AUTOCRYPT_GOSSIP_NAME,
            true,
            Self::AUTOCRYPT_GOSSIP_USERID,
            Self::AUTOCRYPT_GOSSIP_TRUST_AMOUNT,
            &[
                (Self::AUTOCRYPT_NAME,
                 Self::AUTOCRYPT_USERID,
                 Self::AUTOCRYPT_TRUST_AMOUNT),
            ])
    }

    /// Returns the shadow CA for recording Autocrypt Gossip in the
    /// name of the given cert and sender address.
    ///
    /// If you observe an Autocrypt mail from `addr` signed by `cert`,
    /// and the message contains Autocrypt Gossip headers in the
    /// encrypted payload, use this function to get a shadow CA to
    /// turn the Autocrypt Gossip into OpenPGP certifications.
    ///
    /// See [`CertD::shadow_ca`] for more information about shadow
    /// CAs.
    pub fn shadow_ca_autocrypt_gossip_for(&self, cert: &Cert, addr: &str)
                                          -> Result<(Arc<LazyCert<'a>>, bool)>
    {
        // Sanity-check the address.
        let u = UserID::from_address(
            "Autocrypt Gossip from", None, addr)?;

        self.shadow_ca(
            &format!("shadow_of_{:x}", cert.fingerprint()),
            true,
            u,
            1,
            &[
                (Self::AUTOCRYPT_NAME,
                 Self::AUTOCRYPT_USERID,
                 Self::AUTOCRYPT_TRUST_AMOUNT),
                (Self::AUTOCRYPT_GOSSIP_NAME,
                 Self::AUTOCRYPT_GOSSIP_USERID,
                 Self::AUTOCRYPT_GOSSIP_TRUST_AMOUNT),
            ])
    }
}


#[cfg(test)]
mod tests {
    use super::*;

    use openpgp::Fingerprint;
    use openpgp::packet::UserID;
    use openpgp::policy::StandardPolicy;

    use crate::store::Store;

    #[test]
    fn shadow_ca() -> Result<()> {
        tracer!(true, "shadow_ca");

        let path = tempfile::tempdir()?;
        let certd = CertD::open(&path)?;

        // Check that the special exists, and includes secret key
        // material, and that there is a corresponding entry under its
        // fingerprint, which doesn't include any secret key material.
        let check = |name: &str| {
            t!("check({})", name);
            assert!(cert_d::CertD::is_special(name).is_ok());
            let (_tag, ca) = certd.certd()
                .get(name)
                .unwrap()
                .expect("exists");
            let ca = Cert::from_bytes(&ca).expect("valid");
            assert!(ca.is_tsk());

            let (_tag, cert) = certd.certd()
                .get(&ca.fingerprint().to_string())
                .unwrap()
                .expect("exists");
            let cert = Cert::from_bytes(&cert).expect("valid");
            assert!(! cert.is_tsk());

            assert_eq!(ca.fingerprint(), cert.fingerprint());

            ca
        };

        let (koo, _tag) = certd.shadow_ca(
            "keys.openpgp.org",
            true,
            UserID::from_static_bytes(b"Downloaded from keys.openpgp.org"),
            1,
            &[
                ( "public_directories",
                   UserID::from_static_bytes(b"Public Directories"),
                   10,
                ),
            ])?;

        let cert = check(&CertD::shadow_ca_special("keys.openpgp.org"));
        assert_eq!(cert.fingerprint(), koo.fingerprint());
        let public_directories
            = check(&CertD::shadow_ca_special("public_directories"));
        let trust_root
            = check(&CertD::shadow_ca_special(cert_d::TRUST_ROOT));

        let (dane, _tag) = certd.shadow_ca(
            "dane",
            true,
            UserID::from_static_bytes(b"Downloaded from DANE"),
            1,
            &[
                ( "public_directories",
                   UserID::from_static_bytes(b"Public Directories"),
                   10),
            ])?;

        let cert = check(&CertD::shadow_ca_special("dane"));
        assert_eq!(cert.fingerprint(), dane.fingerprint());
        let cert = check(&CertD::shadow_ca_special("keys.openpgp.org"));
        assert_eq!(cert.fingerprint(), koo.fingerprint());
        let cert = check(&CertD::shadow_ca_special("public_directories"));
        assert_eq!(cert, public_directories);
        let cert = check(&CertD::shadow_ca_special(cert_d::TRUST_ROOT));
        assert_eq!(cert, trust_root);

        let (tofu, _tag) = certd.shadow_ca(
            "tofu",
            true,
            UserID::from_static_bytes(b"Trust on First Use (TOFU)"),
            120,
            &[])?;

        let cert = check(&CertD::shadow_ca_special("tofu"));
        assert_eq!(cert.fingerprint(), tofu.fingerprint());
        let cert = check(&CertD::shadow_ca_special("dane"));
        assert_eq!(cert.fingerprint(), dane.fingerprint());
        let cert = check(&CertD::shadow_ca_special("keys.openpgp.org"));
        assert_eq!(cert.fingerprint(), koo.fingerprint());
        let cert = check(&CertD::shadow_ca_special("public_directories"));
        assert_eq!(cert, public_directories);
        let cert = check(&CertD::shadow_ca_special(cert_d::TRUST_ROOT));
        assert_eq!(cert, trust_root);

        Ok(())
    }

    #[test]
    fn shadow_ca_keyserver() -> Result<()> {
        tracer!(true, "keyserver_shadow_ca");

        let path = tempfile::tempdir()?;

        let test = |certd: &CertD, may_create: bool| {
            let (koo, created) = certd.shadow_ca_keys_openpgp_org().unwrap();
            if ! may_create {
                assert!(! created);
            }
            let koo = koo.fingerprint();

            let (mailvelope, created)
                = certd.shadow_ca_keys_mailvelope_com().unwrap();
            if ! may_create {
                assert!(! created);
            }
            let mailvelope = mailvelope.fingerprint();

            let lookup = |uri, expected: Option<&Fingerprint>| {
                let result = certd.shadow_ca_keyserver(uri).unwrap();
                if let Some(fingerprint) = expected {
                    let (ca, created) = result.expect("valid URI");
                    if ! may_create {
                        assert!(! created);
                    }
                    assert_eq!(fingerprint, &ca.fingerprint());
                } else {
                    assert!(result.is_none());
                }
            };

            lookup("hkps://keys.openpgp.org", Some(&koo));
            lookup("HKPS://KEYS.OPENPGP.ORG", Some(&koo));
            lookup("HKPS://KEYS.OPENPGP.io", Some(&koo));
            // hkp is not considered secure.
            lookup("hkp://keys.openpgp.org", None);
            // https is not the right protocol.
            lookup("https://keys.openpgp.org", None);
            // Not a verifying keyserver.
            lookup("hkps://keyserver.ubuntu.com", None);

            lookup("hkps://keys.mailvelope.com", Some(&mailvelope));
        };

        // The first time through we create the trust root and CAs.
        t!("Creating CAs");
        let certd = CertD::open(&path)?;
        test(&certd, true);

        // The second time through we load them from disk.
        t!("Loading CAs");
        let certd2 = CertD::open(&path)?;
        test(&certd2, false);

        Ok(())
    }

    fn check_certifiation(certd: &CertD, certifier: &Cert, cert: Fingerprint,
                          count: usize)
    {
        const P: &StandardPolicy = &StandardPolicy::new();
        let cert: Arc<LazyCert>
            = certd.lookup_by_cert_fpr(&cert).expect("exists");
        let cert: &Cert = cert.to_cert().expect("valid cert");

        let userids: Vec<UserIDAmalgamation> = cert.userids().collect();
        assert_eq!(userids.len(), 1);

        let certifications = userids[0].valid_certifications_by_key(
            P, None, certifier.primary_key().key());
        assert_eq!(certifications.count(), count);
    }

    #[test]
    fn shadow_ca_cerified() -> Result<()> {

        // Check that the shadow CA and intermediate CA are actually
        // certified.
        tracer!(true, "keyserver_shadow_certified");

        let path = tempfile::tempdir()?;

        // The first time through we create the trust root and CAs.
        t!("Creating CAs");
        let certd = CertD::open(&path)?;

        let (koo, created) = certd.shadow_ca_keys_openpgp_org().unwrap();
        assert!(created);
        let koo = koo.to_cert().expect("valid cert");

        let (pd, created) = certd.public_directory_ca().unwrap();
        // This should have been created in the last step.
        assert!(! created);
        let pd = pd.to_cert().expect("valid cert");

        let (tr, created) = certd.trust_root().unwrap();
        // This should have been created in the last step.
        assert!(! created);
        let tr = tr.to_cert().expect("valid cert");

        let (kmc, created) = certd.shadow_ca_keys_mailvelope_com().unwrap();
        assert!(created);
        let kmc = kmc.to_cert().expect("valid cert");

        // Avoid the in-memory cache.
        let certd = CertD::open(&path)?;

        t!("Checking that the PD CA certified the KOO shadow CA.");
        check_certifiation(&certd, &pd, koo.fingerprint(), 1);
        t!("Checking that the PD CA certified the KMC shadow CA.");
        check_certifiation(&certd, &pd, kmc.fingerprint(), 1);
        t!("Checking that the trust root certified the PD CA.");
        check_certifiation(&certd, &tr, pd.fingerprint(), 1);
        t!("Checking that the trust root didn't the KOO shadow CA.");
        check_certifiation(&certd, &tr, koo.fingerprint(), 0);
        t!("Checking that the trust root didn't the KMC shadow CA.");
        check_certifiation(&certd, &tr, kmc.fingerprint(), 0);

        Ok(())
    }

    #[test]
    fn shadow_ca_for_url() -> Result<()> {
        tracer!(true, "shadow_ca_for_url");

        let path = tempfile::tempdir()?;

        let test = |certd: &CertD, may_create: bool| {
            let (alice, created) = certd
                .shadow_ca_for_url("https://example.org/alice.pgp")
                .unwrap().unwrap();
            if ! may_create {
                assert!(! created);
            }
            let alice = alice.fingerprint();

            #[derive(Debug)]
            enum Expectation { Alice, Other, NoCA }
            use Expectation::*;
            let lookup = |uri, expectation| {
                t!("looking up {} expecting {:?}", uri, expectation);
                let result = certd.shadow_ca_for_url(uri).unwrap();
                match expectation {
                    Alice => {
                        let (ca, created) = result.expect("valid URI");
                        if ! may_create {
                            assert!(! created);
                        }
                        assert_eq!(alice, ca.fingerprint());
                    },
                    Other => {
                        let (_ca, created) = result.expect("valid URI");
                        if ! may_create {
                            assert!(! created);
                        }
                    },
                    NoCA => {
                        assert!(result.is_none());
                    },
                }
            };

            lookup("https://example.org/alice.pgp", Alice);
            lookup("HTTPS://EXAMPLE.ORG/alice.pgp", Alice);
            lookup("https://foo@example.org/alice.pgp", Alice);
            lookup("https://foo:bar@example.org/alice.pgp", Alice);
            lookup("https://foo:bar@example.org/alice.pgp#fragment", Alice);
            // http is not considered secure.
            lookup("http://example.org/alice.pgp", NoCA);
            // hkps is not the right protocol.
            lookup("hkps://example.org/alice.pgp", NoCA);
            // Different URLs.
            lookup("https://example.org/bob.pgp", Other);
            lookup("https://example.net/alice.pgp", Other);
            lookup("https://example.org/alice.pgp?some=query", Other);
            lookup("https://example.org/alice.pgp/other/path", Other);
        };

        // The first time through we create the trust root and CAs.
        t!("Creating CAs");
        let certd = CertD::open(&path)?;
        test(&certd, true);

        // The second time through we load them from disk.
        t!("Loading CAs");
        let certd2 = CertD::open(&path)?;
        test(&certd2, false);

        Ok(())
    }

    #[test]
    fn shadow_ca_for_web_cerified() -> Result<()> {
        // Check that the shadow CA and intermediate CA are actually
        // certified.
        tracer!(true, "shadow_ca_for_web_cerified");

        let path = tempfile::tempdir()?;

        // The first time through we create the trust root and CAs.
        t!("Creating CAs");
        let certd = CertD::open(&path)?;

        let (alice, created) = certd
            .shadow_ca_for_url("https://example.org/alice.pgp")
            .unwrap().unwrap();
        assert!(created);
        let alice = alice.to_cert().expect("valid cert");

        let (tr, created) = certd.trust_root().unwrap();
        // This should have been created in the first step.
        assert!(! created);
        let tr = tr.to_cert().expect("valid cert");

        let (pd, created) = certd.public_directory_ca().unwrap();
        // This should have been created in the first step.
        assert!(! created);
        let pd = pd.to_cert().expect("valid cert");

        let (web, created) = certd.shadow_ca_web().unwrap();
        // This should have been created in the first step.
        assert!(! created);
        let web = web.to_cert().expect("valid cert");

        // Avoid the in-memory cache.
        let certd = CertD::open(&path)?;

        t!("Checking that the trust root certified the PD CA.");
        check_certifiation(&certd, &tr, pd.fingerprint(), 1);
        t!("Checking that the PD CA certified the web CA.");
        check_certifiation(&certd, &pd, web.fingerprint(), 1);
        t!("Checking that the web CA certified the ALICE shadow CA.");
        check_certifiation(&certd, &web, alice.fingerprint(), 1);
        t!("Checking that the trust root didn't the web CA.");
        check_certifiation(&certd, &tr, web.fingerprint(), 0);
        t!("Checking that the trust root didn't the ALICE shadow CA.");
        check_certifiation(&certd, &tr, alice.fingerprint(), 0);
        t!("Checking that the PD CA didn't the ALICE shadow CA.");
        check_certifiation(&certd, &pd, alice.fingerprint(), 0);

        Ok(())
    }

    /// Test that we can create and sanitize, or reject CAs for funny
    /// URLs.
    #[test]
    fn shadow_ca_for_url_escapes() -> Result<()> {
        tracer!(true, "shadow_ca_for_url_escapes");

        let path = tempfile::tempdir()?;
        let test = |certd: &CertD, url, stripped| {
            t!("trying {:?}", url);
            let (alice, _created) =
                certd.shadow_ca_for_url(url).unwrap().unwrap();
            if let Some(c) = stripped {
                // Make sure the character is stripped.
                assert!(alice.userids().all(
                    |uid| ! str::from_utf8(uid.value()).unwrap().contains(c)));
            } else {
                // Make sure the odd character is percent-encoded.
                assert!(alice.userids().all(
                    |uid| str::from_utf8(uid.value()).unwrap().contains("%")));
            }
        };

        let test_fail = |certd: &CertD, url| {
            t!("trying {:?}", url);
            certd.shadow_ca_for_url(url).unwrap_err();
        };

        let certd = CertD::open(&path)?;

        // In the path:
        test(&certd, "https://example.org/alice\u{0a}bob.pgp", Some("\u{0a}"));
        test(&certd, "https://example.org/alice\u{0d}bob.pgp", Some("\u{0d}"));
        test(&certd, "https://example.org/alice\u{09}bob.pgp", Some("\u{09}"));
        test(&certd, "https://example.org/alice\u{0c}bob.pgp", Some("\u{0c}"));
        test(&certd, "https://example.org/alice\u{08}\u{08}\u{08}\u{08}\u{08}bob.pgp", None);
        test(&certd, "https://example.org/alice\u{200f}bob.pgp", None); // RTL mark

        // In the query:
        test(&certd, "https://example.org/alice.pgp?q=\u{0a}bob.pgp", Some("\u{0a}"));
        test(&certd, "https://example.org/alice.pgp?q=\u{0d}bob.pgp", Some("\u{0d}"));
        test(&certd, "https://example.org/alice.pgp?q=\u{09}bob.pgp", Some("\u{09}"));
        test(&certd, "https://example.org/alice.pgp?q=\u{0c}bob.pgp", Some("\u{0c}"));
        test(&certd, "https://example.org/alice.pgp?q=\u{08}\u{08}\u{08}\u{08}\u{08}bob.pgp", None);
        test(&certd, "https://example.org/alice.pgp?q=\u{200f}bob.pgp", None); // RTL mark

        // In the host:
        test(&certd, "https://example.\u{0a}bob.pgp.org/alice.pgp", Some("\u{0a}"));
        test(&certd, "https://example.\u{0d}bob.pgp.org/alice.pgp", Some("\u{0d}"));
        test(&certd, "https://example.\u{09}bob.pgp.org/alice.pgp", Some("\u{09}"));
        test_fail(&certd, "https://example.\u{0c}bob.pgp.org/alice.pgp");
        test_fail(&certd, "https://example.\u{08}\u{08}\u{08}\u{08}\u{08}bob.pgp.org/alice.pgp");
        test_fail(&certd, "https://example.\u{200f}bob.pgp.org/alice.pgp"); // RTL mark

        Ok(())
    }
}
