//! Embedded OpenPGP operations for existing exported key material.

use std::{
    collections::{BTreeMap, BTreeSet},
    error::Error,
    fmt, fs,
    io::{self, Cursor, Read},
    path::{Path, PathBuf},
};

use cap_std::{ambient_authority, fs::Dir};
use pgp::{
    composed::{
        ArmorOptions, DecryptionOptions, Deserializable, DetachedSignature, Esk, Message,
        MessageBuilder, PublicOrSecret, SignedPublicKey, SignedPublicSubKey, SignedSecretKey,
        SubpacketConfig, TheRing,
    },
    crypto::{hash::HashAlgorithm, sym::SymmetricKeyAlgorithm},
    packet::{SignatureType, Subpacket, SubpacketData},
    ser::Serialize as _,
    types::{KeyDetails as _, Password, SigningKey, Timestamp, VerifyingKey},
};
use rand::rngs::OsRng;

use crate::repository::{EncryptedEntry, SecretBytes};

const MAX_KEY_FILE_BYTES: u64 = 16 * 1024 * 1024;
const MAX_IMPORTED_KEYS: usize = 1024;

#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct KeyFingerprint(String);

impl KeyFingerprint {
    pub fn as_str(&self) -> &str {
        &self.0
    }
}

impl fmt::Display for KeyFingerprint {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter.write_str(&self.0)
    }
}

#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct KeyHandle(KeyFingerprint);

impl KeyHandle {
    pub fn fingerprint(&self) -> &KeyFingerprint {
        &self.0
    }
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct KeyInfo {
    fingerprint: KeyFingerprint,
    key_id: String,
    user_ids: Vec<String>,
    has_secret: bool,
    requires_passphrase: bool,
    can_encrypt: bool,
    can_sign: bool,
}

impl KeyInfo {
    pub fn fingerprint(&self) -> &KeyFingerprint {
        &self.fingerprint
    }

    pub fn key_id(&self) -> &str {
        &self.key_id
    }

    pub fn user_ids(&self) -> &[String] {
        &self.user_ids
    }

    pub fn has_secret(&self) -> bool {
        self.has_secret
    }

    pub fn requires_passphrase(&self) -> bool {
        self.requires_passphrase
    }

    pub fn can_encrypt(&self) -> bool {
        self.can_encrypt
    }

    pub fn can_sign(&self) -> bool {
        self.can_sign
    }
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SecretProviderError {
    Missing,
    Denied,
    Unavailable,
    Cancelled,
    PersistenceFailed,
}

/// Supplies an unlock secret without exposing it to configuration or the key store.
pub trait SecretProvider {
    fn secret_for(&mut self, key: &KeyInfo) -> Result<SecretBytes, SecretProviderError>;

    /// Confirm that the supplied secret successfully unlocked the key.
    ///
    /// Providers that provision secrets interactively can defer persistence
    /// until this callback proves that the candidate was correct.
    fn secret_accepted(&mut self, _key: &KeyInfo) -> Result<(), SecretProviderError> {
        Ok(())
    }

    /// Report that the supplied secret did not unlock the key.
    fn secret_rejected(&mut self, _key: &KeyInfo) {}
}

#[derive(Clone, Eq, PartialEq)]
pub struct DetachedSignatureBytes(Vec<u8>);

impl DetachedSignatureBytes {
    pub fn new(bytes: Vec<u8>) -> Self {
        Self(bytes)
    }

    pub fn as_bytes(&self) -> &[u8] {
        &self.0
    }

    pub fn into_bytes(self) -> Vec<u8> {
        self.0
    }
}

impl fmt::Debug for DetachedSignatureBytes {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter
            .debug_struct("DetachedSignatureBytes")
            .field("length", &self.0.len())
            .finish()
    }
}

struct KeyMaterial {
    public: SignedPublicKey,
    secret: Option<SignedSecretKey>,
}

/// Imported and verified OpenPGP certificates, indexed by primary fingerprint.
#[derive(Default)]
pub struct KeyStore {
    keys: BTreeMap<KeyFingerprint, KeyMaterial>,
}

impl fmt::Debug for KeyStore {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter
            .debug_struct("KeyStore")
            .field("key_count", &self.keys.len())
            .finish()
    }
}

impl KeyStore {
    pub fn new() -> Self {
        Self::default()
    }

