use std::str::FromStr;

use serde::{Deserialize, Serialize};
use serde::de::{value, IntoDeserializer};

use proxmox::api::api;
use proxmox::api::schema::{
    ApiStringFormat, BooleanSchema, EnumEntry, Schema, StringSchema,
};
use proxmox::{constnamedbitmap, const_regex};

const_regex! {
    pub ACL_PATH_REGEX = concat!(r"^(?:/|", r"(?:/", PROXMOX_SAFE_ID_REGEX_STR!(), ")+", r")$");
}

// define Privilege bitfield

constnamedbitmap! {
    /// Contains a list of privilege name to privilege value mappings.
    ///
    /// The names are used when displaying/persisting privileges anywhere, the values are used to
    /// allow easy matching of privileges as bitflags.
    PRIVILEGES: u64 => {
        /// Sys.Audit allows knowing about the system and its status
        PRIV_SYS_AUDIT("Sys.Audit");
        /// Sys.Modify allows modifying system-level configuration
        PRIV_SYS_MODIFY("Sys.Modify");
        /// Sys.Modify allows to poweroff/reboot/.. the system
        PRIV_SYS_POWER_MANAGEMENT("Sys.PowerManagement");

        /// Datastore.Audit allows knowing about a datastore,
        /// including reading the configuration entry and listing its contents
        PRIV_DATASTORE_AUDIT("Datastore.Audit");
        /// Datastore.Allocate allows creating or deleting datastores
        PRIV_DATASTORE_ALLOCATE("Datastore.Allocate");
        /// Datastore.Modify allows modifying a datastore and its contents
        PRIV_DATASTORE_MODIFY("Datastore.Modify");
        /// Datastore.Read allows reading arbitrary backup contents
        PRIV_DATASTORE_READ("Datastore.Read");
        /// Allows verifying a datastore
        PRIV_DATASTORE_VERIFY("Datastore.Verify");

        /// Datastore.Backup allows Datastore.Read|Verify and creating new snapshots,
        /// but also requires backup ownership
        PRIV_DATASTORE_BACKUP("Datastore.Backup");
        /// Datastore.Prune allows deleting snapshots,
        /// but also requires backup ownership
        PRIV_DATASTORE_PRUNE("Datastore.Prune");

        /// Permissions.Modify allows modifying ACLs
        PRIV_PERMISSIONS_MODIFY("Permissions.Modify");

        /// Remote.Audit allows reading remote.cfg and sync.cfg entries
        PRIV_REMOTE_AUDIT("Remote.Audit");
        /// Remote.Modify allows modifying remote.cfg
        PRIV_REMOTE_MODIFY("Remote.Modify");
        /// Remote.Read allows reading data from a configured `Remote`
        PRIV_REMOTE_READ("Remote.Read");

        /// Sys.Console allows access to the system's console
        PRIV_SYS_CONSOLE("Sys.Console");

        /// Tape.Audit allows reading tape backup configuration and status
        PRIV_TAPE_AUDIT("Tape.Audit");
        /// Tape.Modify allows modifying tape backup configuration
        PRIV_TAPE_MODIFY("Tape.Modify");
        /// Tape.Write allows writing tape media
        PRIV_TAPE_WRITE("Tape.Write");
        /// Tape.Read allows reading tape backup configuration and media contents
        PRIV_TAPE_READ("Tape.Read");

        /// Realm.Allocate allows viewing, creating, modifying and deleting realms
        PRIV_REALM_ALLOCATE("Realm.Allocate");
    }
}

/// Admin always has all privileges. It can do everything except a few actions
/// which are limited to the 'root@pam` superuser
pub const ROLE_ADMIN: u64 = std::u64::MAX;

/// NoAccess can be used to remove privileges from specific (sub-)paths
pub const ROLE_NO_ACCESS: u64 = 0;

#[rustfmt::skip]
#[allow(clippy::identity_op)]
/// Audit can view configuration and status information, but not modify it.
pub const ROLE_AUDIT: u64 = 0
    | PRIV_SYS_AUDIT
    | PRIV_DATASTORE_AUDIT;

#[rustfmt::skip]
#[allow(clippy::identity_op)]
/// Datastore.Admin can do anything on the datastore.
pub const ROLE_DATASTORE_ADMIN: u64 = 0
    | PRIV_DATASTORE_AUDIT
    | PRIV_DATASTORE_MODIFY
    | PRIV_DATASTORE_READ
    | PRIV_DATASTORE_VERIFY
    | PRIV_DATASTORE_BACKUP
    | PRIV_DATASTORE_PRUNE;

#[rustfmt::skip]
#[allow(clippy::identity_op)]
/// Datastore.Reader can read/verify datastore content and do restore
pub const ROLE_DATASTORE_READER: u64 = 0
    | PRIV_DATASTORE_AUDIT
    | PRIV_DATASTORE_VERIFY
    | PRIV_DATASTORE_READ;

#[rustfmt::skip]
#[allow(clippy::identity_op)]
/// Datastore.Backup can do backup and restore, but no prune.
pub const ROLE_DATASTORE_BACKUP: u64 = 0
    | PRIV_DATASTORE_BACKUP;

#[rustfmt::skip]
#[allow(clippy::identity_op)]
/// Datastore.PowerUser can do backup, restore, and prune.
pub const ROLE_DATASTORE_POWERUSER: u64 = 0
    | PRIV_DATASTORE_PRUNE
    | PRIV_DATASTORE_BACKUP;

#[rustfmt::skip]
#[allow(clippy::identity_op)]
/// Datastore.Audit can audit the datastore.
pub const ROLE_DATASTORE_AUDIT: u64 = 0
    | PRIV_DATASTORE_AUDIT;

