//! KeyInfo and related data structures.
//!
//! This module defines the [`KeyInfo`] family of data structures.
//! [`KeyInfoList`] is a collection of `KeyInfo`, and is returned by
//! [`Agent::list_keys`](crate::Agent::list_keys).  `KeyInfoList` can
//! be iterated over, and individual keys can be efficiently looked up
//! by their keygrip.  Note: a keygrip is not an OpenPGP fingerprint.
//! gpg-agent does not support addressing keys by their OpenPGP
//! fingerprint; they can only be looked up by their keygrip.
use std::borrow::Borrow;
use std::collections::HashMap;
use std::str::FromStr;

use sequoia_openpgp as openpgp;
use openpgp::packet::key;
use openpgp::packet::Key;

use sequoia_ipc as ipc;
use ipc::Keygrip;

use anyhow::Context;

use crate::Result;

/// KeyInfo-related errors.
#[derive(thiserror::Error, Debug)]
#[non_exhaustive]
pub enum Error {
    #[error("Error parsing keyinfo data: {0}")]
    ParseError(String),
}

/// The type of key.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum KeyType {
    /// Regular key stored on disk,
    Regular,
    /// Key is stored on a smartcard (token),
    Smartcard,
    /// Unknown type,
    Unknown,
    /// Key type is missing.
    Missing,
}

/// A key's protection, if any.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum KeyProtection {
    /// The key is protected with a passphrase.
    Protected,
    /// The key is not protected.
    NotProtected,
    /// Unknown protection.
    UnknownProtection,
}

/// A key's flags, if any.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
enum KeyFlag {
    /// The key has been disabled.
    Disabled,
    /// The key is listed in sshcontrol.
    SSHControl,
    /// Use of the key needs to be confirmed.
    ConfirmationRequired,
}

/// Information about a key.
///
/// Returned by `Agent::list_keys`.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct KeyInfo {
    keygrip: Keygrip,

    /// TYPE is describes the type of the key:
    ///
    ///  - 'D' - Regular key stored on disk,
    ///  - 'T' - Key is stored on a smartcard (token),
    ///  - 'X' - Unknown type,
    ///  - '-' - Key is missing.
    keytype: KeyType,

    /// SERIALNO is an ASCII string with the serial number of the
    /// smartcard.  If the serial number is not known a single dash
    /// '-' is used instead.
    serialno: Option<String>,

    /// If the key is on a smartcard, this is used to distinguish the
    /// keys on the smartcard.  If it is not known a dash is used
    /// instead.
    ///
    /// Example: `OPENPGP.1`.
    idstr: Option<String>,

    /// If the passphrase for the key was found in the key cache.
    passphrase_cached: bool,

    /// The key protection type:
    ///
    ///   - 'P' - The key is protected with a passphrase,
    ///   - 'C' - The key is not protected,
    ///   - '-' - Unknown protection.
    protection: KeyProtection,

    /// The TTL in seconds for the key or None if not available.
    ttl: Option<u64>,

    /// The key's flag.
    flags: Vec<KeyFlag>,
}

impl KeyInfo {
    /// Returns the key's keygrip.
    pub fn keygrip(&self) -> &ipc::Keygrip {
        &self.keygrip
    }

    /// Returns the key's type.
    pub fn keytype(&self) -> KeyType {
        self.keytype.clone()
    }

    /// Returns the serial number of the smartcard.
    ///
    /// If the key is not on a smartcard, this returns `None`.
    pub fn serialno(&self) -> Option<&str> {
        self.serialno.as_deref()
    }

    /// Returns the key's identifier on the smartcard.
    ///
    /// If the key is not on a smartcard, this returns `None`.
    /// Example: `OPENPGP.1`.
    pub fn idstr(&self) -> Option<&str> {
        self.idstr.as_deref()
    }

    /// Returns whether the passphrase for the key was found in the
    /// key cache.
    pub fn passphrase_cached(&self) -> bool {
        self.passphrase_cached
    }

    /// Returns the key's protection, if any.
    pub fn protection(&self) -> &KeyProtection {
        &self.protection
    }

    /// Returns the TTL in seconds for the key.
    ///
    /// If not available, returns `None`.
    pub fn ttl(&self) -> Option<u64> {
        self.ttl
    }

    /// Returns whether the key has been disabled.
    pub fn key_disabled(&self) -> bool {
        self.flags.contains(&KeyFlag::Disabled)
    }

    /// Returns whether the key is listed in sshcontrol.
    pub fn in_ssh_control(&self) -> bool {
        self.flags.contains(&KeyFlag::SSHControl)
    }