    /// Load a regular exported-key file or a directory tree made only of exported-key files.
    pub fn load(path: impl AsRef<Path>) -> Result<Self, CryptoError> {
        let path = path.as_ref();
        let metadata = fs::symlink_metadata(path)
            .map_err(|error| crypto_io("inspect key material", path, error))?;
        if metadata.file_type().is_symlink() || (!metadata.is_file() && !metadata.is_dir()) {
            return Err(CryptoError::UnsupportedKeyFileType {
                path: path.to_owned(),
            });
        }
        let mut store = Self::new();
        if metadata.is_file() {
            let bytes = read_ambient_key_file(path, metadata.len())?;
            store.import(&bytes)?;
        } else {
            let canonical = fs::canonicalize(path)
                .map_err(|error| crypto_io("canonicalize key directory", path, error))?;
            let directory = Dir::open_ambient_dir(&canonical, ambient_authority())
                .map_err(|error| crypto_io("open key directory", &canonical, error))?;
            import_key_directory(&mut store, &directory, Path::new(""))?;
        }
        if store.keys.is_empty() {
            return Err(CryptoError::NoKeyMaterial);
        }
        Ok(store)
    }

    /// Transactionally import one armored or binary stream containing public and/or secret keys.
    pub fn import(&mut self, bytes: &[u8]) -> Result<Vec<KeyInfo>, CryptoError> {
        if bytes.len() as u64 > MAX_KEY_FILE_BYTES {
            return Err(CryptoError::KeyMaterialTooLarge);
        }
        let (parsed, _) = PublicOrSecret::from_reader_many(Cursor::new(bytes))
            .map_err(|_| CryptoError::CorruptKeyMaterial)?;
        let mut pending = BTreeMap::<KeyFingerprint, KeyMaterial>::new();
        for parsed_key in parsed {
            let parsed_key = parsed_key.map_err(|_| CryptoError::CorruptKeyMaterial)?;
            parsed_key
                .verify_bindings()
                .map_err(|_| CryptoError::InvalidKeyBindings)?;
            let (public, secret) = match parsed_key {
                PublicOrSecret::Public(public) => (public, None),
                PublicOrSecret::Secret(secret) => (secret.to_public_key(), Some(secret)),
            };
            let fingerprint = fingerprint_of(&public);
            if let Some(existing) = pending.get_mut(&fingerprint) {
                if existing.public.primary_key != public.primary_key {
                    return Err(CryptoError::DuplicateFingerprint { fingerprint });
                }
                if existing.secret.is_none() {
                    existing.secret = secret;
                }
            } else {
                pending.insert(fingerprint, KeyMaterial { public, secret });
            }
            if self.keys.len() + pending.len() > MAX_IMPORTED_KEYS {
                return Err(CryptoError::TooManyKeys);
            }
        }
        if pending.is_empty() {
            return Err(CryptoError::NoKeyMaterial);
        }
        for (fingerprint, material) in &pending {
            if self
                .keys
                .get(fingerprint)
                .is_some_and(|existing| existing.public.primary_key != material.public.primary_key)
            {
                return Err(CryptoError::DuplicateFingerprint {
                    fingerprint: fingerprint.clone(),
                });
            }
        }
        for (fingerprint, material) in pending {
            if let Some(existing) = self.keys.get_mut(&fingerprint) {
                if existing.secret.is_none() {
                    existing.secret = material.secret;
                }
            } else {
                self.keys.insert(fingerprint, material);
            }
        }
        Ok(self.infos().collect())
    }

    pub fn len(&self) -> usize {
        self.keys.len()
    }

    pub fn is_empty(&self) -> bool {
        self.keys.is_empty()
    }