#[rustfmt::skip]
#[allow(clippy::identity_op)]
/// Remote.Audit can audit the remote
pub const ROLE_REMOTE_AUDIT: u64 = 0
    | PRIV_REMOTE_AUDIT;

#[rustfmt::skip]
#[allow(clippy::identity_op)]
/// Remote.Admin can do anything on the remote.
pub const ROLE_REMOTE_ADMIN: u64 = 0
    | PRIV_REMOTE_AUDIT
    | PRIV_REMOTE_MODIFY
    | PRIV_REMOTE_READ;

#[rustfmt::skip]
#[allow(clippy::identity_op)]
/// Remote.SyncOperator can do read and prune on the remote.
pub const ROLE_REMOTE_SYNC_OPERATOR: u64 = 0
    | PRIV_REMOTE_AUDIT
    | PRIV_REMOTE_READ;

#[rustfmt::skip]
#[allow(clippy::identity_op)]
/// Tape.Audit can audit the tape backup configuration and media content
pub const ROLE_TAPE_AUDIT: u64 = 0
    | PRIV_TAPE_AUDIT;

#[rustfmt::skip]
#[allow(clippy::identity_op)]
/// Tape.Admin can do anything on the tape backup
pub const ROLE_TAPE_ADMIN: u64 = 0
    | PRIV_TAPE_AUDIT
    | PRIV_TAPE_MODIFY
    | PRIV_TAPE_READ
    | PRIV_TAPE_WRITE;

#[rustfmt::skip]
#[allow(clippy::identity_op)]
/// Tape.Operator can do tape backup and restore (but no configuration changes)
pub const ROLE_TAPE_OPERATOR: u64 = 0
    | PRIV_TAPE_AUDIT
    | PRIV_TAPE_READ
    | PRIV_TAPE_WRITE;

#[rustfmt::skip]
#[allow(clippy::identity_op)]
/// Tape.Reader can do read and inspect tape content
pub const ROLE_TAPE_READER: u64 = 0
    | PRIV_TAPE_AUDIT
    | PRIV_TAPE_READ;

/// NoAccess can be used to remove privileges from specific (sub-)paths
pub const ROLE_NAME_NO_ACCESS: &str = "NoAccess";

#[api(
    type_text: "<role>",
)]
#[repr(u64)]
#[derive(Serialize, Deserialize)]
/// Enum representing roles via their [PRIVILEGES] combination.
///
/// Since privileges are implemented as bitflags, each unique combination of privileges maps to a
/// single, unique `u64` value that is used in this enum definition.
pub enum Role {
    /// Administrator
    Admin = ROLE_ADMIN,
    /// Auditor
    Audit = ROLE_AUDIT,
    /// Disable Access
    NoAccess = ROLE_NO_ACCESS,
    /// Datastore Administrator
    DatastoreAdmin = ROLE_DATASTORE_ADMIN,
    /// Datastore Reader (inspect datastore content and do restores)
    DatastoreReader = ROLE_DATASTORE_READER,
    /// Datastore Backup (backup and restore owned backups)
    DatastoreBackup = ROLE_DATASTORE_BACKUP,
    /// Datastore PowerUser (backup, restore and prune owned backup)
    DatastorePowerUser = ROLE_DATASTORE_POWERUSER,
    /// Datastore Auditor
    DatastoreAudit = ROLE_DATASTORE_AUDIT,
    /// Remote Auditor
    RemoteAudit = ROLE_REMOTE_AUDIT,
    /// Remote Administrator
    RemoteAdmin = ROLE_REMOTE_ADMIN,
    /// Syncronisation Opertator
    RemoteSyncOperator = ROLE_REMOTE_SYNC_OPERATOR,
    /// Tape Auditor
    TapeAudit = ROLE_TAPE_AUDIT,
    /// Tape Administrator
    TapeAdmin = ROLE_TAPE_ADMIN,
    /// Tape Operator
    TapeOperator = ROLE_TAPE_OPERATOR,
    /// Tape Reader
    TapeReader = ROLE_TAPE_READER,
}


impl FromStr for Role {
    type Err = value::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Self::deserialize(s.into_deserializer())
    }
}

pub const ACL_PATH_FORMAT: ApiStringFormat =
    ApiStringFormat::Pattern(&ACL_PATH_REGEX);

pub const ACL_PATH_SCHEMA: Schema = StringSchema::new(
    "Access control path.")
    .format(&ACL_PATH_FORMAT)
    .min_length(1)
    .max_length(128)
    .schema();

pub const ACL_PROPAGATE_SCHEMA: Schema = BooleanSchema::new(
    "Allow to propagate (inherit) permissions.")
    .default(true)
    .schema();

pub const ACL_UGID_TYPE_SCHEMA: Schema = StringSchema::new(
    "Type of 'ugid' property.")
    .format(&ApiStringFormat::Enum(&[
        EnumEntry::new("user", "User"),
        EnumEntry::new("group", "Group")]))
    .schema();

#[api(
    properties: {
        propagate: {
            schema: ACL_PROPAGATE_SCHEMA,
        },
	path: {
            schema: ACL_PATH_SCHEMA,
        },
        ugid_type: {
            schema: ACL_UGID_TYPE_SCHEMA,
        },
	ugid: {
            type: String,
            description: "User or Group ID.",
        },
	roleid: {
            type: Role,
        }
    }
)]
#[derive(Serialize, Deserialize)]
/// ACL list entry.
pub struct AclListItem {
    pub path: String,
    pub ugid: String,
    pub ugid_type: String,
    pub propagate: bool,
    pub roleid: String,
}