    /// Returns whether use of the key needs to be confirmed.
    pub fn confirmation_required(&self) -> bool {
        self.flags.contains(&KeyFlag::ConfirmationRequired)
    }

    /// Parses a single keyinfo entry.
    ///
    /// An entry is of the form:
    ///
    /// ```text
    /// KEYINFO <keygrip> <type> <serialno> <idstr> <cached> <protection> <ssh-fpr> <ttl> <flags>
    /// ```
    ///
    /// Note: `keyinfo` should be the whole line without the leading
    /// `KEYINFO`.
    pub(crate) fn parse(keyinfo: &str) -> Result<Self> {
        let mut iter = keyinfo.split(' ');

        let Some(keygrip) = iter.next() else {
            return Err(Error::ParseError("KEYGRIP field missing".into()).into());
        };
        let keygrip = Keygrip::from_str(keygrip)
            .with_context(|| {
                format!("Parsing {:?} as a keygrip", keygrip)
            })?;

        // TYPE is describes the type of the key:
        //     'D' - Regular key stored on disk,
        //     'T' - Key is stored on a smartcard (token),
        //     'X' - Unknown type,
        //     '-' - Key is missing.
        let Some(keytype) = iter.next() else {
            return Err(Error::ParseError("TYPE field missing".into()).into());
        };
        let keytype = match keytype {
            "D" => KeyType::Regular,
            "T" => KeyType::Smartcard,
            "-" => KeyType::Missing,
            "X"|_ => KeyType::Unknown,
        };

        // SERIALNO is an ASCII string with the serial number of the
        //          smartcard.  If the serial number is not known a single
        //          dash '-' is used instead.
        let Some(serialno) = iter.next() else {
            return Err(Error::ParseError("SERIALNO field missing".into()).into());
        };
        let serialno = if serialno == "-" {
            None
        } else {
            Some(serialno.to_string())
        };

        // IDSTR is the IDSTR used to distinguish keys on a smartcard.  If it
        //       is not known a dash is used instead.
        let Some(idstr) = iter.next() else {
            return Err(Error::ParseError("IDSTR field missing".into()).into());
        };
        let idstr = if idstr == "-" {
            None
        } else {
            Some(idstr.to_string())
        };

        // CACHED is 1 if the passphrase for the key was found in the key cache.
        //        If not, a '-' is used instead.
        let Some(cached) = iter.next() else {
            return Err(Error::ParseError("CACHED field missing".into()).into());
        };
        let passphrase_cached = match cached {
            "1" => true,
            _ => false,
        };

        // PROTECTION describes the key protection type:
        //     'P' - The key is protected with a passphrase,
        //     'C' - The key is not protected,
        //     '-' - Unknown protection.
        let Some(protection) = iter.next() else {
            return Err(Error::ParseError("PROTECTION field missing".into()).into());
        };
        let protection = match protection {
            "P" => KeyProtection::Protected,
            "C" => KeyProtection::NotProtected,
            _ => KeyProtection::UnknownProtection,
        };

        // FPR returns the formatted ssh-style fingerprint of the key.  It is only
        //     printed if the option --ssh-fpr has been used.  If ALGO is not given
        //     to that option the default ssh fingerprint algo is used.  Without the
        //     option a '-' is printed.
        let Some(_ssh_fpr) = iter.next() else {
            return Err(Error::ParseError("FPR field missing".into()).into());
        };

        // TTL is the TTL in seconds for that key or '-' if n/a.
        let Some(ttl) = iter.next() else {
            return Err(Error::ParseError("TTL field missing".into()).into());
        };
        let ttl = if ttl == "-" {
            None
        } else {
            Some(u64::from_str_radix(ttl, 10).with_context(|| {
                format!("Parsing {:?} as a u64", ttl)
            })?)
        };

        // FLAGS is a word consisting of one-letter flags:
        //       'D' - The key has been disabled,
        //       'S' - The key is listed in sshcontrol (requires --with-ssh),
        //       'c' - Use of the key needs to be confirmed,
        //       '-' - No flags given.
        let Some(flags) = iter.next() else {
            return Err(Error::ParseError("FLAGS field missing".into()).into());
        };
        let flags = flags.chars()
            .filter_map(|c| {
                match c {
                    'D' => Some(KeyFlag::Disabled),
                    'S' => Some(KeyFlag::SSHControl),
                    'c' => Some(KeyFlag::ConfirmationRequired),
                    _ => None,
                }
            })
            .collect::<Vec<KeyFlag>>();

        Ok(KeyInfo {
            keygrip,
            keytype,
            serialno,
            idstr,
            passphrase_cached,
            protection,
            ttl,
            flags,
        })
    }
}