    pub fn infos(&self) -> impl Iterator<Item = KeyInfo> + '_ {
        self.keys.values().map(key_info)
    }

    /// Serialize one verified certificate as transferable ASCII armor.
    pub fn export_armored(
        &self,
        identity: &str,
        include_secret: bool,
        passphrase: Option<&SecretBytes>,
    ) -> Result<SecretBytes, CryptoError> {
        let handle = self.resolve(identity)?;
        let material = self.material(&handle)?;
        let bytes = if include_secret {
            let secret = material
                .secret
                .as_ref()
                .ok_or(CryptoError::MissingSecretKey)?;
            let password = Password::from(passphrase.map_or(&[][..], |value| value.expose()));
            if !secret_unlocks(secret, &password) {
                return Err(CryptoError::InvalidKeyPassphrase);
            }
            secret.to_armored_bytes(ArmorOptions::default())
        } else {
            material.public.to_armored_bytes(ArmorOptions::default())
        }
        .map_err(|_| CryptoError::CorruptKeyMaterial)?;
        Ok(SecretBytes::new(bytes))
    }

    /// Prove that a supplied passphrase unlocks every protected packet in a secret key.
    pub fn validate_secret_passphrase(
        &self,
        identity: &str,
        passphrase: Option<&SecretBytes>,
    ) -> Result<(), CryptoError> {
        let handle = self.resolve(identity)?;
        let secret = self
            .material(&handle)?
            .secret
            .as_ref()
            .ok_or(CryptoError::MissingSecretKey)?;
        let password = Password::from(passphrase.map_or(&[][..], |value| value.expose()));
        if secret_unlocks(secret, &password) {
            Ok(())
        } else {
            Err(CryptoError::InvalidKeyPassphrase)
        }
    }

    /// Resolve a full primary/subkey fingerprint, 8/16-digit key ID, or exact UTF-8 user ID.
    pub fn resolve(&self, identity: &str) -> Result<KeyHandle, CryptoError> {
        let hex_identity = normalize_hex_identity(identity);
        let mut matches = BTreeSet::new();
        if let Some(hex_identity) = hex_identity {
            for (fingerprint, key) in &self.keys {
                if key_matches_hex(key, &hex_identity) {
                    matches.insert(fingerprint.clone());
                }
            }
        }
        if matches.is_empty() {
            for (fingerprint, key) in &self.keys {
                if key
                    .public
                    .details
                    .users
                    .iter()
                    .any(|user| user.id.as_str() == Some(identity))
                {
                    matches.insert(fingerprint.clone());
                }
            }
        }
        match matches.len() {
            0 => Err(CryptoError::MissingIdentity {
                identity: identity.to_owned(),
            }),
            1 => Ok(KeyHandle(matches.pop_first().expect("one match"))),
            _ => Err(CryptoError::AmbiguousIdentity {
                identity: identity.to_owned(),
            }),
        }
    }

    /// Apply upstream `.gpg-id` comment and whitespace rules, then resolve every recipient.
    pub fn resolve_recipients(&self, contents: &[u8]) -> Result<Vec<KeyHandle>, CryptoError> {
        let contents =
            std::str::from_utf8(contents).map_err(|_| CryptoError::InvalidRecipientFile)?;
        let mut recipients = Vec::new();
        let mut seen = BTreeSet::new();
        for line in contents.lines() {
            let identity = line.split('#').next().unwrap_or_default().trim();
            if identity.is_empty() {
                continue;
            }
            let recipient = self.resolve(identity)?;
            if seen.insert(recipient.clone()) {
                recipients.push(recipient);
            }
        }
        if recipients.is_empty() {
            return Err(CryptoError::MissingRecipients);
        }
        Ok(recipients)
    }

    /// Encrypt a plaintext for every resolved recipient using pass-compatible uncompressed
    /// SEIPD v1 with AES-256.
    pub fn encrypt(
        &self,
        plaintext: SecretBytes,
        recipients: &[KeyHandle],
    ) -> Result<EncryptedEntry, CryptoError> {
        if recipients.is_empty() {
            return Err(CryptoError::MissingRecipients);
        }
        let mut unique = BTreeSet::new();
        let mut targets = Vec::new();
        for recipient in recipients {
            if !unique.insert(recipient) {
                continue;
            }
            let material = self.material(recipient)?;
            let target = encryption_target(&material.public).ok_or_else(|| {
                CryptoError::MissingEncryptionKey {
                    fingerprint: recipient.0.clone(),
                }
            })?;
            targets.push(target);
        }
        let reader = SecretReader::new(plaintext);
        let mut rng = OsRng;
        let mut message = MessageBuilder::from_reader("", reader)
            .seipd_v1(&mut rng, SymmetricKeyAlgorithm::AES256);
        for target in targets {
            match target {
                EncryptionTarget::Primary(key) => message
                    .encrypt_to_key(&mut rng, key)
                    .map_err(|_| CryptoError::EncryptionFailed)?,
                EncryptionTarget::Subkey(key) => message
                    .encrypt_to_key(&mut rng, key)
                    .map_err(|_| CryptoError::EncryptionFailed)?,
            };
        }
        message
            .to_vec(rng)
            .map(EncryptedEntry::new)
            .map_err(|_| CryptoError::EncryptionFailed)
    }

    /// Report whether every public-key session packet names exactly the requested primary
    /// certificates. Unknown, anonymous, symmetric, missing, or extra recipients do not match.
    pub fn is_encrypted_for(
        &self,
        ciphertext: &EncryptedEntry,
        recipients: &[KeyHandle],
    ) -> Result<bool, CryptoError> {
        let message = Message::from_bytes(Cursor::new(ciphertext.as_bytes()))
            .map_err(|_| CryptoError::CorruptMessage)?;
        let Message::Encrypted { esk, .. } = message else {
            return Err(CryptoError::CorruptMessage);
        };
        let expected = recipients
            .iter()
            .map(|recipient| {
                self.material(recipient)?;
                Ok(recipient.0.clone())
            })
            .collect::<Result<BTreeSet<_>, CryptoError>>()?;
        if expected.is_empty() {
            return Ok(false);
        }
        let mut actual = BTreeSet::new();
        for packet in esk {
            let Esk::PublicKeyEncryptedSessionKey(packet) = packet else {
                return Ok(false);
            };
            let matches = self
                .keys
                .iter()
                .filter(|(_, material)| packet_matches_public(&packet, &material.public))
                .map(|(fingerprint, _)| fingerprint)
                .collect::<Vec<_>>();
            if matches.len() != 1 {
                return Ok(false);
            }
            actual.insert((*matches[0]).clone());
        }
        Ok(actual == expected)
    }

    /// Return the secret keys named by a pass entry's PKESK packets.
    pub fn decrypting_keys(
        &self,
        ciphertext: &EncryptedEntry,
    ) -> Result<Vec<KeyInfo>, CryptoError> {
        let message = Message::from_bytes(Cursor::new(ciphertext.as_bytes()))
            .map_err(|_| CryptoError::CorruptMessage)?;
        if !message.is_encrypted() {
            return Err(CryptoError::CorruptMessage);
        }
        let keys = self
            .decrypting_material(&message)
            .into_iter()
            .map(|(_, material)| key_info(material))
            .collect::<Vec<_>>();
        if keys.is_empty() {
            return Err(CryptoError::MissingSecretKey);
        }
        Ok(keys)
    }

    /// Decrypt a pass entry with only the secret keys named by its PKESK packets.
    pub fn decrypt(
        &self,
        ciphertext: &EncryptedEntry,
        provider: &mut impl SecretProvider,
    ) -> Result<SecretBytes, CryptoError> {
        let message = Message::from_bytes(Cursor::new(ciphertext.as_bytes()))
            .map_err(|_| CryptoError::CorruptMessage)?;
        if !message.is_encrypted() {
            return Err(CryptoError::CorruptMessage);
        }
        let candidates = self.decrypting_material(&message);
        if candidates.is_empty() {
            return Err(CryptoError::MissingSecretKey);
        }

        let mut provider_failure = None;
        let mut attempted = false;
        for (fingerprint, material) in &candidates {
            let secret = material.secret.as_ref().expect("filtered secret key");
            let key = secret_requires_password(secret).then(|| key_info(material));
            let supplied = if let Some(info) = key.as_ref() {
                match provider.secret_for(info) {
                    Ok(passphrase) => passphrase,
                    Err(reason @ SecretProviderError::Cancelled) => {
                        return Err(CryptoError::SecretProvider {
                            fingerprint: (*fingerprint).clone(),
                            reason,
                        });
                    }
                    Err(reason) => {
                        provider_failure.get_or_insert_with(|| CryptoError::SecretProvider {
                            fingerprint: (*fingerprint).clone(),
                            reason,
                        });
                        continue;
                    }
                }
            } else {
                SecretBytes::new(Vec::new())
            };
            attempted = true;
            let password = Password::from(supplied.expose());
            let message = Message::from_bytes(Cursor::new(ciphertext.as_bytes()))
                .map_err(|_| CryptoError::CorruptMessage)?;
            let ring = TheRing {
                secret_keys: vec![secret],
                key_passwords: vec![&password],
                decrypt_options: DecryptionOptions::new().enable_gnupg_aead(),
                ..Default::default()
            };
            if let Ok((mut decrypted, _)) = message.decrypt_the_ring(ring, true)
                && let Ok(plaintext) = decrypted.as_data_vec()
            {
                if let Some(info) = key.as_ref() {
                    provider.secret_accepted(info).map_err(|reason| {
                        CryptoError::SecretProvider {
                            fingerprint: (*fingerprint).clone(),
                            reason,
                        }
                    })?;
                }
                return Ok(SecretBytes::new(plaintext));
            }
            if let Some(info) = key.as_ref() {
                provider.secret_rejected(info);
            }
        }
        if attempted {
            Err(CryptoError::DecryptionFailed)
        } else {
            Err(provider_failure.expect("every candidate had a provider failure"))
        }
    }

    fn decrypting_material<'a>(
        &'a self,
        message: &Message<'_>,
    ) -> Vec<(&'a KeyFingerprint, &'a KeyMaterial)> {
        self.keys
            .iter()
            .filter(|(_, material)| {
                material
                    .secret
                    .as_ref()
                    .is_some_and(|secret| message_matches_secret(message, secret))
            })
            .collect()
    }

    pub fn sign(
        &self,
        data: &[u8],
        signer: &KeyHandle,
        provider: &mut impl SecretProvider,
    ) -> Result<DetachedSignatureBytes, CryptoError> {
        let material = self.material(signer)?;
        let secret = material
            .secret
            .as_ref()
            .ok_or_else(|| CryptoError::MissingSigningKey {
                fingerprint: signer.0.clone(),
            })?;
        let target = signing_target(secret).ok_or_else(|| CryptoError::MissingSigningKey {
            fingerprint: signer.0.clone(),
        })?;
        let key = signing_target_requires_password(target).then(|| key_info(material));
        let supplied = if let Some(info) = key.as_ref() {
            provider
                .secret_for(info)
                .map_err(|reason| CryptoError::SecretProvider {
                    fingerprint: signer.0.clone(),
                    reason,
                })?
        } else {
            SecretBytes::new(Vec::new())
        };
        let password = Password::from(supplied.expose());
        let signature = match match target {
            SigningTarget::Primary(key) => sign_data(key, &password, data),
            SigningTarget::Subkey(key) => sign_data(key, &password, data),
        } {
            Ok(signature) => signature,
            Err(error) => {
                if let Some(info) = key.as_ref() {
                    provider.secret_rejected(info);
                }
                return Err(error);
            }
        };
        if let Some(info) = key.as_ref() {
            provider
                .secret_accepted(info)
                .map_err(|reason| CryptoError::SecretProvider {
                    fingerprint: signer.0.clone(),
                    reason,
                })?;
        }
        let mut bytes = Vec::new();
        signature
            .to_writer(&mut bytes)
            .map_err(|_| CryptoError::SigningFailed)?;
        Ok(DetachedSignatureBytes(bytes))
    }

    /// Encode a detached signature as the ASCII armor required by Git's
    /// `gpgsig` commit header.
    pub fn armor_signature(
        &self,
        signature: &DetachedSignatureBytes,
    ) -> Result<Vec<u8>, CryptoError> {
        DetachedSignature::from_bytes(Cursor::new(signature.as_bytes()))
            .map_err(|_| CryptoError::SigningFailed)?
            .to_armored_bytes(ArmorOptions::default())
            .map_err(|_| CryptoError::SigningFailed)
    }

    /// Verify a detached `.gpg-id.sig` against an explicit set of allowed primary identities.
    pub fn verify(
        &self,
        data: &[u8],
        signature: &DetachedSignatureBytes,
        allowed_signers: &[KeyHandle],
    ) -> Result<KeyHandle, CryptoError> {
        if allowed_signers.is_empty() {
            return Err(CryptoError::InvalidSignature);
        }
        let signature = DetachedSignature::from_bytes(Cursor::new(signature.as_bytes()))
            .map_err(|_| CryptoError::CorruptSignature)?;
        for signer in allowed_signers {
            let material = self.material(signer)?;
            if verify_with_public(&signature, &material.public, data) {
                return Ok(signer.clone());
            }
        }
        Err(CryptoError::InvalidSignature)
    }

    fn material(&self, handle: &KeyHandle) -> Result<&KeyMaterial, CryptoError> {
        self.keys
            .get(&handle.0)
            .ok_or_else(|| CryptoError::MissingIdentity {
                identity: handle.0.0.clone(),
            })
    }
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub enum CryptoError {
    UnsupportedKeyFileType {
        path: PathBuf,
    },
    KeyMaterialTooLarge,
    TooManyKeys,
    NoKeyMaterial,
    CorruptKeyMaterial,
    InvalidKeyBindings,
    DuplicateFingerprint {
        fingerprint: KeyFingerprint,
    },
    MissingIdentity {
        identity: String,
    },
    AmbiguousIdentity {
        identity: String,
    },
    InvalidRecipientFile,
    MissingRecipients,
    MissingEncryptionKey {
        fingerprint: KeyFingerprint,
    },
    CorruptMessage,
    MissingSecretKey,
    SecretProvider {
        fingerprint: KeyFingerprint,
        reason: SecretProviderError,
    },
    DecryptionFailed,
    InvalidKeyPassphrase,
    MissingSigningKey {
        fingerprint: KeyFingerprint,
    },
    EncryptionFailed,
    SigningFailed,
    CorruptSignature,
    InvalidSignature,
    Io {
        operation: &'static str,
        path: PathBuf,
        source: io::ErrorKind,
    },
}

