use std::collections::HashMap;
use std::collections::hash_map::Entry;
use std::path::Path;
use std::sync::Arc;
use std::sync::Weak;
use std::time::Duration;
use std::time::SystemTime;

use futures::lock::Mutex;

use card_backend_pcsc;

use openpgp_card::ocard::crypto::Cryptogram;
use openpgp_card::ocard::data::KeyStatus;
use openpgp_card::ocard::KeyType;
use openpgp_card::ocard::Transaction;

use sequoia_openpgp as openpgp;
use openpgp::Cert;
use openpgp::Fingerprint;
use openpgp::Result;
use openpgp::crypto::mem::Protected;
use openpgp::crypto::ecdh;
use openpgp::crypto::mpi;
use openpgp::crypto::Password;
use openpgp::crypto::SessionKey;
use openpgp::packet;
use openpgp::types::Curve;
use openpgp::types::HashAlgorithm;
use openpgp::types::PublicKeyAlgorithm;

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

mod certd;

// XXX: openpgp-card-sequoia is deprecated.  We've copied what we
// need, but we should fork it.
#[cfg(test)]
mod privkey;

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

struct BackendInternal {
    home: Directory,

    // False if the backend is disabled.
    enabled: bool,

    // OpenPGP cards don't store the OpenPGP key data structures nor
    // do they (always) have all the information required to
    // reconstruct them (in particular, it's missing the ECC algorithm
    // parameters).  Since looking up a key by subkey means doing a
    // full scan, we cache the results.
    certd: Arc<certd::CertD>,

    // One device per OpenPGP card.
    // XXX: Make this a HashMap keyed on the card id.
    devices: Vec<Device>,

    last_scan: Option<SystemTime>,
}

/// A Device exposes the (usable) keys managed by a particular
/// OpenPGP Card.
///
/// A key is usable if we have the OpenPGP data structure.  If we
/// don't have the OpenPGP key, we ignore the key.
#[derive(Clone)]
pub struct Device {
    // "application identifier".
    id: String,
    inner: Arc<Mutex<DeviceInternal>>
}

