use crate::backend::VfsFetchError;
use crate::config::VfsUser;
use crate::{AuthResult, UserAuthError, UserVfs, VfsAuth, VirtualFS, sha256_hash};
use async_trait::async_trait;
use std::collections::HashMap;
use std::sync::{Mutex, RwLock};

#[derive(Debug)]
pub struct VfsManager {
    users: RwLock<Vec<VfsUser>>,
    cache: HashMap<String, VirtualFS>,
}

impl VfsManager {
    /// Creates a new `VfsManager` with the provided list of VFS users.
    ///
    /// # Arguments
    /// * `users` - Initial list of [`VfsUser`] entries to manage.
    pub fn new(users: Vec<VfsUser>) -> Self {
        Self {
            users: RwLock::new(users),
            cache: HashMap::new(),
        }
    }

    /// Replaces the current user list with a new one.
    ///
    /// Acquires a write lock on the internal user list before replacing it.
    ///
    /// # Arguments
    /// * `users` - The new list of [`VfsUser`] entries.
    pub fn set_users(&self, users: Vec<VfsUser>) {
        let lck = &mut *self.users.write().unwrap();
        *lck = users;
    }

    /// Creates a new [`VirtualFS`] instance for the given user, bypassing authentication.
    ///
    /// Returns `None` if no user with the given name exists.
    ///
    /// # Arguments
    /// * `user` - The username to look up.
    pub fn create_vfs(&self, user: &str) -> Option<VirtualFS> {
        Some(VirtualFS::new(self.get_user(user)?))
    }

    /// Returns a snapshot of all registered users.
    ///
    /// Acquires a read lock and clones the full user list.
    pub fn get_users(&self) -> Vec<VfsUser> {
        let lck = &*self.users.read().unwrap();
        (*lck).to_vec()
    }

    /// Looks up a single user by name.
    ///
    /// Returns `None` if no matching user is found.
    ///
    /// # Arguments
    /// * `user` - The username to search for.
    pub fn get_user(&self, user: &str) -> Option<VfsUser> {
        let lck = &*self.users.read().unwrap();
        (*lck).iter().find(|x| x.user_name == user).cloned()
    }

    /// Executes a closure against a cached [`VirtualFS`] for the given user,
    /// creating and caching one on first access.
    ///
    /// If a [`VirtualFS`] for `user` is already cached, the closure runs against it
    /// directly. Otherwise, a new instance is created via [`create_vfs`], the closure
    /// runs against it, and the instance is inserted into the cache for future calls.
    ///
    /// # Arguments
    /// * `user` - The username whose VFS to operate on.
    /// * `func` - A closure that receives a mutable reference to the [`VirtualFS`]
    ///   and returns a value of type `R`.
    ///
    /// # Errors
    /// Returns [`VfsFetchError::UserNotFound`] if `user` does not exist.
    ///
    /// # Example
    /// ```rust
    /// manager.with_vfs("alice", |vfs| vfs.list_files())?;
    /// ```
    pub fn with_vfs<F: FnOnce(&mut VirtualFS) -> R, R>(
        &mut self,
        user: &str,
        func: F,
    ) -> Result<R, VfsFetchError> {
        if let Some(x) = self.cache.get_mut(user) {
            Ok(func(x))
        } else {
            let user = user.to_string();
            let mut vfs = self.create_vfs(&user).ok_or(VfsFetchError::UserNotFound)?;
            let ret = func(&mut vfs);
            self.cache.insert(user, vfs);
            Ok(ret)
        }
    }
}

#[async_trait]
impl VfsAuth for VfsManager {
    // TODO: add some security and rate limiting etc.
    async fn auth_pass(&self, username: &str, password: &str) -> AuthResult<Box<dyn UserVfs>> {
        let hash = sha256_hash(password);
        if let Some(user) = self
            .users
            .read()
            .unwrap()
            .iter()
            .find(|x| x.user_name == username && x.sha256_hash == hash)
            .cloned()
        {
            let vfs = VirtualFS::new(user);
            Ok(Box::new(vfs))
        } else {
            Err(UserAuthError::InvalidLogin)
        }
    }

    async fn auth_key(&self, username: &str, key: &str) -> AuthResult<Box<dyn UserVfs>> {
        if let Some(user) = self
            .users
            .read()
            .unwrap()
            .iter()
            .find(|x| x.user_name == username && x.keys.iter().any(|x| x == key))
            .cloned()
        {
            let vfs = VirtualFS::new(user);
            Ok(Box::new(vfs))
        } else {
            Err(UserAuthError::InvalidLogin)
        }
    }

    async fn get_user(&self, username: &str) -> Option<VfsUser> {
        self.users
            .read()
            .unwrap()
            .iter()
            .find(|x| x.user_name == username)
            .cloned()
    }
}