impl fmt::Display for CryptoError {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::UnsupportedKeyFileType { path } => {
                write!(
                    formatter,
                    "unsupported exported-key file type: {}",
                    path.display()
                )
            }
            Self::KeyMaterialTooLarge => formatter.write_str("exported key material is too large"),
            Self::TooManyKeys => formatter.write_str("too many exported keys"),
            Self::NoKeyMaterial => formatter.write_str("no OpenPGP key material was found"),
            Self::CorruptKeyMaterial => formatter.write_str("OpenPGP key material is malformed"),
            Self::InvalidKeyBindings => formatter.write_str("OpenPGP key bindings are invalid"),
            Self::DuplicateFingerprint { fingerprint } => {
                write!(
                    formatter,
                    "conflicting OpenPGP key fingerprint: {fingerprint}"
                )
            }
            Self::MissingIdentity { identity } => {
                write!(formatter, "OpenPGP identity was not found: {identity}")
            }
            Self::AmbiguousIdentity { identity } => {
                write!(formatter, "OpenPGP identity is ambiguous: {identity}")
            }
            Self::InvalidRecipientFile => formatter.write_str(".gpg-id is not valid UTF-8"),
            Self::MissingRecipients => formatter.write_str("no OpenPGP recipients were provided"),
            Self::MissingEncryptionKey { fingerprint } => {
                write!(formatter, "OpenPGP key cannot encrypt: {fingerprint}")
            }
            Self::CorruptMessage => formatter.write_str("encrypted OpenPGP message is malformed"),
            Self::MissingSecretKey => {
                formatter.write_str("no imported secret key can decrypt the message")
            }
            Self::SecretProvider {
                fingerprint,
                reason,
            } => write!(
                formatter,
                "secret provider could not unlock OpenPGP key {fingerprint}: {reason:?}"
            ),
            Self::DecryptionFailed => formatter.write_str("OpenPGP decryption failed"),
            Self::InvalidKeyPassphrase => {
                formatter.write_str("the OpenPGP key passphrase is incorrect")
            }
            Self::MissingSigningKey { fingerprint } => {
                write!(formatter, "OpenPGP key cannot sign: {fingerprint}")
            }
            Self::EncryptionFailed => formatter.write_str("OpenPGP encryption failed"),
            Self::SigningFailed => formatter.write_str("OpenPGP signing failed"),
            Self::CorruptSignature => {
                formatter.write_str("detached OpenPGP signature is malformed")
            }
            Self::InvalidSignature => formatter.write_str("detached OpenPGP signature is invalid"),
            Self::Io {
                operation,
                path,
                source,
            } => write!(formatter, "cannot {operation} {}: {source}", path.display()),
        }
    }
}

