use std::collections::HashMap;
use std::collections::hash_map::Entry;
use std::fs::{DirBuilder, OpenOptions};
use std::sync::Arc;
use std::sync::Weak;
use std::ops::Deref;
use std::ops::DerefMut;
use std::path::Path;
use std::path::PathBuf;
#[cfg(unix)]
use std::os::unix::fs::OpenOptionsExt;

use futures::lock::Mutex;
use futures::lock::MutexGuard;

use anyhow::Context;

use sequoia_openpgp as openpgp;
use openpgp::Cert;
use openpgp::cert::prelude::*;
use openpgp::crypto::Decryptor;
use openpgp::crypto::mpi;
use openpgp::crypto::Password;
use openpgp::crypto::SessionKey;
use openpgp::crypto::Signer;
use openpgp::Result;
use openpgp::Fingerprint;
use openpgp::packet;
use openpgp::parse::Parse;
use openpgp::serialize::Serialize;
use openpgp::types::HashAlgorithm;
use openpgp::types::PublicKeyAlgorithm;

use sequoia_keystore_backend as backend;
use backend::Backend as _;
use backend::ImportStatus;
use backend::KeyHandle as _;
use backend::Error;
use backend::utils::Directory;
use backend::PasswordSource;
use backend::Protection;

/// Known soft keys.
#[derive(Clone)]
pub struct Backend {
    inner: Arc<Mutex<BackendInternal>>,
}

struct BackendInternal {
    // This usually looks like: `$SEQUOIA_HOME/keystore/softkeys`.
    // Each file in this directory that ends in `asc` or `pgp` is read
    // at start up or when [`Backend::scan`] is called.
    home: Directory,

    // Map from certificate fingerprint to Device.
    certs: HashMap<Fingerprint, Device>,

    // Map from key to certificate fingerprint.
    keys: HashMap<Fingerprint, Key>,
}

// If we do: backend.0.lock().unwrap().method(), then method doesn't
// have a reference to the `Arc`, which we sometimes need.  This
// conveniently encapsulates an `Arc` and a `MutexGuard`.
struct BackendLockGuard<'a>(MutexGuard<'a, BackendInternal>, Arc<Mutex<BackendInternal>>);

impl<'a> Deref for BackendLockGuard<'a> {
    type Target = MutexGuard<'a, BackendInternal>;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl<'a> DerefMut for BackendLockGuard<'a> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}

impl Backend {
    async fn lock(&self) -> BackendLockGuard {
        BackendLockGuard(self.inner.lock().await, self.inner.clone())
    }
}

/// A Device corresponds to a certificate.
// The certificate's fingerprint is also the id.  It's stored here to
// avoid having to lock DeviceInternal.
#[derive(Clone)]
pub struct Device {
    fpr: Fingerprint,
    inner: Arc<Mutex<DeviceInternal>>,
}

impl Device {
    /// Creates a new `WeakDevice` from this `Device`.
    fn downgrade(&self) -> WeakDevice {
        WeakDevice {
            fpr: self.fpr.clone(),
            inner: Arc::downgrade(&self.inner),
        }
    }
}

/// A `Device`, but with a weak reference to the data.
///
/// Before you use this, you need to upgrade this to a `Device`.
#[derive(Clone)]
pub struct WeakDevice {
    fpr: Fingerprint,
    inner: Weak<Mutex<DeviceInternal>>,
}

impl WeakDevice {
    /// Upgrades the `WeakDevice` to a `Device`, if possible.
    fn upgrade(&self) -> Option<Device> {
        Some(Device {
            fpr: self.fpr.clone(),
            inner: self.inner.upgrade()?,
        })
    }
}

struct DeviceInternal {
    #[allow(dead_code)]
    backend: Weak<Mutex<BackendInternal>>,

    cert: Cert,

    // The vector is sorted by the fingerprints.
    keys: Vec<WeakKey>,
}

/// A Key corresponds to an OpenPGP primary key or subkey.
// The key's fingerprint is also the id.  It's stored here to avoid
// having to lock KeyInternal.
#[derive(Clone)]
pub struct Key {
    fpr: Fingerprint,
    inner: Arc<Mutex<KeyInternal>>,
}

impl Key {
    /// Creates a new `WeakKey` from this `Key`.
    fn downgrade(&self) -> WeakKey {
        WeakKey {
            fpr: self.fpr.clone(),
            inner: Arc::downgrade(&self.inner),
        }
    }
}

/// A `Key`, but with a weak reference to the data.
///
/// Before you use this, you need to upgrade this to a `Key`.
#[derive(Clone)]
pub struct WeakKey {
    fpr: Fingerprint,
    inner: Weak<Mutex<KeyInternal>>,
}

impl WeakKey {
    /// Upgrades the `WeakKey` to a `Key`, if possible.
    fn upgrade(&self) -> Option<Key> {
        Some(Key {
            fpr: self.fpr.clone(),
            inner: self.inner.upgrade()?,
        })
    }
}

/// A secret key.
struct KeyInternal {
    #[allow(dead_code)]
    backend: Weak<Mutex<BackendInternal>>,

    // Back pointer to the device.
    device: WeakDevice,

    // The key's id (this is just the key's fingerprint).
    id: Fingerprint,

    // The Key data structure.
    //
    // We may have multiple variants for the same key (e.g., if they
    // are encrypted with different passwords).  The variants are
    // sorted according to when they are added or updated (oldest
    // first).  When exporting a key, we take the most recently added
    // or updated variant, i.e., the last one in this list.
    variants: Vec<KeyVariant>,