impl Device {
    /// Creates a new `WeakDevice` from this `Device`.
    fn downgrade(&self) -> WeakDevice {
        WeakDevice {
            id: self.id.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 {
    id: String,
    inner: Weak<Mutex<DeviceInternal>>,
}

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

struct DeviceInternal {
    /// The "application identifier".
    ///
    /// This is of the form `FFFE:43233446`.  It identifies both a
    /// card and the OpenPGP application.
    id: String,

    /// The opened card.
    ///
    /// The card is open in shared mode so that other applications can
    /// use it.
    card: openpgp_card::Card<openpgp_card::state::Open>,

    /// Whether the card has a pinpad.
    pinpad: bool,

    /// A map from id (fingerprint) to key.
    keys: HashMap<Fingerprint, Key>,
}

#[derive(Clone)]
pub struct Key {
    // The fingerprint is also the id.
    fpr: Fingerprint,
    inner: Arc<Mutex<KeyInternal>>,
}

/// A secret key.
struct KeyInternal {
    device: WeakDevice,

    fingerprint: Fingerprint,
    public_key: packet::Key<packet::key::PublicParts,
                            packet::key::UnspecifiedRole>,

    /// The cached password.
    ///
    /// OpenPGP cards can have up to three passwords: an admin
    /// password, a user password, and an optional signing password.
    /// The user password unlocks the decryption and authentication
    /// slots.  If configured, the signing password unlocks the
    /// signing slot.  Otherwise, the user password unlocks the
    /// signing slot.
    ///
    /// We simplify things by having a per-slot password.  This means
    /// that occasionally we'll ask for a password even though we know
    /// it (e.g., we've cached the password for the decryption key,
    /// and we're trying to use the authentication key), but that's
    /// not a big deal.
    password: Option<Password>,

    /// The key's slot.
    slot: openpgp_card::ocard::KeyType,
}

impl Backend {
    /// Initializes an openpgp card backend.
    ///
    /// `home` is the directory where the backend will look for its
    /// configuration, e.g., `$HOME/.sq/keystore/openpgp-card`.
    ///
    /// If `default` is true, this backend uses the cards managed by
    /// PCSC.  Otherwise, the backend is disabled.
    pub async fn init<P: AsRef<Path>>(home: P, default: bool)
        -> Result<Self>
    {
        log::trace!("Backend::init");

        let home = Directory::from(home.as_ref());

        let certd = Arc::new(certd::CertD::new()?);

        Ok(Backend {
            inner: Arc::new(Mutex::new(BackendInternal {
                home,
                enabled: default,
                certd,
                devices: vec![],
                last_scan: None,
            }))
        })
    }

    /// Initializes an ephemeral OpenPGP card backend.
    ///
    /// This is primarily useful for testing.
    pub async fn init_ephemeral() -> Result<Self> {
        log::trace!("Backend::init_ephemeral");

        let home = Directory::ephemeral()?;
        // XXX: default should be false, not true
        Self::init(home, true).await
    }
}

impl BackendInternal {
    async fn scan_internal(&mut self, force: bool) -> Result<()> {
        log::trace!("Backend::scan");

        if ! self.enabled {
            log::debug!("OpenPGP Card backend is disabled");
            return Ok(());
        }

        if ! force {
            // Don't scan too often.  Rate limit it to once every few seconds.
            if let Some(last_scan) = self.last_scan {
                if let Ok(duration) = SystemTime::now().duration_since(last_scan) {
                    if duration < Duration::new(3, 0) {
                        return Ok(())
                    }
                }
            }
        }

        let certd = Arc::clone(&self.certd);

        let card_backends = match card_backend_pcsc::PcscBackend::cards(None) {
            Ok(card_backends) => card_backends.collect(),
            Err(err) => {
                log::debug!("Listing openpgp cards using PC/SC: {}", err);
                Vec::new()
            }
        };

        // XXX: Remove keys and devices that are not longer available.

        for card_backend in card_backends.into_iter() {
            let card_backend = match card_backend {
                Ok(card_backend) => card_backend,
                Err(err) => {
                    log::debug!("Opening openpgp card backend: {}", err);
                    continue;
                }
            };

            let mut card = match openpgp_card::Card::new(card_backend) {
                Ok(card) => card,
                Err(err) => {
                    log::debug!("Opening openpgp card: {}", err);
                    continue;
                }
            };

            let mut tx = match card.transaction() {
                Ok(tx) => tx,
                Err(err) => {
                    log::debug!("Starting transaction on openpgp card: {}", err);
                    continue;
                }
            };

            // The device's identifier.
            let id = match tx.application_identifier() {
                Ok(id) => id,
                Err(err) => {
                    log::debug!("Getting application id from openpgp card: {}", err);
                    continue;
                }
            };
            let id = id.ident();

            // Gather information about the keys.
            let keyinfo = match tx.key_information() {
                Ok(keyinfo) => keyinfo,
                Err(err) => {
                    log::debug!("Getting key information from openpgp card: {}", err);
                    continue;
                }
            };

            let present = if let Some(keyinfo) = keyinfo {
                [
                    keyinfo.sig_status() != KeyStatus::NotPresent,
                    keyinfo.dec_status() != KeyStatus::NotPresent,
                    keyinfo.aut_status() != KeyStatus::NotPresent
                ]
            } else {
                [false, false, false]
            };

            let fprs = match tx.fingerprints() {
                Ok(tx) => tx,
                Err(err) => {
                    log::debug!("Getting fingerprints from openpgp card: {}", err);
                    continue;
                }
            };

            let pinpad = tx.feature_pinpad_verify();

            drop(tx);

            // See if we already know about this device.
            let mut device = None;
            for d in self.devices.iter_mut() {
                if d.id == id {
                    device = Some(d);
                }
            }

            let device = if let Some(device) = device {
                // Already known.
                device
            } else {
                // Create a new device.
                let device = Device {
                    id: id.clone(),
                    inner: Arc::new(Mutex::new(DeviceInternal {
                        id: id.clone(),
                        keys: HashMap::new(),
                        card: card,
                        pinpad,
                    })),
                };

                self.devices.push(device);

                self.devices.last_mut().unwrap()
            };

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

            let fprs = &[
                (fprs.signature(), KeyType::Signing),
                (fprs.decryption(), KeyType::Decryption),
                (fprs.authentication(), KeyType::Authentication),
            ][..];

            for ((fpr, slot), present) in fprs.into_iter().zip(present.into_iter()) {
                let Some(fpr) = fpr else { continue };
                // Convert from an
                // openpgp_card::ocard::data::Fingerprint to a
                // sequoia_openpgp::Fingerprint.
                let fpr = Fingerprint::from_bytes(4, fpr.as_bytes())?;
                log::trace!("{}: {}present",
                            fpr, if present { "" } else { "NOT " });

                match device_internal.keys.entry(fpr.clone()) {
                    Entry::Occupied(_oe) => {
                        // Already have it.
                    }
                    Entry::Vacant(ve) => {
                        // Need to add it.
                        log::debug!("Found key {}", fpr);

                        if let Some(pk) = certd.find_one(&fpr).await {
                            let key = Key {
                                fpr: fpr.clone(),
                                inner: Arc::new(Mutex::new(KeyInternal {
                                    device: device.downgrade(),
                                    fingerprint: fpr,
                                    // XXX
                                    public_key: pk,
                                    password: None,
                                    slot: slot.clone(),
                                })),
                            };

                            ve.insert(key);
                        } else {
                            log::debug!("Ignoring {}: no public key available",
                                        fpr);
                        }
                    }
                }
            }
        }

        self.last_scan = Some(SystemTime::now());

        Ok(())
    }
}

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

    async fn scan(&mut self) -> Result<()> {
        let mut backend = self.inner.lock().await;
        backend.scan_internal(false).await
    }

    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 mut backend = self.inner.lock().await;

        if let Err(err) = backend.scan_internal(false).await {
            log::debug!("Scanning OpenPGP Cards: {}", err);
        }

        Box::new(
            backend.devices.iter()
                .map(|device| {
                    Box::new(device.clone())
                        as Box<dyn backend::DeviceHandle + Send + Sync>
                })
                .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 mut backend = self.inner.lock().await;

        // The first time through we look for the key without
        // scanning.  If we don't find it, then we rescan.
        for scan in [false, true] {
            if scan {
                log::trace!("Rescanning");
                if let Err(err) = backend.scan_internal(true).await {
                    log::debug!("Scanning OpenPGP Cards: {}", err);
                }
            }

            for device in backend.devices.iter() {
                if device.id == id {
                    return Ok(Box::new(device.clone())
                              as Box<dyn backend::DeviceHandle + Send + Sync>);
                }
            }
        }

        Err(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 mut backend = self.inner.lock().await;

        // The first time through we look for the key without
        // scanning.  If we don't find it, then we rescan.
        for scan in [false, true] {
            if scan {
                log::trace!("Rescanning");
                if let Err(err) = backend.scan_internal(true).await {
                    log::debug!("Scanning OpenPGP Cards: {}", err);
                }
            }

            for device in backend.devices.iter() {
                let device = device.inner.lock().await;

                for (key_id, key) in device.keys.iter() {
                    if &key_id.to_string() == id {
                        return Ok(Box::new(key.clone())
                                  as Box<dyn backend::KeyHandle + Send + Sync>);
                    }
                }
            }
        }

        Err(Error::NotFound(id.into()).into())
    }

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

        Err(Error::ExternalImportRequired(Some(
            "To import a key into an OpenPGP Card, use something like: \
             oct admin --card ABCD:01234567 import key.priv".into())).into())
    }
}

#[async_trait::async_trait]
impl backend::DeviceHandle for Device {
    fn id(&self) -> String {
        log::trace!("Device::id");

        self.id.clone()
    }

    async fn available(&self) -> bool {
        log::trace!("Device::available");

        // XXX: Right now, we only support plugged-in cards.  Change
        // this when we support registering OpenPGP cards.
        true
    }

    async fn configured(&self) -> bool {
        log::trace!("Device::configured");

        // XXX: Right now, we only support plugged-in cards.  Change
        // this when we support registering OpenPGP cards.
        true
    }

    async fn registered(&self) -> bool {
        log::trace!("Device::registered");


        // XXX: Right now, we only support plugged-in cards.  Change
        // this when we support registering OpenPGP cards.
        true
    }

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

        // We manage passwords at the slot level.
        let device = self.inner.lock().await;
        for key in device.keys.values() {
            let mut key_internal = key.inner.lock().await;
            key_internal.password = None;
        }

        Ok(())
    }

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

        let device = self.inner.lock().await;
        let keys = device.keys.values()
            .map(|key| {
                Box::new(key.clone())
                    as Box<dyn backend::KeyHandle + Send + Sync>
            })
            .collect::<Vec<_>>();

        Box::new(keys.into_iter())
    }
}