impl Error for CryptoError {}

fn fingerprint_of(key: &SignedPublicKey) -> KeyFingerprint {
    KeyFingerprint(format!("{:X}", key.fingerprint()))
}

fn key_info(material: &KeyMaterial) -> KeyInfo {
    KeyInfo {
        fingerprint: fingerprint_of(&material.public),
        key_id: material
            .public
            .legacy_key_id()
            .to_string()
            .to_ascii_uppercase(),
        user_ids: material
            .public
            .details
            .users
            .iter()
            .filter_map(|user| user.id.as_str().map(str::to_owned))
            .collect(),
        has_secret: material.secret.is_some(),
        requires_passphrase: material
            .secret
            .as_ref()
            .is_some_and(secret_requires_password),
        can_encrypt: encryption_target(&material.public).is_some(),
        can_sign: can_sign(&material.public),
    }
}

fn normalize_hex_identity(identity: &str) -> Option<String> {
    let identity = identity
        .strip_prefix("0x")
        .or_else(|| identity.strip_prefix("0X"))
        .unwrap_or(identity);
    if matches!(identity.len(), 8 | 16 | 40 | 64)
        && identity.bytes().all(|byte| byte.is_ascii_hexdigit())
    {
        Some(identity.to_ascii_uppercase())
    } else {
        None
    }
}

