use std::fmt;

use crate::error::{Error, Result};
use crate::types::Permission;

const SUBSCRIBE_BIT: u8 = 0b0001;
const PUBLISH_BIT: u8 = 0b0010;
const QUERY_BIT: u8 = 0b0100;
const FETCH_BIT: u8 = 0b1000;

/// A compact set of permissions stored as a bitmask.
///
/// With only three `Permission` variants, a single `u8` is sufficient.
/// `Copy`, no allocation, O(1) membership checks.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PermissionSet(u8);

impl PermissionSet {
    /// An empty permission set.
    pub const EMPTY: Self = Self(0);

    /// Build a set from a slice of permissions.
    pub fn from_slice(perms: &[Permission]) -> Self {
        let mut bits = 0u8;
        for perm in perms {
            bits |= bit_for(*perm);
        }
        Self(bits)
    }

    /// Returns `true` if the set contains the given permission.
    pub fn contains(self, perm: Permission) -> bool {
        self.0 & bit_for(perm) != 0
    }

    /// Returns `Ok(())` if the set contains the required permission,
    /// or `Err(Error::PermissionDenied)` otherwise.
    pub fn check(self, perm: Permission) -> Result<()> {
        if self.contains(perm) {
            Ok(())
        } else {
            Err(Error::PermissionDenied)
        }
    }
}

impl fmt::Display for PermissionSet {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let mut first = true;
        f.write_str("{")?;
        for perm in [
            Permission::Subscribe,
            Permission::Publish,
            Permission::Query,
            Permission::Fetch,
        ] {
            if self.contains(perm) {
                if !first {
                    f.write_str(", ")?;
                }
                first = false;
                fmt::Debug::fmt(&perm, f)?;
            }
        }
        f.write_str("}")
    }
}

fn bit_for(perm: Permission) -> u8 {
    match perm {
        Permission::Subscribe => SUBSCRIBE_BIT,
        Permission::Publish => PUBLISH_BIT,
        Permission::Query => QUERY_BIT,
        Permission::Fetch => FETCH_BIT,
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn empty_set_contains_nothing() {
        let set = PermissionSet::EMPTY;
        assert!(!set.contains(Permission::Subscribe));
        assert!(!set.contains(Permission::Publish));
        assert!(!set.contains(Permission::Query));
    }

    #[test]
    fn from_slice_all_permissions() {
        let set = PermissionSet::from_slice(&[
            Permission::Subscribe,
            Permission::Publish,
            Permission::Query,
        ]);
        assert!(set.contains(Permission::Subscribe));
        assert!(set.contains(Permission::Publish));
        assert!(set.contains(Permission::Query));
    }

    #[test]
    fn from_slice_subset() {
        let set = PermissionSet::from_slice(&[Permission::Publish, Permission::Query]);
        assert!(!set.contains(Permission::Subscribe));
        assert!(set.contains(Permission::Publish));
        assert!(set.contains(Permission::Query));
    }

    #[test]
    fn check_granted_returns_ok() {
        let set = PermissionSet::from_slice(&[Permission::Subscribe]);
        assert!(set.check(Permission::Subscribe).is_ok());
    }

    #[test]
    fn check_denied_returns_permission_denied() {
        let set = PermissionSet::from_slice(&[Permission::Subscribe]);
        let err = set.check(Permission::Publish).unwrap_err();
        assert!(matches!(err, Error::PermissionDenied));
    }

    #[test]
    fn from_empty_slice() {
        let set = PermissionSet::from_slice(&[]);
        assert_eq!(set, PermissionSet::EMPTY);
    }

    #[test]
    fn duplicate_permissions_are_idempotent() {
        let set = PermissionSet::from_slice(&[Permission::Publish, Permission::Publish]);
        assert!(set.contains(Permission::Publish));
        assert!(!set.contains(Permission::Subscribe));
    }

    #[test]
    fn display_empty() {
        assert_eq!(PermissionSet::EMPTY.to_string(), "{}");
    }

    #[test]
    fn display_single() {
        let set = PermissionSet::from_slice(&[Permission::Publish]);
        assert_eq!(set.to_string(), "{Publish}");
    }

    #[test]
    fn display_all() {
        let set = PermissionSet::from_slice(&[
            Permission::Subscribe,
            Permission::Publish,
            Permission::Query,
        ]);
        assert_eq!(set.to_string(), "{Subscribe, Publish, Query}");
    }
}
