use core::{
    marker::PhantomData,
    ops::{Add, AddAssign},
};

use yggdrasil_abi::io::{FileMode, GroupId, UserId};

use super::Metadata;

/// Zero-sized token type used to ensure checked access to node functions
#[derive(Clone)]
pub struct AccessToken(PhantomData<()>);

#[allow(missing_docs)]
impl Metadata {
    pub fn is_writeable(&self, uid: UserId, gid: GroupId) -> bool {
        uid.is_root()
            || (uid == self.uid && self.mode.contains(FileMode::USER_WRITE))
            || (gid == self.gid && self.mode.contains(FileMode::GROUP_WRITE))
            || self.mode.contains(FileMode::OTHER_WRITE)
    }

    pub fn is_readable(&self, uid: UserId, gid: GroupId) -> bool {
        uid.is_root()
            || (uid == self.uid && self.mode.contains(FileMode::USER_READ))
            || (gid == self.gid && self.mode.contains(FileMode::GROUP_READ))
            || self.mode.contains(FileMode::OTHER_READ)
    }

    pub fn is_executable(&self, uid: UserId, gid: GroupId) -> bool {
        (uid == self.uid && self.mode.contains(FileMode::USER_READ))
            || (gid == self.gid && self.mode.contains(FileMode::GROUP_READ))
            || self.mode.contains(FileMode::OTHER_READ)
    }
}

impl AccessToken {
    /// Creates an "authorized" [AccessToken].
    ///
    /// # Safety
    ///
    /// Unsafe: allows for unchecked authorization of any node actions.
    pub const unsafe fn authorized() -> Self {
        Self(PhantomData)
    }

    #[cfg(test)]
    pub const fn test_authorized() -> Self {
        unsafe { Self::authorized() }
    }
}

impl Add for AccessToken {
    type Output = Self;

    fn add(self, _rhs: Self) -> Self::Output {
        self
    }
}

impl AddAssign for AccessToken {
    fn add_assign(&mut self, _rhs: Self) {}
}

#[cfg(test)]
mod tests {
    use yggdrasil_abi::io::{FileMode, GroupId, UserId};

    use crate::vfs::Metadata;

    #[test]
    fn access_check() {
        let m = unsafe {
            Metadata {
                uid: UserId::from_raw(1),
                gid: GroupId::from_raw(2),
                ..Metadata::now_root(FileMode::new(0o644))
            }
        };
        unsafe {
            assert!(!m.is_writeable(UserId::from_raw(3), GroupId::from_raw(3)));
            assert!(!m.is_writeable(UserId::from_raw(2), GroupId::from_raw(2)));
            assert!(m.is_writeable(UserId::from_raw(0), GroupId::from_raw(1)));
            assert!(m.is_writeable(UserId::from_raw(1), GroupId::from_raw(2)));
        }
    }
}