fn key_matches_hex(key: &KeyMaterial, identity: &str) -> bool {
    let mut identities = vec![
        format!("{:X}", key.public.fingerprint()),
        key.public.legacy_key_id().to_string().to_ascii_uppercase(),
    ];
    for subkey in &key.public.public_subkeys {
        identities.push(format!("{:X}", subkey.fingerprint()));
        identities.push(subkey.legacy_key_id().to_string().to_ascii_uppercase());
    }
    identities
        .iter()
        .any(|candidate| candidate == identity || candidate.ends_with(identity))
}

enum EncryptionTarget<'a> {
    Primary(&'a SignedPublicKey),
    Subkey(&'a SignedPublicSubKey),
}

fn encryption_target(key: &SignedPublicKey) -> Option<EncryptionTarget<'_>> {
    for subkey in &key.public_subkeys {
        let revoked = subkey
            .signatures
            .iter()
            .any(|signature| signature.typ() == Some(SignatureType::SubkeyRevocation));
        let encrypts = subkey.signatures.iter().any(|signature| {
            let flags = signature.key_flags();
            flags.encrypt_comms() || flags.encrypt_storage()
        });
        if encrypts && !revoked {
            return Some(EncryptionTarget::Subkey(subkey));
        }
    }
    if primary_flags(key, |flags| {
        flags.encrypt_comms() || flags.encrypt_storage()
    }) {
        Some(EncryptionTarget::Primary(key))
    } else {
        None
    }
}