    // The unlocked variant, if any.
    unlocked: Option<KeyVariant>,

    // The public key.
    public_key: packet::Key<packet::key::PublicParts,
                            packet::key::UnspecifiedRole>,
}

#[derive(Debug, Clone)]
struct KeyVariant {
    /// The secret key material.
    key: packet::Key<packet::key::SecretParts, packet::key::UnspecifiedRole>,

    /// The file that the secret key material was read from.
    path: PathBuf,
}

impl Backend {
    /// Initializes a SoftKeys backend.
    ///
    /// `home` is the directory where the backend will look for its
    /// configuration.  If this is `None`, then it will look in
    /// `$HOME/.sq/keystore/softkeys`.
    ///
    /// Currently, this backend looks for certificates containing
    /// secret key material in files with an 'asc' or 'pgp' extension.
    /// Other files are silently ignored.
    pub async fn init<P: AsRef<Path>>(home: Option<P>) -> Result<Box<Self>> {
        log::trace!("Backend::init");

        let home = if let Some(home) = home {
            home.as_ref().into()
        } else if let Some(home) = dirs::home_dir() {
            home.join(".sq").join("keystore").join("softkeys")
        } else {
            return Err(anyhow::anyhow!("Failed get the home directory"));
        };

        log::debug!("Home directory is: {:?}", home);

        let mut backend = Backend {
            inner: Arc::new(Mutex::new(BackendInternal {
                home: home.clone().into(),
                keys: Default::default(),
                certs: Default::default(),
            })),
        };

        // Restrict access to the home directory to the current user.
        backend.restrict_access().await;

        if let Err(err) = backend.scan().await {
            log::debug!("Scanning {:?} for keys: {}", home, err);
        }

        Ok(Box::new(backend))
    }

    /// Initializes an ephemeral SoftKeys backend.
    pub async fn init_ephemeral() -> Result<Self> {
        log::trace!("Backend::init_ephemeral");

        let home = Directory::ephemeral()?;

        log::debug!("Home directory is: {:?}", home.display());

        let mut backend = Backend {
            inner: Arc::new(Mutex::new(BackendInternal {
                home: home.clone().into(),
                keys: Default::default(),
                certs: Default::default(),
            })),
        };

        // Restrict access to the home directory to the current user.
        backend.restrict_access().await;

        if let Err(err) = backend.scan().await {
            log::error!("Scanning {:?} for keys: {}", home, err);
        }

        Ok(backend)
    }