#[async_trait::async_trait]
impl backend::KeyHandle for Key {
    fn id(&self) -> String {
        log::trace!("Key::id");

        self.fpr.to_string()
    }

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

        self.fpr.clone()
    }

    async fn device<'a>(&self)
        -> Box<dyn backend::DeviceHandle + Send + Sync + 'a>
    {
        log::trace!("Key::device");

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

        // Get the device that the key is on.
        let device = match key_internal.device.upgrade() {
            Some(device) => device,
            None => panic!("Device disappeared"),
        };

        Box::new(device)
    }

    /// Returns whether the key is available.
    ///
    /// A key managed by an OpenPGP card is considered to *not* be
    /// available if:
    ///
    /// - It is not present
    /// - The smartcard is not inserted.
    async fn available(&self) -> bool {
        log::trace!("Key::available");

        // XXX: Right now, we only support plugged-in cards.  Change
        // this when we support registering OpenPGP cards.
        true
    }

    async fn locked(&self) -> Protection {
        log::trace!("Key::locked");

        // We pessimistically consider a slot to be locked if we
        // haven't cached a password for the slot.  On some cards, we
        // could use [`Card::check_user_verified`], but on other cards
        // that increase the pin's error count, which is a bit of a
        // disaster.
        //
        // [`Card::check_user_verified`]: https://docs.rs/openpgp-card/latest/openpgp_card/struct.Card.html#method.check_user_verified

        // Due to the locking order requirements, in order to get the
        // device, we have to drop the lock on the key.  We copy what
        // we need from the key, and then get the device.
        let key_internal = self.inner.lock().await;
        let slot = key_internal.slot;

        let slot_str = || {
            match slot {
                KeyType::Signing => "signing".to_string(),
                KeyType::Decryption => "decryption".to_string(),
                KeyType::Authentication => "authentication".to_string(),
                s => format!("{:?}", s),
            }
        };

        if key_internal.password.is_some() {
            return Protection::Unlocked;
        }

        // Get the device that the key is on.
        let device = match key_internal.device.upgrade() {
            Some(device) => device,
            None => {
                return Protection::ExternalOther(
                    Some("Card disappeared".into()));
            }
        };
        // Now we drop the lock on key and take the lock on device.
        drop(key_internal);
        let mut device_internal = device.inner.lock().await;

        if device_internal.pinpad {
            return Protection::ExternalPassword(None);
        }

        let mut tx = match device_internal.card.transaction() {
            Ok(tx) => tx,
            Err(err) => {
                log::debug!("Starting transaction on openpgp card: {}", err);
                return Protection::ExternalOther(Some(
                    "Communication with card failed".into()));
            }
        };

        match tx.user_interaction_flag(slot) {
            Ok(uif) => {
                if let Some(uif) = uif {
                    if uif.touch_policy().touch_required() {
                        return Protection::ExternalTouch(Some(
                            format!("Touch the OpenPGP card to unlock the {} key",
                                    slot_str())));
                    }
                }
            }
            Err(err) => {
                log::debug!("Getting user interaction flags: {}", err);
            }
        }

        Protection::Password(Some(
            format!("Enter PIN to unlock the {} key", slot_str())))
    }

    async fn password_source(&self) -> PasswordSource {
        log::trace!("Key::password_source");

        // Due to the locking order requirements, in order to get the
        // device, we have to drop the lock on the key.
        let key_internal = self.inner.lock().await;

        // Get the device that the key is on.
        let device = match key_internal.device.upgrade() {
            Some(device) => device,
            None => {
                // We don't have a way to return an error.
                return PasswordSource::ExternalSideEffect;
            }
        };
        // Now we drop the lock on key and take the lock on device.
        drop(key_internal);
        let mut device_internal = device.inner.lock().await;
        // Retake the key lock.
        let key_internal = self.inner.lock().await;

        if device_internal.pinpad {
            // XXX: Is ExternalOnDemand more accurate?
            return PasswordSource::ExternalSideEffect;
        }

        let mut tx = match device_internal.card.transaction() {
            Ok(tx) => tx,
            Err(err) => {
                log::debug!("Starting transaction on openpgp card: {}", err);
                // We don't have a way to return an error.
                return PasswordSource::ExternalSideEffect;
            }
        };

        match tx.user_interaction_flag(key_internal.slot) {
            Ok(uif) => {
                if let Some(uif) = uif {
                    if uif.touch_policy().touch_required() {
                        return PasswordSource::InlineWithConfirmation;
                    }
                }
            }
            Err(err) => {
                log::debug!("Getting user interaction flags: {}", err);
            }
        }

        PasswordSource::Inline
    }

    async fn decryption_capable(&self) -> bool {
        log::trace!("Key::decryption_capable");

        let key_internal = self.inner.lock().await;
        match key_internal.slot {
            KeyType::Signing => false,
            KeyType::Decryption => true,
            KeyType::Authentication => false,
            _ => false,
        }
    }

    async fn signing_capable(&self) -> bool {
        log::trace!("Key::signing_capable");

        let key_internal = self.inner.lock().await;
        match key_internal.slot {
            KeyType::Signing => true,
            KeyType::Decryption => false,
            KeyType::Authentication => true,
            _ => false,
        }
    }

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

        // Due to the locking order requirements, in order to get the
        // device, we have to drop the lock on the key.
        let key_internal = self.inner.lock().await;

        if key_internal.password.is_some() {
            return Err(Error::AlreadyUnlocked(
                "Key is already unlocked".into()).into());
        }

        // Get the device that the key is on.
        let device = match key_internal.device.upgrade() {
            Some(device) => device,
            None => {
                return Err(anyhow::anyhow!("Card disappeared"));
            }
        };
        // Now we drop the lock on key and take the lock on device.
        drop(key_internal);
        let mut device_internal = device.inner.lock().await;
        // Retake the key lock.
        let mut key_internal = self.inner.lock().await;

        // Prompt the user for the password.

        let pinpad = device_internal.pinpad;

        let mut tx = match device_internal.card.transaction() {
            Ok(tx) => tx,
            Err(err) => {
                log::debug!("Starting transaction on openpgp card: {}", err);
                // XXX: Turn it into a real error.
                return Err(err.into());
            }
        };

        if let Some(password) = password {
            // The user supplied a password.

            if pinpad {
                // We can't use the password: we need to use the pin
                // pad.
                return Err(Error::NoInlinePassword(None).into());
            }

            let p = password.map(|p| String::from_utf8(p.to_vec()))?;

            if key_internal.slot == KeyType::Signing {
                tx.verify_user_signing_pin(p.into())?;
            } else {
                tx.verify_user_pin(p.into())?;
            }
            key_internal.password = Some(password.clone());
        } else {
            // The user didn't supply a password.  This means we are
            // supposed to unlock the slot using the pin pad.

            if ! pinpad {
                return Err(Error::NoExternalPassword(
                    Some("Cannot prompt user for password".into())).into());
            }

            fn cb() {}
            if key_internal.slot == KeyType::Signing {
                tx.verify_user_signing_pinpad(&cb)?;
            } else {
                tx.verify_user_pinpad(&cb)?;
            }
        }

        Ok(())
    }

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

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

        key_internal.password = None;

        // XXX: We should also lock the slot.  Currently the
        // openpgp-card library doesn't (appear to) support that.

        Ok(())
    }

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

        let key = self.inner.lock().await;
        key.public_key.clone()
    }

    // XXX: Use plaintext_len.
    async fn decrypt_ciphertext(&mut self,
                                ciphertext: &mpi::Ciphertext,
                                _plaintext_len: Option<usize>)
        -> Result<SessionKey>
    {
        log::trace!("Key::decrypt_ciphertext");

        // Due to the locking order requirements, in order to get the
        // device, we have to drop the lock on the key.  We copy what
        // we need from the key, and then get the device.
        let key_internal = self.inner.lock().await;

        // Copy what we need.
        let public_key = key_internal.public_key.clone();
        let password = key_internal.password.clone();

        // Get the device that the key is on.
        let device = match key_internal.device.upgrade() {
            Some(device) => device,
            None => {
                return Err(anyhow::anyhow!("Card disappeared"));
            }
        };
        // Now we drop the lock on key and take the lock on device.
        drop(key_internal);
        let mut device_internal = device.inner.lock().await;

        let mut tx = match device_internal.card.transaction() {
            Ok(tx) => tx,
            Err(err) => {
                log::debug!("Starting transaction on openpgp card: {}", err);
                // XXX: Turn it into a real error.
                return Err(err.into());
            }
        };

        // The rest of this function is derived from
        // openpgp-card-sequoia's [CardDecryptor::decrypt].
        //
        // [CardDecryptor::decrypt]: https://gitlab.com/openpgp-card/openpgp-card/-/blob/fa3e2e5c/openpgp-card-sequoia/src/decryptor.rs
        //
        // The file has the following copyright notice:
        //
        // SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer <heiko@schaefer.name>
        // SPDX-License-Identifier: MIT OR Apache-2.0

        let unlock = || -> Result<()> {
            if let Some(password) = password {
                let password = password.map(|p| String::from_utf8(p.to_vec()))?;
                tx.verify_user_pin(password.into())?;
            }

            Ok(())
        };

        // Delegate a decryption operation to the OpenPGP card.
        //
        // This fn prepares the data structures that openpgp-card needs to
        // perform the decryption operation.
        //
        // (7.2.11 PSO: DECIPHER)
        match (ciphertext, public_key.mpis()) {
            (mpi::Ciphertext::RSA { c: ct }, mpi::PublicKey::RSA { .. }) => {
                let dm = Cryptogram::RSA(ct.value());

                unlock()?;
                let dec = tx.card().decipher(dm)?;

                let sk = SessionKey::from(&dec[..]);
                Ok(sk)
            }
            (mpi::Ciphertext::ECDH { ref e, .. }, mpi::PublicKey::ECDH { ref curve, .. }) => {
                let dm = if curve == &Curve::Cv25519 {
                    assert_eq!(
                        e.value()[0],
                        0x40,
                        "Unexpected shape of decrypted Cv25519 data"
                    );

                    // Ephemeral key without header byte 0x40
                    Cryptogram::ECDH(&e.value()[1..])
                } else {
                    // NIST curves: ephemeral key with header byte
                    Cryptogram::ECDH(e.value())
                };

                // Decryption operation on the card
                unlock()?;
                let mut dec = tx.card().decipher(dm)?;

                // Specifically handle return value format like Gnuk's
                // (Gnuk returns a leading '0x04' byte and
                // an additional 32 trailing bytes)
                if curve == &Curve::NistP256 && dec.len() == 65 {
                    assert_eq!(dec[0], 0x04, "Unexpected shape of decrypted NistP256 data");

                    // see Gnuk src/call-ec.c:82
                    dec = dec[1..33].to_vec();
                }

                #[allow(non_snake_case)]
                let S: Protected = dec.into();

                Ok(ecdh::decrypt_unwrap(&public_key, &S, ciphertext, None /* XXX */)?)
            }

            (ciphertext, public) => Err(anyhow::anyhow!(
                "Unsupported combination of ciphertext {:?} \
                 and public key {:?} ",
                ciphertext,
                public
            )),
        }
    }

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

        // Due to the locking order requirements, in order to get the
        // device, we have to drop the lock on the key.  We copy what
        // we need from the key, and then get the device.
        let key_internal = self.inner.lock().await;

        // Copy what we need.
        let public_key = key_internal.public_key.clone();
        let password = key_internal.password.clone();
        let slot = key_internal.slot;

        // Get the device that the key is on.
        let device = match key_internal.device.upgrade() {
            Some(device) => device,
            None => {
                return Err(anyhow::anyhow!("Card disappeared"));
            }
        };
        // Now we drop the lock on key and take the lock on device.
        drop(key_internal);
        let mut device_internal = device.inner.lock().await;

        let mut tx = match device_internal.card.transaction() {
            Ok(tx) => tx,
            Err(err) => {
                log::debug!("Starting transaction on openpgp card: {}", err);
                // XXX: Turn it into a real error.
                return Err(err.into());
            }
        };

        let unlock = || -> Result<()> {
            if let Some(password) = password {
                log::trace!("Unlocking key with password");
                let password = password.map(|p| String::from_utf8(p.to_vec()))?;
                tx.verify_user_signing_pin(password.into())?;
                log::trace!("Successfully unlocked slot");
            } else {
                log::debug!("Not unlocking slot; no password cached");
            }

            Ok(())
        };

        // The rest of this function is derived from
        // openpgp-card-sequoia's [CardSigner::sign].
        //
        // [CardDecryptor::decrypt]: https://gitlab.com/openpgp-card/openpgp-card/-/blob/fa3e2e5c/openpgp-card-sequoia/src/signer.rs
        //
        // The file has the following copyright notice:
        //
        // SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer <heiko@schaefer.name>
        // SPDX-License-Identifier: MIT OR Apache-2.0

        let sig_fn = if slot == KeyType::Signing {
            Transaction::signature_for_hash
        } else {
            Transaction::authenticate_for_hash
        };
        // Delegate a signing (or auth) operation to the OpenPGP card.
        //
        // This fn prepares the data structures that openpgp-card needs to
        // perform the signing operation.
        //
        // (7.2.10 PSO: COMPUTE DIGITAL SIGNATURE)
        // or (7.2.13 INTERNAL AUTHENTICATE)
        use openpgp_card::ocard::crypto::Hash;
        let sig = match (public_key.pk_algo(), public_key.mpis()) {
            #[allow(deprecated)]
            (PublicKeyAlgorithm::RSASign, mpi::PublicKey::RSA { .. })
            | (PublicKeyAlgorithm::RSAEncryptSign, mpi::PublicKey::RSA { .. }) => {
                let hash = match hash_algo {
                    sequoia_openpgp::types::HashAlgorithm::SHA256 => Hash::SHA256(
                        digest
                            .try_into()
                            .map_err(|_| anyhow::anyhow!("invalid slice length"))?,
                    ),
                    sequoia_openpgp::types::HashAlgorithm::SHA384 => Hash::SHA384(
                        digest
                            .try_into()
                            .map_err(|_| anyhow::anyhow!("invalid slice length"))?,
                    ),
                    sequoia_openpgp::types::HashAlgorithm::SHA512 => Hash::SHA512(
                        digest
                            .try_into()
                            .map_err(|_| anyhow::anyhow!("invalid slice length"))?,
                    ),
                    _ => {
                        return Err(anyhow::anyhow!(
                            "Unsupported hash algorithm for RSA {:?}",
                            hash_algo
                        ));
                    }
                };
                unlock()?;
                let sig = sig_fn(tx.card(), hash)?;
                let mpi = mpi::MPI::new(&sig[..]);
                mpi::Signature::RSA { s: mpi }
            }
            (PublicKeyAlgorithm::EdDSA, mpi::PublicKey::EdDSA { .. }) => {
                let hash = Hash::EdDSA(digest);
                unlock()?;
                let sig = sig_fn(tx.card(), hash)?;
                let r = mpi::MPI::new(&sig[..32]);
                let s = mpi::MPI::new(&sig[32..]);
                mpi::Signature::EdDSA { r, s }
            }
            (PublicKeyAlgorithm::ECDSA, mpi::PublicKey::ECDSA { curve, .. }) => {
                let hash = match curve {
                    Curve::NistP256 => Hash::ECDSA(&digest[..32]),
                    Curve::NistP384 => Hash::ECDSA(&digest[..48]),
                    Curve::NistP521 => Hash::ECDSA(&digest[..64]),
                    _ => Hash::ECDSA(digest),
                };
                unlock()?;
                let sig = sig_fn(tx.card(), hash)?;
                let len_2 = sig.len() / 2;
                let r = mpi::MPI::new(&sig[..len_2]);
                let s = mpi::MPI::new(&sig[len_2..]);
                mpi::Signature::ECDSA { r, s }
            }
            // FIXME: implement NIST etc
            (pk_algo, _) => return Err(anyhow::anyhow!(
                "Unsupported combination of algorithm {:?} and pubkey {:?}",
                pk_algo,
                public_key
            )),
        };

        log::trace!("Returned a {} signature", public_key.pk_algo());
        Ok((public_key.pk_algo(), sig))
    }

    async fn export(&mut self)
        -> Result<openpgp::packet::Key<
            openpgp::packet::key::SecretParts,
            openpgp::packet::key::UnspecifiedRole>>
    {
        Err(Error::OperationNotSupported(
            "Keys cannot be exported from OpenPGP cards.".into()).into())
    }

    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"
                    });

        Err(Error::OperationNotSupported(
            "Use an external tool to manage OpenPGP cards.".into()).into())
    }

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

        Err(Error::OperationNotSupported(
            "Use an external tool to manage OpenPGP cards.".into()).into())
    }
}

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

    use openpgp::KeyHandle;
    use openpgp::cert::amalgamation::key::ValidKeyAmalgamation;
    use openpgp::parse::Parse;
    use openpgp::policy::StandardPolicy;
    use openpgp::serialize::Serialize;

    const P: &StandardPolicy = &StandardPolicy::new();

    use backend::test_framework;

    use backend::Backend as _;

    /// This is the ID and PINs for the opcard-rs virtual smart card.
    const TEST_CARD_ID: &str = "0000:00000000";
    const TEST_CARD_ADMIN_PIN: &str = "12345678";
    const TEST_CARD_USER_PIN: &str = "123456";

    fn get_test_card() -> Option<openpgp_card::Card<openpgp_card::state::Open>>
    {
        let card_backends = match card_backend_pcsc::PcscBackend::cards(None) {
            Ok(card_backends) => card_backends.collect(),
            Err(err) => {
                log::debug!("Listing openpgp cards using PC/SC: {}", err);
                Vec::new()
            }
        };

        let mut test_card = None;
        for card_backend in card_backends.into_iter() {
            let card_backend = match card_backend {
                Ok(card_backend) => card_backend,
                Err(err) => {
                    log::debug!("Opening openpgp card backend: {}", err);
                    continue;
                }
            };

            let mut card = match openpgp_card::Card::new(card_backend) {
                Ok(card) => card,
                Err(err) => {
                    log::debug!("Opening openpgp card: {}", err);
                    continue;
                }
            };

            let tx = match card.transaction() {
                Ok(tx) => tx,
                Err(err) => {
                    log::debug!("Starting transaction on openpgp card: {}", err);
                    continue;
                }
            };

            let id = match tx.application_identifier() {
                Ok(id) => id,
                Err(err) => {
                    log::debug!("Getting application id from openpgp card: {}", err);
                    continue;
                }
            };
            let id = id.ident();

            if id == TEST_CARD_ID {
                drop(tx);
                test_card = Some(card);
                break;
            } else {
                eprintln!("Found card {}", id);
            }
        }

        test_card
    }

    fn preinit() -> bool {
        // Don't run the tests if the test card is not available.
        get_test_card().is_some()
    }

    async fn init_backend() -> Backend {
        let backend = Backend::init_ephemeral().await.expect("can init backend");

        let mut card = if let Some(card) = get_test_card() {
            card
        } else {
            panic!("Test card ({}) not available", TEST_CARD_ID);
        };

        // Reset the card so that it is empty.
        let mut tx = card.transaction().expect("can start a transaction");
        tx.factory_reset().expect("can factory reset");

        backend
    }

    async fn import_cert(backend: &mut Backend, cert: &Cert) {
        let vc = cert.with_policy(P, None).expect("valid cert");

        eprintln!("Importing cert {}'s subkeys", cert.fingerprint());
        for ka in vc.keys().subkeys() {
            eprintln!("  - {}, secret: {}, key flags: {:?}",
                      ka.key().fingerprint(), ka.key().has_secret(), ka.key_flags());
        }

        let signing_key
            = vc.keys().subkeys().for_signing().secret().next();
        let auth_key
            = vc.keys().subkeys().for_authentication().secret().next();
        let encryption_key
            = vc.keys().subkeys().for_transport_encryption().secret().next();

        assert!(signing_key.is_some()
                || auth_key.is_some()
                || encryption_key.is_some(),
                "Expect at least one subkey with secret key material");

        let mut card = if let Some(card) = get_test_card() {
            card
        } else {
            panic!("Test card ({}) not available", TEST_CARD_ID);
        };

        let mut tx = card.transaction().expect("can start a transaction");

        tx.verify_admin_pin(TEST_CARD_ADMIN_PIN.to_string().into())
            .expect("can use admin pin");

        let mut admin = tx.to_admin_card(None)
            .expect("can access admin functionality");

        // Import the keys.
        let mut import_key = |key: Option<ValidKeyAmalgamation<_, _, ()>>, slot: KeyType| {
            if let Some(key) = key {
                eprintln!("Importing key {} to {:?} slot",
                          key.key().fingerprint(), slot);

                assert!(key.key().has_unencrypted_secret());
                let key = Box::new(privkey::SequoiaKey::new(key.into(), None));
                //let key = openpgp_card_sequoia::util::vka_as_uploadable_key(
                //    key.into(), None);
                admin.import_key(key, slot)
                    .expect("can import key");
            } else {
                eprintln!("No {:?} key to import", slot);
            }
        };

        import_key(signing_key, KeyType::Signing);
        import_key(auth_key, KeyType::Authentication);
        import_key(encryption_key, KeyType::Decryption);

        // And insert the certificate into our local certd so that we
        // can find the OpenPGP keys.
        backend.inner.lock().await.certd.certd().insert(
            &cert.fingerprint().to_string(),
            (), false,
            |(), disk| {
                let cert_;
                let cert = if let Some(disk) = disk {
                    // Merge.
                    let disk = Cert::from_bytes(disk).expect("valid cert");
                    cert_ = cert.clone().merge_public(disk).expect("can merge");
                    &cert_
                } else {
                    // New.
                    cert
                };

                let mut bytes = Vec::new();
                cert.serialize(&mut bytes).expect("can serialize to a vec");
                Ok(bytes.into())
            })
            .expect("inserted");
    }

    sequoia_keystore_backend::generate_tests!(
        preinit,
        true, // Serialize tests.
        Backend, init_backend,
        import_cert,
        false, // Can import encrypted secret key material.
        Some(1), // Supported key sets.
        Some(TEST_CARD_USER_PIN), // Default password.
        false, // Can export.
        false, // Can change password.
        false // Can delete secret key material.
    );
}