#[derive(Clone, Copy)]
enum SigningTarget<'a> {
    Primary(&'a pgp::packet::SecretKey),
    Subkey(&'a pgp::packet::SecretSubkey),
}

fn signing_target(key: &SignedSecretKey) -> Option<SigningTarget<'_>> {
    for subkey in &key.secret_subkeys {
        let revoked = subkey
            .signatures
            .iter()
            .any(|signature| signature.typ() == Some(SignatureType::SubkeyRevocation));
        let signs = subkey
            .signatures
            .iter()
            .any(|signature| signature.key_flags().sign());
        if signs && !revoked {
            return Some(SigningTarget::Subkey(&subkey.key));
        }
    }
    let public = key.to_public_key();
    if primary_flags(&public, |flags| flags.sign()) {
        Some(SigningTarget::Primary(&key.primary_key))
    } else {
        None
    }
}

fn can_sign(key: &SignedPublicKey) -> bool {
    key.public_subkeys.iter().any(|subkey| {
        let revoked = subkey
            .signatures
            .iter()
            .any(|signature| signature.typ() == Some(SignatureType::SubkeyRevocation));
        !revoked
            && subkey
                .signatures
                .iter()
                .any(|signature| signature.key_flags().sign())
    }) || primary_flags(key, |flags| flags.sign())
}

fn primary_flags(
    key: &SignedPublicKey,
    predicate: impl Fn(&pgp::packet::KeyFlags) -> bool,
) -> bool {
    key.details
        .direct_signatures
        .iter()
        .chain(
            key.details
                .users
                .iter()
                .flat_map(|user| user.signatures.iter()),
        )
        .any(|signature| predicate(&signature.key_flags()))
}

fn secret_requires_password(key: &SignedSecretKey) -> bool {
    key.primary_key.secret_params().is_encrypted()
        || key
            .secret_subkeys
            .iter()
            .any(|subkey| subkey.secret_params().is_encrypted())
}

fn secret_unlocks(key: &SignedSecretKey, password: &Password) -> bool {
    matches!(key.primary_key.unlock(password, |_, _| Ok(())), Ok(Ok(())))
        && key
            .secret_subkeys
            .iter()
            .all(|subkey| matches!(subkey.key.unlock(password, |_, _| Ok(())), Ok(Ok(()))))
}

fn signing_target_requires_password(target: SigningTarget<'_>) -> bool {
    match target {
        SigningTarget::Primary(key) => key.secret_params().is_encrypted(),
        SigningTarget::Subkey(key) => key.secret_params().is_encrypted(),
    }
}

fn message_matches_secret(message: &Message<'_>, key: &SignedSecretKey) -> bool {
    let Message::Encrypted { esk, .. } = message else {
        return false;
    };
    esk.iter().any(|esk| {
        let Esk::PublicKeyEncryptedSessionKey(esk) = esk else {
            return false;
        };
        esk.match_identity(key.primary_key.public_key())
            || key
                .secret_subkeys
                .iter()
                .any(|subkey| esk.match_identity(subkey.public_key()))
    })
}

fn packet_matches_public(
    packet: &pgp::packet::PublicKeyEncryptedSessionKey,
    key: &SignedPublicKey,
) -> bool {
    packet.match_identity(&key.primary_key)
        || key
            .public_subkeys
            .iter()
            .any(|subkey| packet.match_identity(&subkey.key))
}

fn sign_data<K: SigningKey>(
    key: &K,
    password: &Password,
    data: &[u8],
) -> Result<DetachedSignature, CryptoError> {
    let hashed = vec![
        Subpacket::regular(SubpacketData::IssuerFingerprint(key.fingerprint()))
            .map_err(|_| CryptoError::SigningFailed)?,
        Subpacket::regular(SubpacketData::SignatureCreationTime(Timestamp::now()))
            .map_err(|_| CryptoError::SigningFailed)?,
    ];
    let unhashed = vec![
        Subpacket::regular(SubpacketData::IssuerKeyId(key.legacy_key_id()))
            .map_err(|_| CryptoError::SigningFailed)?,
    ];
    DetachedSignature::sign_binary_data_with_subpackets(
        OsRng,
        key,
        password,
        HashAlgorithm::Sha256,
        data,
        SubpacketConfig::UserDefined { hashed, unhashed },
    )
    .map_err(|_| CryptoError::SigningFailed)
}

