use super::openpgp::{
    self,
    cert::Cert,
    packet::{PKESK, SKESK},
    parse::stream::{DecryptionHelper, MessageLayer, MessageStructure, VerificationHelper},
    KeyHandle, KeyID,
};

#[derive(Clone, Debug)]
pub enum KeyringFailure {
    SecretKeyNotFound,
    #[allow(dead_code)]
    SmartcardDecrypt,
}

impl std::fmt::Display for KeyringFailure {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            KeyringFailure::SecretKeyNotFound => f.write_str("Secret key was not found"),
            KeyringFailure::SmartcardDecrypt => {
                f.write_str("Smartcard could not decrypt any PKESKs")
            }
        }
    }
}

impl std::error::Error for KeyringFailure {}

#[derive(Clone, Debug)]
pub struct Keyring {
    full_certs: Vec<Cert>,
    root: Option<Cert>,
}

impl Keyring {
    pub fn new(certs: impl AsRef<[Cert]>) -> Self {
        Self {
            full_certs: certs.as_ref().to_vec(),
            root: Default::default(),
        }
    }

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

    // Sets the root cert, returning the old cert
    pub fn set_root_cert(&mut self, cert: impl Into<Option<Cert>>) -> Option<Cert> {
        let mut cert = cert.into();
        std::mem::swap(&mut self.root, &mut cert);
        cert
    }

    pub fn root_cert(&self) -> Option<&Cert> {
        self.root.as_ref()
    }

    pub fn get_cert_for_primary_keyid<'a>(&'a self, keyid: &KeyID) -> Option<&'a Cert> {
        self.full_certs.iter().find(|cert| &cert.keyid() == keyid)
    }

    // NOTE: This can't return an iterator because iterators are all different types
    // and returning different types is naughty
    fn get_certs_for_pkesk<'a>(&'a self, pkesk: &'a PKESK) -> impl Iterator<Item = &Cert> + 'a {
        self.full_certs.iter().filter(move |cert| {
            pkesk.recipient().is_wildcard() || cert.keys().any(|k| &k.keyid() == pkesk.recipient())
        })
    }
}

impl VerificationHelper for &mut Keyring {
    fn get_certs(&mut self, ids: &[KeyHandle]) -> openpgp::Result<Vec<Cert>> {
        Ok(ids
            .iter()
            .flat_map(|kh| {
                self.root
                    .iter()
                    .filter(move |cert| &cert.key_handle() == kh)
            })
            .cloned()
            .collect())
    }
    fn check(&mut self, structure: MessageStructure) -> openpgp::Result<()> {
        for layer in structure.into_iter() {
            #[allow(unused_variables)]
            match layer {
                MessageLayer::Compression { algo } => {}
                MessageLayer::Encryption {
                    sym_algo,
                    aead_algo,
                } => {}
                MessageLayer::SignatureGroup { results } => {
                    for result in results {
                        if let Err(e) = result {
                            // FIXME: anyhow leak
                            return Err(anyhow::anyhow!(e.to_string()));
                        }
                    }
                }
            }
        }
        Ok(())
    }
}

impl DecryptionHelper for &mut Keyring {
    fn decrypt<D>(
        &mut self,
        pkesks: &[PKESK],
        _skesks: &[SKESK],
        sym_algo: Option<openpgp::types::SymmetricAlgorithm>,
        mut decrypt: D,
    ) -> openpgp::Result<Option<openpgp::Fingerprint>>
    where
        D: FnMut(openpgp::types::SymmetricAlgorithm, &openpgp::crypto::SessionKey) -> bool,
    {
        // unoptimized route: use all locally stored certs
        for pkesk in pkesks {
            for cert in self.get_certs_for_pkesk(pkesk) {
                for key in cert.keys().secret() {
                    let secret_key = key.key().clone();
                    // NOTE: Returns an error if using an encrypted secret key.
                    // TODO: support skipping or validating encrypted secret keys.
                    let mut keypair = secret_key.into_keypair()?;
                    if pkesk
                        .decrypt(&mut keypair, sym_algo)
                        .map(|(algo, sk)| decrypt(algo, &sk))
                        .unwrap_or(false)
                    {
                        return Ok(Some(key.fingerprint()));
                    }
                }
            }
        }

        Err(KeyringFailure::SecretKeyNotFound.into())
    }
}