use crate::error::{KeyringError, Result};
use crate::*;
use security_framework::base::Error as SfError;
use security_framework::passwords::{
    delete_generic_password, get_generic_password, set_generic_password,
};

pub type OSStatus = i32;

#[allow(non_upper_case_globals)]
const errSecItemNotFound: OSStatus = -25300;

#[allow(non_upper_case_globals)]
#[inline(always)]
fn map_keyring_error(err: SfError) -> KeyringError {
    match err.code() {
        errSecItemNotFound => KeyringError::NoPasswordFound,
        err => KeyringError::from(err),
    }
}

pub struct IosKeyringManager {
    application: String,
}

impl IosKeyringManager {
    pub fn new(application: &str) -> Result<Self> {
        Ok(IosKeyringManager {
            application: application.to_owned(),
        })
    }

    pub fn with_keyring<F, T>(&self, service: &str, key: &str, func: F) -> Result<T>
    where
        F: FnOnce(&mut dyn Keyring) -> Result<T>,
    {
        let mut kr = IosKeyring::new(&self.application, service, key)?;
        func(&mut kr)
    }
}

pub struct IosKeyring<'a> {
    application: &'a str,
    service: &'a str,
    key: &'a str,
}

impl<'a> IosKeyring<'a> {
    fn new(application: &'a str, service: &'a str, key: &'a str) -> Result<IosKeyring<'a>> {
        Ok(IosKeyring {
            application,
            service,
            key,
        })
    }

    fn make_key_string(&self) -> String {
        [
            crate::escape(self.application),
            crate::escape(self.service),
            crate::escape(self.key),
        ]
        .join("\t")
    }
}
impl<'a> Keyring for IosKeyring<'a> {
    fn set_value(&mut self, value: &str) -> Result<()> {
        let account_name = self.make_key_string();
        set_generic_password("", &account_name, value.as_bytes()).map_err(map_keyring_error)?;
        Ok(())
    }

    fn get_value(&self) -> Result<String> {
        let account_name = self.make_key_string();
        let data = get_generic_password("", &account_name).map_err(map_keyring_error)?;
        let mut buf = Vec::new();
        buf.extend_from_slice(&data);
        String::from_utf8(buf).map_err(|e| {
            KeyringError::from(format!("couldn't convert keychain data to string: {}", e))
        })
    }

    fn delete_value(&mut self) -> Result<()> {
        let account_name = self.make_key_string();
        delete_generic_password("", &account_name).map_err(map_keyring_error)?;
        Ok(())
    }
}