/// A collection of `KeyInfo`.
pub struct KeyInfoList {
    keys: HashMap<Keygrip, KeyInfo>,
}

impl FromIterator<KeyInfo> for KeyInfoList {
    fn from_iter<I>(iter: I) -> Self
    where I: IntoIterator<Item=KeyInfo>
    {
        KeyInfoList {
            keys: HashMap::from_iter(
                iter.into_iter().map(|i| (i.keygrip().clone(), i))),
        }
    }
}

impl Default for KeyInfoList {
    fn default() -> Self {
        Self {
            keys: Default::default(),
        }
    }
}

impl KeyInfoList {
    /// Returns an empty `KeyInfoList`.
    pub fn empty() -> Self {
        Self::default()
    }

    /// Returns the information about the specified key, if any.
    pub fn lookup<K>(&self, keygrip: K) -> Option<&KeyInfo>
        where K: Borrow<Keygrip>
    {
        let keygrip = keygrip.borrow();
        self.keys.get(keygrip)
    }

    /// Returns the information about the specified key, if any.
    pub fn lookup_by_key<K, R, P>(&self, key: K) -> Option<&KeyInfo>
    where K: Borrow<Key<P, R>>,
          P: key:: KeyParts,
          R: key:: KeyRole,
    {
        let key = key.borrow();
        // The mapping from Key to Keygrip is not total so this might
        // fail.  But if there isn't a keygrip for a key, then
        // gpg-agent can't manage it, and thus can't return it.
        // Therefore if a key doesn't have a keygrip, the correct
        // answer is not to return an error, but to return `None`.
        let keygrip = Keygrip::of(key.mpis()).ok()?;
        self.lookup(keygrip)
    }

    /// Returns an iterator over the elements of the `KeyInfoList`.
    pub fn iter(&self) -> KeyInfoListIter {
        KeyInfoListIter {
            iter: self.keys.values(),
        }
    }

    /// Returns the number of elements.
    pub fn len(&self) -> usize {
        self.keys.len()
    }
}

/// An iterator over a `KeyInfoList`.
pub struct KeyInfoListIter<'a> {
    iter: std::collections::hash_map::Values<'a, Keygrip, KeyInfo>,
}

impl<'a> IntoIterator for &'a KeyInfoList {
    type Item = &'a KeyInfo;
    type IntoIter = KeyInfoListIter<'a>;

    fn into_iter(self) -> Self::IntoIter {
        KeyInfoListIter {
            iter: self.keys.values()
        }
    }
}

impl<'a> Iterator for KeyInfoListIter<'a> {
    type Item = &'a KeyInfo;

    fn next(&mut self) -> Option<Self::Item> {
        self.iter.next()
    }
}

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

    #[test]
    fn parse_keyinfo_list() -> Result<()> {
        let entry = "EF8CE31AE9E310D660C7C9709A028442A8B52112 D - - - C - - -";
        eprintln!("{}", entry);
        assert_eq!(
            KeyInfo::parse(entry).unwrap(),
            KeyInfo {
                keygrip: Keygrip::from_str(
                    "EF8CE31AE9E310D660C7C9709A028442A8B52112").unwrap(),
                keytype: KeyType::Regular,
                serialno: None,
                idstr: None,
                passphrase_cached: false,
                protection: KeyProtection::NotProtected,
                ttl: None,
                flags: vec![ ],
            });

        let entry = "EAFE58E4F5269129D7233A0FA6307C366A3EA2CC D - - - P - - -";
        eprintln!("{}", entry);
        assert_eq!(
            KeyInfo::parse(entry).unwrap(),
            KeyInfo {
                keygrip: Keygrip::from_str(
                    "EAFE58E4F5269129D7233A0FA6307C366A3EA2CC").unwrap(),
                keytype: KeyType::Regular,
                serialno: None,
                idstr: None,
                passphrase_cached: false,
                protection: KeyProtection::Protected,
                ttl: None,
                flags: vec![ ],
            });

        let entry = "9483454871CC1239D4C2A1416F2742D39A14DB14 T D2760001240103040006181329630000 OPENPGP.3 - - - - -";
        eprintln!("{}", entry);
        assert_eq!(
            KeyInfo::parse(entry).unwrap(),
            KeyInfo {
                keygrip: Keygrip::from_str(
                    "9483454871CC1239D4C2A1416F2742D39A14DB14").unwrap(),
                keytype: KeyType::Smartcard,
                serialno: Some("D2760001240103040006181329630000".to_string()),
                idstr: Some("OPENPGP.3".to_string()),
                passphrase_cached: false,
                protection: KeyProtection::UnknownProtection,
                ttl: None,
                flags: vec![ ],
            });

        Ok(())
    }
}