fn verify_with_public(signature: &DetachedSignature, key: &SignedPublicKey, data: &[u8]) -> bool {
    if verify_data(signature, &key.primary_key, data) {
        return true;
    }
    key.public_subkeys
        .iter()
        .any(|subkey| verify_data(signature, &subkey.key, data))
}

fn verify_data<K: VerifyingKey>(signature: &DetachedSignature, key: &K, data: &[u8]) -> bool {
    signature.verify(key, data).is_ok()
}

struct SecretReader {
    secret: SecretBytes,
    position: usize,
}

impl SecretReader {
    fn new(secret: SecretBytes) -> Self {
        Self {
            secret,
            position: 0,
        }
    }
}

impl Read for SecretReader {
    fn read(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
        let remaining = &self.secret.expose()[self.position..];
        let length = remaining.len().min(buffer.len());
        buffer[..length].copy_from_slice(&remaining[..length]);
        self.position += length;
        Ok(length)
    }
}

fn read_ambient_key_file(path: &Path, length: u64) -> Result<Vec<u8>, CryptoError> {
    if length > MAX_KEY_FILE_BYTES {
        return Err(CryptoError::KeyMaterialTooLarge);
    }
    let parent = path.parent().unwrap_or_else(|| Path::new("."));
    let name = path
        .file_name()
        .ok_or_else(|| CryptoError::UnsupportedKeyFileType {
            path: path.to_owned(),
        })?;
    let parent = fs::canonicalize(parent)
        .map_err(|error| crypto_io("canonicalize key parent", parent, error))?;
    let directory = Dir::open_ambient_dir(&parent, ambient_authority())
        .map_err(|error| crypto_io("open key parent", &parent, error))?;
    let metadata = directory
        .symlink_metadata(name)
        .map_err(|error| crypto_io("inspect key file", path, error))?;
    if metadata.file_type().is_symlink() || !metadata.is_file() {
        return Err(CryptoError::UnsupportedKeyFileType {
            path: path.to_owned(),
        });
    }
    read_cap_file(&directory, name, path, metadata.len())
}

fn import_key_directory(
    store: &mut KeyStore,
    directory: &Dir,
    relative: &Path,
) -> Result<(), CryptoError> {
    let mut entries = directory
        .read_dir(".")
        .map_err(|error| crypto_io("read key directory", relative, error))?
        .collect::<Result<Vec<_>, _>>()
        .map_err(|error| crypto_io("read key directory entry", relative, error))?;
    entries.sort_by_key(cap_std::fs::DirEntry::file_name);
    for entry in entries {
        let name = entry.file_name();
        let path = relative.join(&name);
        let file_type = entry
            .file_type()
            .map_err(|error| crypto_io("inspect key directory entry", &path, error))?;
        if file_type.is_symlink() || (!file_type.is_file() && !file_type.is_dir()) {
            return Err(CryptoError::UnsupportedKeyFileType { path });
        }
        if file_type.is_dir() {
            let child = entry
                .open_dir()
                .map_err(|error| crypto_io("open key directory", &path, error))?;
            import_key_directory(store, &child, &path)?;
        } else {
            let length = entry
                .metadata()
                .map_err(|error| crypto_io("inspect key file", &path, error))?
                .len();
            let bytes = read_cap_file(directory, &name, &path, length)?;
            store.import(&bytes)?;
        }
    }
    Ok(())
}

fn read_cap_file(
    directory: &Dir,
    name: impl AsRef<Path>,
    path: &Path,
    length: u64,
) -> Result<Vec<u8>, CryptoError> {
    if length > MAX_KEY_FILE_BYTES {
        return Err(CryptoError::KeyMaterialTooLarge);
    }
    let mut file = directory
        .open(name)
        .map_err(|error| crypto_io("open key file", path, error))?;
    let mut bytes = Vec::with_capacity(length as usize);
    file.read_to_end(&mut bytes)
        .map_err(|error| crypto_io("read key file", path, error))?;
    if bytes.len() as u64 > MAX_KEY_FILE_BYTES {
        return Err(CryptoError::KeyMaterialTooLarge);
    }
    Ok(bytes)
}

fn crypto_io(operation: &'static str, path: &Path, error: io::Error) -> CryptoError {
    CryptoError::Io {
        operation,
        path: path.to_owned(),
        source: error.kind(),
    }
}
