// License: see LICENSE file at root directory of `master` branch

//! # Users

use std::{
    convert::TryFrom,
    io::{self, Error, ErrorKind},
};

use bailamos::db::Id;
use rusqlite::Connection;
use zeros::{ReadOnlyZeros, Zeros};

use crate::db::ID;

/// # Passphrase
pub type Passphrase = ReadOnlyZeros;

/// # Table
pub const TABLE: &str = "users";

/// # Date created
pub const DATE_CREATED: &str = "date_created";

/// # Date modified
pub const DATE_MODIFIED: &str = "date_modified";

/// # Name
pub const NAME: &str = "name";

/// # Passphrase
pub const PASSPHRASE: &str = "passphrase";

/// # E-mail
pub const EMAIL: &str = "email";

/// # User
#[derive(Debug)]
pub struct User {
    id: Option<Id>,
    name: String,
    passphrase: Passphrase,
    email: Option<String>,
}

impl User {

    /// # Makes new instance
    pub const fn new(id: Option<Id>, name: String, passphrase: Passphrase, email: Option<String>) -> Self {
        Self {
            id,
            name,
            passphrase,
            email,
        }
    }

    /// # ID
    pub fn id(&self) -> Option<Id> {
        self.id
    }

    /// # Name
    pub fn name(&self) -> &str {
        &self.name
    }

    /// # Passphrase
    pub fn passphrase(&self) -> &Passphrase {
        &self.passphrase
    }

    /// # E-mail
    pub fn email(&self) -> Option<&str> {
        self.email.as_ref().map(|e| e.as_str())
    }

}

/// # Hashes passphrase
pub fn hash_passphrase<B>(passphrase: B) -> Passphrase where B: AsRef<[u8]> {
    Passphrase::from(Zeros::from(tiny_keccak::sha3_512(&tiny_keccak::sha3_512(passphrase.as_ref()))))
}

/// # Loads users, sorted by name, in ascending order
pub fn load_users(conn: &Connection) -> io::Result<Vec<User>> {
    let result = move || -> rusqlite::Result<_> {
        let mut statement = conn.prepare_cached(&format!("select {},{},{},{} from {} order by {};", ID, NAME, PASSPHRASE, EMAIL, TABLE, NAME))?;
        let mut result = vec![];
        for r in statement.query_map(rusqlite::NO_PARAMS, |r| Ok((r.get(0)?, r.get(1)?, r.get::<_, Vec<u8>>(2)?, r.get(3)?)))? {
            let (id, name, passphrase, email) = r?;
            let passphrase = Passphrase::try_from(passphrase).map_err(|e| rusqlite::Error::UserFunctionError(Box::new(Error::from(e))))?;
            result.push(User { id, name, passphrase, email});
        }
        Ok(result)
    };
    result().map_err(|e| Error::new(ErrorKind::Other, e))
}