    /// Restricts access to the home directory to the current user.
    ///
    /// This is a best-effort mechanism.  And, the home directory
    /// might not even exist at this point.
    async fn restrict_access(&self) {
        fn restrict(home: &Path) -> Result<()> {
            #[cfg(unix)]
            {
                use std::fs;
                use std::os::unix::fs::PermissionsExt;

                let mut perms = fs::metadata(home)?.permissions();
                perms.set_mode(0o700);
                fs::set_permissions(home, perms)?;
            }

            Ok(())
        }

        let _ = restrict(&self.lock().await.home);
    }
}

impl BackendLockGuard<'_> {
    /// Ingests a certificate.
    ///
    /// This merges the secret key of a certificate into the backend.
    /// If the certificate does not contain any secret key material,
    /// then an empty vector is returned.
    ///
    /// This does NOT write the secret key material to disk; it merges
    /// the secret key material with the secret key material that is
    /// in memory.
    ///
    /// If `return_import_results`, this also returns the import
    /// status and a capability to each imported key.
    pub async fn ingest<'a>(&mut self, cert: Cert, file: PathBuf,
                            return_import_results: bool)
        -> Result<Vec<(ImportStatus,
                       Box<dyn backend::KeyHandle + Send + Sync + 'a>)>>
    {
        log::trace!("BackendLockGuard::ingest");

        let secret_keys = cert.keys().secret().count();
        log::trace!("Certificate {} has {} secret keys.",
                    cert.fingerprint(), secret_keys);

        if secret_keys == 0 {
            return Ok(Vec::new());
        }

        // Add an entry for the certificate.
        let device_entry = self.0.certs.entry(cert.fingerprint());
        let device = match device_entry {
            Entry::Occupied(oe) => {
                oe.get().clone()
            }
            Entry::Vacant(ve) => {
                let device = Device {
                    fpr: cert.fingerprint(),
                    inner: Arc::new(Mutex::new(DeviceInternal {
                        backend: Arc::downgrade(&self.1),
                        cert: cert.clone(),
                        keys: Vec::new(),
                    })),
                };
                ve.insert(device.clone());
                device
            }
        };

        // Add entries for the secret keys.
        let mut import_result = Vec::with_capacity(secret_keys);
        let mut keys = Vec::with_capacity(secret_keys);
        for ka in cert.keys().secret() {
            log::debug!("Adding {} (cert: {})", ka.key().keyid(), cert.keyid());

            let id = ka.key().fingerprint();

            match self.0.keys.entry(ka.key().fingerprint()) {
                Entry::Occupied(oe) => {
                    log::debug!("{} ({}) already present",
                                ka.key().keyid(), cert.keyid());

                    let sk = oe.get();

                    keys.push(sk.downgrade());

                    // We collect all secret key variants, but avoid
                    // duplicates.
                    let variants = &mut sk.inner.lock().await.variants;
                    let import_status = if let Some(i)
                        = variants.iter().position(|k| &k.key == ka.key())
                    {
                        log::debug!("{} ({}): variant unchanged",
                                    ka.key().keyid(), cert.keyid());

                        // Move it to the end.  When exporting, we
                        // want to export the most recently imported
                        // variant, which we assume is the last one.
                        let this_one = variants.swap_remove(i);
                        variants.push(this_one);
                        ImportStatus::Unchanged
                    } else {
                        log::debug!("{} ({}): new variant",
                                    ka.key().keyid(), cert.keyid());

                        variants.push(KeyVariant {
                            key: ka.key().clone(),
                            path: file.clone(),
                        });
                        ImportStatus::Updated
                    };

                    if return_import_results {
                        import_result.push((
                            import_status,
                            Box::new(sk.clone())
                                as Box<dyn backend::KeyHandle + Send + Sync + 'a>));
                    }
                }
                Entry::Vacant(ve) => {
                    let sk = Key {
                        fpr: id.clone(),
                        inner: Arc::new(Mutex::new(KeyInternal {
                            backend: Arc::downgrade(&self.1),
                            device: device.downgrade(),
                            id: id.clone(),
                            variants: vec![ KeyVariant {
                                key: ka.key().clone(),
                                path: file.clone(),
                            }],
                            unlocked: None,
                            public_key: ka.key().clone().take_secret().0,
                        })),
                    };
                    keys.push(sk.downgrade());
                    ve.insert(sk.clone());

                    if return_import_results {
                        import_result.push((
                            ImportStatus::New,
                            Box::new(sk)
                                as Box<dyn backend::KeyHandle + Send + Sync + 'a>));
                    }
                }
            }
        }

        // Merge cert.
        let mut e = device.inner.lock().await;

        e.cert = e.cert.clone().merge_public_and_secret(cert)
            .expect("same certificate");

        e.keys.append(&mut keys);

        e.keys.sort_by(|a, b| a.fpr.cmp(&b.fpr));
        e.keys.dedup_by(|a, b| a.fpr == b.fpr);

        Ok(import_result)
    }
}

#[async_trait::async_trait]
impl backend::Backend for Backend {
    fn id(&self) -> String {
        "softkeys".into()
    }

    async fn scan(&mut self) -> Result<()> {
        log::trace!("Backend::scan");

        let mut backend = self.lock().await;

        log::debug!("Scanning: {:?}", backend.home.display());
        for entry in backend.home.read_dir()
            .with_context(|| format!("{:?}", backend.home.display()))?
        {
            let entry = match entry {
                Ok(entry) => entry,
                Err(err) => {
                    log::debug!("While listing {:?}: {}",
                                backend.home.display(), err);
                    continue;
                }
            };

            let filename = entry.path();

            let read = if let Some(extension) = filename.extension() {
                if extension == "asc" || extension == "pgp" {
                    true
                } else {
                    false
                }
            } else {
                false
            };

            if ! read {
                log::debug!("Ignoring {:?}", filename);
                continue;
            }
            log::debug!("Considering: {:?}", filename);

            let parser = CertParser::from_file(&filename)?;
            for certr in parser {
                let cert = match certr {
                    Ok(cert) => cert,
                    Err(err) => {
                        log::info!("{:?} partially corrupted: {}",
                                   filename, err);
                        continue;
                    }
                };

                log::debug!("Found certificate {} in {:?}",
                            cert.keyid(), filename);

                backend.ingest(cert, filename.clone(), false).await?;
            }
        }

        Ok(())
    }

    async fn list<'a>(&'a self)
        -> Box<dyn Iterator<Item=Box<dyn backend::DeviceHandle + Send + Sync + 'a>>
               + Send + Sync + 'a>
    {
        log::trace!("Backend::list");

        let backend = self.inner.lock().await;

        Box::new(
            backend.certs.values()
                .map(|sc| {
                    Box::new(sc.clone())
                        as Box<dyn backend::DeviceHandle + Send + Sync + 'a>
                })
                .collect::<Vec<_>>()
                .into_iter())
    }

    async fn find_device<'a>(&self, id: &str)
        -> Result<Box<dyn backend::DeviceHandle + Send + Sync + 'a>>
    {
        log::trace!("Backend::find_device");

        let backend = self.inner.lock().await;

        backend.certs.get(&id.parse::<Fingerprint>()?)
            .map(|sc| {
                Box::new(sc.clone())
                    as Box<dyn backend::DeviceHandle + Send + Sync + 'a>
            })
            .ok_or_else(|| Error::NotFound(id.into()).into())
    }

    async fn find_key<'a>(&self, id: &str)
        -> Result<Box<dyn backend::KeyHandle + Send + Sync + 'a>>
    {
        log::trace!("Backend::find_key");

        let backend = self.lock().await;

        backend.keys.get(&id.parse::<Fingerprint>()?)
            .map(|sk| {
                Box::new(sk.clone()) as Box<dyn backend::KeyHandle + Send + Sync + 'a>
            })
            .ok_or_else(|| Error::NotFound(id.into()).into())
    }

    async fn import<'a>(&self, mut cert: Cert)
        -> Result<Vec<(ImportStatus,
                       Box<dyn backend::KeyHandle + Send + Sync + 'a>)>>
    {
        log::trace!("Backend::import");

        if ! cert.is_tsk() {
            // There's no secret key material and thus nothing to do.
            log::trace!("{} does not contain any secret key material.",
                        cert.fingerprint());
            return Ok(Vec::new());
        }

        let mut backend = self.lock().await;

        // Create all directories up to but excluding the last
        // component.
        if let Some(path) = backend.home.parent() {
            DirBuilder::new()
                .recursive(true)
                .create(path)
                .with_context(
                    || format!("creating {}", path.display()))?;
        }

        // And now create the last component.
        if ! backend.home.exists() {
            let mut builder = DirBuilder::new();
            #[cfg(unix)]
            {
                // Restrict access to the current user.
                use std::os::unix::fs::DirBuilderExt;
                builder.mode(0o700);
            }
            builder.create(&backend.home)
                .with_context(
                    || format!("creating {}", backend.home.display()))?;
        }

        let fpr = cert.fingerprint();

        let filename = backend.home.join(format!("{}.pgp", fpr));
        log::trace!("Updating {}.", filename.display());

        let changed = match Cert::from_file(&filename) {
            Ok(old) => {
                if old.fingerprint() != fpr {
                    return Err(anyhow::anyhow!(
                        "{} contains {}, but expected {}",
                        filename.display(),
                        old.fingerprint(),
                        fpr));
                }

                // Prefer the secret key material from `cert`.
                cert = old.clone().merge_public_and_secret(cert.clone())?;

                if cert.as_tsk() == old.as_tsk() {
                    // Nothing changed.
                    log::trace!("Certificate is unchanged.");
                    false
                } else {
                    true
                }
            }
            Err(err) => {
                // If the file doesn't exist yet, it's not an
                // error: it just means that we don't have to
                // merge.
                if let Some(ioerr) = err.downcast_ref::<std::io::Error>() {
                    if ioerr.kind() == std::io::ErrorKind::NotFound {
                        // Not found.  No problem.
                        true
                    } else {
                        return Err(err);
                    }
                } else {
                    return Err(err);
                }
            }
        };

        if changed {
            // We write to a temporary file and then move it into
            // place.  This doesn't eliminate races, but it does
            // prevent a partial update from destroying the existing
            // data.
            let mut f = RestrictedNamedTempFile::new_in(
                filename.parent().expect("file in softkeys"))?;
            cert.as_tsk().serialize(f.file())?;
            f.persist(&filename)?;
        }

        backend.ingest(cert, filename, true).await
    }
}

#[async_trait::async_trait]
impl backend::DeviceHandle for Device {
    fn id(&self) -> String {
        self.fpr.to_hex()
    }

    /// Certificates cannot be registered so if it exists, it is
    /// available.
    async fn available(&self) -> bool {
        true
    }

    /// Certificates cannot be unconfigured.
    async fn configured(&self) -> bool {
        true
    }

    /// Certificates cannot be registered.  (They are always available.)
    async fn registered(&self) -> bool {
        true
    }

    async fn lock(&mut self) -> Result<()> {
        log::trace!("DeviceHandle::lock");

        let mut dh = self.inner.lock().await;

        for k in dh.keys.iter_mut() {
            if let Some(mut kh) = k.upgrade() {
                if let Err(err) = kh.lock().await {
                    log::debug!("Error locking key {}: {}",
                                kh.id(), err);
                }
            }
        }

        Ok(())
    }

    async fn list<'a>(&'a self)
        -> Box<dyn Iterator<Item=Box<dyn backend::KeyHandle + Send + Sync + 'a>>
               + Send + Sync + 'a>
    {
        log::trace!("DeviceHandle::list");

        let device = self.inner.lock().await;

        Box::new(
            device
                .keys
                .iter()
                .filter_map(|k| {
                    Some(Box::new(k.upgrade()?)
                         as Box<dyn sequoia_keystore_backend::KeyHandle
                                + Send + Sync>)
                })
                .collect::<Vec<_>>()
                .into_iter())
    }
}

#[async_trait::async_trait]
impl backend::KeyHandle for Key {
    fn id(&self) -> String {
        self.fpr.to_hex()
    }

    fn fingerprint(&self) -> Fingerprint {
        log::trace!("KeyHandle::fingerprint");

        self.fpr.clone()
    }

    async fn device<'a>(&self) -> Box<dyn backend::DeviceHandle + Send + Sync + 'a> {
        let kh = self.inner.lock().await;
        Box::new(kh.device.upgrade().expect("device outlines keys"))
    }

    async fn available(&self) -> bool {
        true
    }

    async fn locked(&self) -> Protection {
        let kh = self.inner.lock().await;
        let unlocked = kh.unlocked.is_some()
            || kh.variants.iter().any(|k| k.key.has_unencrypted_secret());
        if unlocked {
            Protection::Unlocked
        } else {
            Protection::Password(None)
        }
    }

    async fn password_source(&self) -> PasswordSource {
        PasswordSource::Inline
    }

    async fn decryption_capable(&self) -> bool {
        let kh = self.inner.lock().await;

        if kh.variants.is_empty() {
            // Key was deleted.
            return false;
        }

        let variant = &kh.variants[0];

        // XXX: Also look up the certificate in the cert store and
        // check the key flags.
        variant.key.pk_algo().for_encryption()
    }

    async fn signing_capable(&self) -> bool {
        let kh = self.inner.lock().await;

        if kh.variants.is_empty() {
            // Key was deleted.
            return false;
        }

        let variant = &kh.variants[0];

        // XXX: Also look up the certificate in the cert store and
        // check the key flags.
        variant.key.pk_algo().for_signing()
    }

    async fn unlock(&mut self, password: Option<&Password>) -> Result<()> {
        log::trace!("KeyHandle::unlock({})", self.fpr);

        let mut kh = self.inner.lock().await;

        if kh.variants.is_empty() {
            // Key was deleted.
            return Err(Error::NotFound("key was deleted".into()).into());
        }

        let password = if let Some(password) = password {
            password
        } else {
            return Err(Error::NoExternalPassword(None).into());
        };

        if kh.unlocked.is_some() {
            log::trace!("KeyHandle::unlock({}): Already unlocked", self.fpr);
            return Err(Error::AlreadyUnlocked(kh.id.to_hex()).into());
        }

        let mut err = None;
        for variant in kh.variants.iter() {
            let mut unlocked = variant.key.clone();
            match unlocked.secret_mut()
                .decrypt_in_place(&variant.key, password)
            {
                Ok(()) => {
                    log::trace!("KeyHandle::unlock({}): Unlocked", self.fpr);
                    kh.unlocked = Some(KeyVariant {
                        key: unlocked,
                        path: variant.path.clone(),
                    });
                    return Ok(());
                }
                Err(err_) => {
                    err = Some(err_);
                }
            }
        }

        let err = err.expect("at least one key variant");
        log::trace!("KeyHandle::unlock({}): Error: {}", self.fpr, err);
        Err(err)
    }

    async fn lock(&mut self) -> Result<()> {
        log::trace!("KeyHandle::lock({})", self.fpr);

        let mut kh = self.inner.lock().await;
        kh.unlocked = None;
        Ok(())
    }

    async fn public_key(&self)
        -> packet::Key<packet::key::PublicParts,
                       packet::key::UnspecifiedRole>
    {
        log::trace!("KeyHandle::public_key");

        self.inner.lock().await.public_key.clone()
    }

    async fn decrypt_ciphertext(&mut self,
                          ciphertext: &mpi::Ciphertext,
                          plaintext_len: Option<usize>)
        -> Result<SessionKey>
    {
        log::trace!("KeyHandle::decrypt_ciphertext");

        let kh = self.inner.lock().await;

        if kh.variants.is_empty() {
            // Key was deleted.
            return Err(Error::NotFound("key was deleted".into()).into());
        }

        // We may have multiple variants.  We try them in turn unless
        // a key is already unlocked in which case we only try that
        // one.
        let variants: Box<dyn Iterator<Item=&KeyVariant>>
            = if let Some(k) = kh.unlocked.as_ref() {
                Box::new(std::iter::once(k))
            } else {
                Box::new(kh.variants.iter())
            };

        let mut err = None;
        for variant in variants {
            let key = &variant.key;
            if key.secret().is_encrypted() {
                if err.is_none() {
                    err = Some(Error::Locked(key.fingerprint().to_hex()).into());
                }
                continue;
            }

            // `into_keypair` fails if the key is encrypted, but we
            // already checked that it isn't.
            let mut keypair = key.clone().into_keypair().expect("decrypted");

            match keypair.decrypt(ciphertext, plaintext_len) {
                Ok(sk) => {
                    log::debug!("Decrypted ciphertext using {}", key.keyid());
                    return Ok(sk);
                }
                Err(err_) => {
                    log::debug!("Decrypting ciphertext using {}: {}",
                                key.keyid(), err_);
                    if err.is_none() {
                        err = Some(err_);
                    }
                }
            }
        }

        Err(err.expect("have key variants that failed"))
    }

    async fn sign(&mut self, hash_algo: HashAlgorithm, digest: &[u8])
        -> Result<(PublicKeyAlgorithm, mpi::Signature)>
    {
        log::trace!("KeyHandle::sign");

        let kh = self.inner.lock().await;

        if kh.variants.is_empty() {
            // Key was deleted.
            return Err(Error::NotFound("key was deleted".into()).into());
        }

        // We may have multiple variants.  We try them in turn unless
        // a key is already unlocked in which case we only try that
        // one.
        let variants: Box<dyn Iterator<Item=&KeyVariant>>
            = if let Some(k) = kh.unlocked.as_ref() {
                Box::new(std::iter::once(k))
            } else {
                Box::new(kh.variants.iter())
            };

        // Remember the first error.
        let mut err = None;
        for variant in variants {
            let key = &variant.key;

            if key.secret().is_encrypted() {
                if err.is_none() {
                    err = Some(Error::Locked(key.fingerprint().to_hex()).into());
                }
                continue;
            }

            // `into_keypair` fails if the key is encrypted, but we
            // already checked that it isn't.
            let mut keypair = key.clone().into_keypair().expect("decrypted");

            match keypair.sign(hash_algo, digest) {
                Ok(sig) => return Ok((key.pk_algo(), sig)),
                Err(err_) => err = Some(err_),
            }
        }

        Err(err.expect("At least one key variant"))
    }

    async fn export(&mut self)
        -> Result<openpgp::packet::Key<
            openpgp::packet::key::SecretParts,
            openpgp::packet::key::UnspecifiedRole>>
    {
        log::trace!("KeyHandle::export({})", self.fingerprint());

        let kh = self.inner.lock().await;

        if kh.variants.is_empty() {
            // Key was deleted.
            return Err(Error::NotFound("key was deleted".into()).into());
        }

        // We may have multiple variants.  We take the one that was
        // added last, which is the last variant in the vector.
        let variant = &kh.variants.iter().last().expect("have a variant");

        Ok(variant.key.clone())
    }

    async fn change_password(&mut self, password: Option<&Password>)
        -> Result<()>
    {
        log::trace!("KeyHandle::change_password({}, {})",
                    self.fingerprint(),
                    if let Some(password) = password {
                        if password.map(|p| p.is_empty()) {
                            "clear password"
                        } else {
                            "set password"
                        }
                    } else {
                        "ask for password"
                    });

        let password = if let Some(password) = password {
            password
        } else {
            return Err(Error::NoExternalPassword(None).into());
        };

        let mut kh = self.inner.lock().await;

        if kh.variants.is_empty() {
            // Key was deleted.
            return Err(Error::NotFound("key was deleted".into()).into());
        }

        // We may have multiple variants.  We try them in turn unless
        // a key is already unlocked in which case we only try that
        // one.
        let variants: Box<dyn Iterator<Item=&KeyVariant>>
            = if let Some(k) = kh.unlocked.as_ref() {
                Box::new(std::iter::once(k))
            } else {
                Box::new(kh.variants.iter())
            };

        // Extract the variant.  We need to exit the loop, because we
        // want to move the variant to the end of `variants`, but we
        // can't change `variants` while in the loop.
        let mut result = Ok(None);
        for variant in variants {
            let key = &variant.key;
            if key.secret().is_encrypted() {
                if let Ok(None) = result {
                    result = Err(Error::Locked(key.fingerprint().to_hex()).into());
                }
            } else {
                result = Ok(Some(variant.clone()));
                break;
            }
        }

        let variant = match result {
            Ok(Some(variant)) => variant,
            Ok(None) => {
                panic!("Have at least one key variant");
            }
            Err(err) => return Err(err),
        };

        let mut new_key = variant.key.clone();
        // If the password is "", we remove the password protection.
        if ! password.map(|p| p.is_empty()) {
            new_key = new_key.encrypt_secret(&password)?;
        }

        // Find the original variant.
        let i = kh.variants.iter().position(|v| v.path == variant.path)
            .expect("have variant");

        // Update the on-disk copy.
        let cert = Cert::from_file(&variant.path)
            .with_context(|| {
                format!("Reading {}", variant.path.display())
            })?;
        let orig_key = cert.keys().find(|k| {
            k.key().fingerprint() == new_key.fingerprint()
        });

        let orig_key = if let Some(orig_key) = orig_key {
            orig_key
        } else {
            log::info!("Certificate in {} changed: {} disappeared",
                       variant.path.display(), new_key.fingerprint());
            return Err(Error::InvalidArgument(format!(
                "On-disk data structures changed. \
                 {} disappeared.",
                new_key.fingerprint())).into());
        };

        let cert = if orig_key.primary() {
            cert.insert_packets(new_key.clone().role_into_primary())
                .with_context(|| {
                    format!("Updating {}", variant.path.display())
                })?.0
        } else {
            cert.insert_packets(new_key.clone().role_into_subordinate())
                .with_context(|| {
                    format!("Updating {}", variant.path.display())
                })?.0
        };

        let mut f = RestrictedNamedTempFile::new_in(
            variant.path.parent().expect("file in softkeys"))
            .with_context(|| {
                format!("Creating temporary file in {}",
                        variant.path.parent().expect("file in softkeys")
                        .display())
            })?;

        log::debug!("Writing updated certificate to {}",
                    f.path().display());
        cert.as_tsk().serialize(f.file())
            .with_context(|| {
                format!("Updating {}", f.path().display())
            })?;

        log::debug!("Moving {} to {}",
                    f.path().display(), variant.path.display());
        f.persist(&variant.path)
            .with_context(|| {
                format!("Updating {}", variant.path.display())
            })?;

        // Move it to the end.  When exporting, we
        // want to export the most recently imported
        // variant, which we assume is the last one.
        let mut orig = kh.variants.swap_remove(i);
        orig.key = new_key;
        kh.variants.push(orig);

        Ok(())
    }

    async fn delete_secret_key_material(&mut self)
        -> Result<()>
    {
        log::trace!("KeyHandle::delete_secret_key_material");

        let mut kh = self.inner.lock().await;

        if kh.variants.is_empty() {
            // Key was deleted.
            return Err(Error::NotFound("key was deleted".into()).into());
        }

        let fpr = self.fpr.clone();

        while let Some(variant) = kh.variants.pop() {
            // Update the on-disk copy.
            assert_eq!(variant.key.fingerprint(), fpr);

            let cert = Cert::from_file(&variant.path)
                .with_context(|| {
                    format!("Reading {}", variant.path.display())
                })?;

            let orig_key = cert.keys().find(|k| {
                k.key().fingerprint() == fpr
            });
            let orig_key = if let Some(orig_key) = orig_key {
                orig_key
            } else {
                log::info!("Certificate in {} changed: {} disappeared",
                           variant.path.display(), fpr);
                return Err(Error::InvalidArgument(format!(
                    "On-disk data structures changed. {} disappeared.",
                    fpr)).into());
            };

            let cert = if orig_key.primary() {
                cert.insert_packets(kh.public_key.clone().role_into_primary())
                    .with_context(|| {
                        format!("Updating {}", variant.path.display())
                    })?.0
            } else {
                cert.insert_packets(kh.public_key.clone().role_into_subordinate())
                    .with_context(|| {
                        format!("Updating {}", variant.path.display())
                    })?.0
            };

            if ! cert.is_tsk() {
                log::debug!("{} has no secret key material left, removing {}.",
                            cert.fingerprint(),
                            variant.path.display());
                std::fs::remove_file(&variant.path)
                    .with_context(|| {
                        format!("Removing {}", variant.path.display())
                    })?;
            } else {
                log::debug!("Removing {} from {}.",
                            fpr, variant.path.display());

                let mut f = RestrictedNamedTempFile::new_in(
                    variant.path.parent().expect("file in softkeys"))
                    .with_context(|| {
                        format!("Creating temporary file in {}",
                                variant.path.parent().expect("file in softkeys")
                                .display())
                    })?;
                log::debug!("Writing updated certificate to {}",
                            f.path().display());
                cert.as_tsk().serialize(f.file())
                    .with_context(|| {
                        format!("Updating {}", f.path().display())
                    })?;

                log::debug!("Moving {} to {}",
                            f.path().display(), variant.path.display());
                f.persist(&variant.path)
                    .with_context(|| {
                        format!("Updating {}", variant.path.display())
                    })?;
            }
        }

        kh.unlocked = None;

        if let Some(device) = kh.device.upgrade() {
            // Remove the key from the device.
            drop(kh);

            let mut dh = device.inner.lock().await;

            let mut index = None;
            for (i, key) in dh.keys.iter().enumerate() {
                if key.fpr == fpr {
                    index = Some(i);
                    break;
                }
            }

            if let Some(i) = index {
                dh.keys.swap_remove(i);
            }

            // And remove it from the backend.
            if let Some(backend) = dh.backend.upgrade() {
                let device_fpr = device.fpr.clone();
                let remove_device = dh.keys.is_empty();
                drop(dh);

                let mut bh = backend.lock().await;

                bh.keys.remove(&fpr);

                if remove_device {
                    // Remove the device from the backend, too.
                    bh.certs.remove(&device_fpr);
                }
            }
        }

        Ok(())
    }
}

/// Similar to `tempfile::NamedTempFile`, but restricts access to the
/// created files to the current user on UNIX.
struct RestrictedNamedTempFile {
    path: PathBuf,
    file: std::fs::File,
}

impl RestrictedNamedTempFile {
    /// Creates a new temporary file in the given directory.
    fn new_in(dir: &Path) -> Result<Self> {
        loop {
            let mut r = [0; 4];
            openpgp::crypto::random(&mut r)?;
            let name =
                format!("{}.tmp", openpgp::fmt::hex::encode(&r));
            let path = dir.join(name);

            let mut o = OpenOptions::new();
            o.create_new(true)
                .write(true);
            #[cfg(unix)]
            o.mode(0o600);

            match o.open(&path) {
                Ok(file) => return Ok(RestrictedNamedTempFile {
                    path,
                    file,
                }),

                Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists =>
                    continue, // Retry.

                Err(e) => return Err(e.into()),
            }
        }
    }

    /// Returns the temporary path.
    fn path(&self) -> &Path {
        &self.path
    }

    /// Returns a mutable reference to the open file.
    fn file(&mut self) -> &mut std::fs::File {
        &mut self.file
    }

    /// Persists the temporary file under the given name.
    fn persist(self, path: &Path) -> Result<()> {
        std::fs::rename(&self.path, path)?;
        Ok(())
    }
}

impl Drop for RestrictedNamedTempFile {
    fn drop(&mut self) {
        let _ = std::fs::remove_file(&self.path);
    }
}

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

    use backend::test_framework;

    fn preinit() -> bool {
        true
    }

    async fn init_backend() -> Backend {
        let backend = Backend::init_ephemeral().await
            .expect("creating an ephemeral backend");

        backend
    }

    async fn import_cert(backend: &mut Backend, cert: &Cert) {
        backend.import(cert.clone()).await.expect("can import");
    }

    sequoia_keystore_backend::generate_tests!(
        preinit, false, // Tests don't need to be serialized.
        Backend, init_backend,
        import_cert,
        true, // Can import encrypted secret key material.
        None, // unlimited key sets
        None, // No default password
        true, // Can export.
        true, // Can change password.
        true // Can delete secret key material.
    );

    #[tokio::test]
    pub async fn decrypt_multiple_variants() -> Result<()> {
        // Load two variants of a certificate: one where the keys are
        // encrypted with the password "bob" and the other where they
        // are encrypted with the password "smithy".  Make sure we can
        // decrypt the message using either passwords.

        let mut backend = init_backend().await;

        let cert = Cert::from_bytes(
            test_framework::data::key("bob-password-bob.asc"))
            .expect("valid cert");
        import_cert(&mut backend, &cert).await;

        let cert = Cert::from_bytes(
            test_framework::data::key("bob-password-smithy.asc"))
            .expect("valid cert");
        import_cert(&mut backend, &cert).await;

        let msg: &[u8] = test_framework::data::message("bob.asc");
        let keyid = KeyHandle::KeyID("0A4BA2249168D779".parse().expect("valid"));

        test_framework::try_decrypt(
            &mut backend, msg, Some(b"hi bob\n"),
            keyid.clone(), Some(Password::from("bob"))).await.unwrap();
        test_framework::try_decrypt(
            &mut backend, msg, Some(b"hi bob\n"),
            keyid.clone(), Some(Password::from("smithy"))).await.unwrap();

        assert!(test_framework::try_decrypt(
            &mut backend, msg, Some(b"hi bob\n"),
            keyid, Some(Password::from("sup3r s3cur3"))).await.is_err());

        Ok(())
    }

    #[tokio::test]
    pub async fn decrypt_multiple_variants2() -> Result<()> {
        // Load two variants of a certificate: one where the keys are
        // encrypted with the password "bob" and the other where they
        // are not encrypted.  Make sure we can decrypt the message
        // with and without the password.

        let mut backend = init_backend().await;

        let cert = Cert::from_bytes(
            test_framework::data::key("bob-password-bob.asc"))
            .expect("valid cert");
        import_cert(&mut backend, &cert).await;

        let cert2 = Cert::from_bytes(
            test_framework::data::key("bob-no-password.asc"))
            .expect("valid cert");
        import_cert(&mut backend, &cert2).await;

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

        let msg: &[u8] = test_framework::data::message("bob.asc");
        let keyid = KeyHandle::KeyID("0A4BA2249168D779".parse()?);

        eprintln!("Decrypting with bob (correct)");
        test_framework::try_decrypt(
            &mut backend, msg, Some(b"hi bob\n"),
            keyid.clone(), Some(Password::from("bob"))).await.unwrap();

        eprintln!("Decrypting with sup3r s3cur3 (incorrect)");
        assert!(test_framework::try_decrypt(
            &mut backend, msg, Some(b"hi bob\n"),
            keyid.clone(), Some(Password::from("sup3r s3cur3"))).await.is_err());

        Ok(())
    }

    #[tokio::test]
    pub async fn verify_with_passwords2() -> Result<()> {
        let _ = env_logger::Builder::from_default_env().try_init();

        // Sign a message using a key that is password protected.  To
        // make it trickier, use a key with multiple variants.

        let mut backend = init_backend().await;

        let cert = Cert::from_bytes(
            test_framework::data::key("bob-password-bob.asc"))
            .expect("valid cert");
        import_cert(&mut backend, &cert).await;
        let cert = Cert::from_bytes(
            test_framework::data::key("bob-password-smithy.asc"))
            .expect("valid cert");
        import_cert(&mut backend, &cert).await;

        let signing_key = "9410E96E68643821794608269CB5006116833EE7";

        let mut k = backend.find_key(signing_key).await?;
        k.unlock(Some(&Password::from("bob"))).await?;

        let signing_fpr = Fingerprint::from_hex(signing_key)
            .expect("valid");
        let signer = backend.find_key(signing_key).await?;

        test_framework::sign_verify(
            vec![ signer.into(), ],
            vec![ cert.clone() ],
            Some(&[ signing_fpr ][..]))?;

        k.lock().await?;
        k.unlock(Some(&Password::from("smithy"))).await?;

        let signing_fpr = Fingerprint::from_hex(signing_key)
            .expect("valid");
        let signer = backend.find_key(signing_key).await?;

        test_framework::sign_verify(
            vec![ signer.into(), ],
            vec![ cert.clone() ],
            Some(&[ signing_fpr ][..]))?;

        Ok(())
    }

    #[tokio::test]
    pub async fn changed_password_saved() -> Result<()> {
        let _ = env_logger::Builder::from_default_env().try_init();

        // Change a certificate's password.  Make sure it is written
        // to disk.

        let mut backend = init_backend().await;

        let cert = Cert::from_bytes(
            test_framework::data::key("bob-password-bob.asc"))
            .expect("valid cert");
        import_cert(&mut backend, &cert).await;

        let msg: &[u8] = test_framework::data::message("bob.asc");
        let fpr_str = "86353DE523CB334DC0B1F0F00A4BA2249168D779";
        let fpr: sequoia_openpgp::Fingerprint
            = fpr_str.parse().expect("valid");
        let keyid = KeyHandle::KeyID(fpr.into());

        // Wrong password.
        assert!(test_framework::try_decrypt(
            &mut backend, msg, Some(b"hi bob\n"),
            keyid.clone(), Some(Password::from("sup3r s3cur3"))).await.is_err());

        // Right password.
        test_framework::try_decrypt(
            &mut backend, msg, Some(b"hi bob\n"),
            keyid.clone(), Some(Password::from("bob"))).await.unwrap();

        // Change the password.
        let mut key = backend.find_key(fpr_str)
            .await.expect("key is present");

        key.change_password(Some(&"new password".into()))
            .await.expect("can change password");

        // Relock the key.
        key.lock().await.expect("can lock key");

        log::info!("Decrypting with wrong password:");
        assert!(test_framework::try_decrypt(
            &mut backend, msg, Some(b"hi bob\n"),
            keyid.clone(), Some(Password::from("sup3r s3cur3"))).await.is_err());
        log::info!("Decrypting with old password:");
        assert!(test_framework::try_decrypt(
            &mut backend, msg, Some(b"hi bob\n"),
            keyid.clone(), Some(Password::from("smithy"))).await.is_err());
        log::info!("Decrypting with new password:");
        test_framework::try_decrypt(
            &mut backend, msg, Some(b"hi bob\n"),
            keyid.clone(), Some(Password::from("new password"))).await.unwrap();

        // Copy the backend's directory.
        let home = {
            let backend = backend.lock().await;
            backend.home.clone()
        };

        let new_home
            = tempfile::Builder::new().prefix("sq-keystore").tempdir()?;

        dircpy::copy_dir(&home, new_home.path())
            .with_context(|| format!("Copying {:?} to {:?}",
                                     home, new_home))?;

        let mut new_backend = *Backend::init(Some(new_home.path())).await
            .expect(&format!("opening backend {}", new_home.path().display()));

        // Wrong password.
        assert!(test_framework::try_decrypt(
            &mut new_backend, msg, Some(b"hi bob\n"),
            keyid.clone(), Some(Password::from("sup3r s3cur3"))).await.is_err());

        // Old password.
        assert!(test_framework::try_decrypt(
            &mut new_backend, msg, Some(b"hi bob\n"),
            keyid.clone(), Some(Password::from("bob"))).await.is_err());

        // New password.
        test_framework::try_decrypt(
            &mut new_backend, msg, Some(b"hi bob\n"),
            keyid.clone(), Some(Password::from("new password"))).await.unwrap();

        Ok(())
    }
}
