use serde::{Deserialize, Serialize};

#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum OperatorRole {
    ClusterAdmin,
    TenantAdmin,
    BucketAdmin,
    Auditor,
    ReadOnlyOperator,
    SecurityOfficer,
}

#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum OperatorAction {
    AdministerCluster,
    AdministerTenant,
    AdministerBucket,
    ReadDiagnostics,
    ReadAudit,
    ManageSecurity,
    ManageCredentials,
    SimulatePolicy,
}

#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RoleAssignment {
    pub principal_id: String,
    pub role: OperatorRole,
    pub scope: String,
}

impl RoleAssignment {
    pub fn new(
        principal_id: impl Into<String>,
        role: OperatorRole,
        scope: impl Into<String>,
    ) -> Self {
        Self {
            principal_id: principal_id.into(),
            role,
            scope: scope.into(),
        }
    }

    pub fn permits(&self, action: OperatorAction, resource: &str) -> bool {
        match self.role {
            OperatorRole::ClusterAdmin => self.scope_matches(resource),
            OperatorRole::TenantAdmin => {
                matches!(
                    action,
                    OperatorAction::AdministerTenant
                        | OperatorAction::ReadDiagnostics
                        | OperatorAction::ReadAudit
                        | OperatorAction::SimulatePolicy
                ) && self.scope_matches(resource)
            }
            OperatorRole::BucketAdmin => {
                matches!(
                    action,
                    OperatorAction::AdministerBucket
                        | OperatorAction::ReadDiagnostics
                        | OperatorAction::SimulatePolicy
                ) && self.scope_matches(resource)
            }
            OperatorRole::Auditor => {
                matches!(action, OperatorAction::ReadAudit) && self.scope_matches(resource)
            }
            OperatorRole::ReadOnlyOperator => {
                matches!(
                    action,
                    OperatorAction::ReadDiagnostics | OperatorAction::ReadAudit
                ) && self.scope_matches(resource)
            }
            OperatorRole::SecurityOfficer => {
                matches!(
                    action,
                    OperatorAction::ManageSecurity
                        | OperatorAction::ManageCredentials
                        | OperatorAction::SimulatePolicy
                        | OperatorAction::ReadAudit
                ) && self.scope_matches(resource)
            }
        }
    }

    fn scope_matches(&self, resource: &str) -> bool {
        self.scope == "*" || self.scope == resource || resource.starts_with(self.scope.as_str())
    }
}
