use crate::error::{KeyringError, Result};
use crate::Keyring;
use dbus_secret_service::{Collection, EncryptionType, SecretService};
use std::collections::HashMap;
use std::sync::{Arc, Mutex};

pub struct LinuxKeyringManager {
    pub ss: Arc<Mutex<SecretService>>,
    pub application: String,
}

impl LinuxKeyringManager {
    pub fn new(application: &str) -> Result<Self> {
        let s = Self {
            ss: Arc::new(Mutex::new(SecretService::connect(EncryptionType::Dh)?)),
            application: application.to_owned(),
        };

        // Ensure keyring works
        s.with_keyring("test_service", "test_key", |k| {
            if let Err(e) = k.set_value("test_value") {
                return Err(KeyringError::Generic(format!(
                    "Keyring is not setting values: {}",
                    e
                )));
            }
            match k.get_value() {
                Ok(v) => {
                    if v != "test_value" {
                        return Err(KeyringError::Generic(
                            "Keyring is not returning correct values".to_owned(),
                        ));
                    }
                }
                Err(e) => {
                    return Err(KeyringError::Generic(format!(
                        "Keyring is not operational: {}",
                        e
                    )));
                }
            };
            if let Err(e) = k.delete_value() {
                return Err(KeyringError::Generic(format!(
                    "Keyring is not deleting values: {}",
                    e
                )));
            }

            //
            Ok(())
        })?;
        Ok(s)
    }

    pub fn with_keyring<F, T>(&self, service: &str, key: &str, func: F) -> Result<T>
    where
        F: FnOnce(&mut dyn Keyring) -> Result<T>,
    {
        let ss = self
            .ss
            .lock()
            .map_err(|e| KeyringError::Generic(format!("Failed to lock secret service: {}", e)))?;

        let coll = match ss.get_default_collection() {
            Ok(c) => c,
            Err(_) => ss.create_collection("Default", "default")?,
        };

        let label = &format!(
            "Password for {} on {} in {}",
            key, service, &self.application
        )[..];
        let mut attributes: HashMap<&str, &str> = HashMap::new();
        attributes.insert("service", service);
        attributes.insert("username", key);
        attributes.insert("application", &self.application);

        let mut kr = LinuxKeyring::new(coll, label, attributes)?;
        func(&mut kr)
    }
}

pub struct LinuxKeyring<'a> {
    collection: Collection<'a>,
    label: &'a str,
    attributes: HashMap<&'a str, &'a str>,
}

impl<'a> LinuxKeyring<'a> {
    fn new(
        collection: Collection<'a>,
        label: &'a str,
        attributes: HashMap<&'a str, &'a str>,
    ) -> Result<LinuxKeyring<'a>> {
        Ok(LinuxKeyring {
            collection,
            label,
            attributes,
        })
    }
}

impl<'a> Keyring for LinuxKeyring<'a> {
    fn set_value(&mut self, value: &str) -> Result<()> {
        self.collection.create_item(
            self.label,
            self.attributes.clone(),
            value.as_bytes(),
            true, // replace
            "text/plain",
        )?;
        Ok(())
    }

    fn get_value(&self) -> Result<String> {
        let search = self.collection.search_items(self.attributes.clone())?;
        let item = search.first().ok_or(KeyringError::NoPasswordFound)?;
        let secret_bytes = item.get_secret()?;
        let secret = String::from_utf8(secret_bytes)?;
        Ok(secret)
    }

    fn delete_value(&mut self) -> Result<()> {
        let search = self.collection.search_items(self.attributes.clone())?;
        let item = search.first().ok_or(KeyringError::NoPasswordFound)?;
        item.delete().map_err(KeyringError::SecretServiceError)
    }
}
