//! Role and Permission types for IAM
//!
//! Roles define sets of permissions that can be assigned to principals.

use serde::{Deserialize, Serialize};

use crate::condition::Condition;
use crate::scope::Scope;

/// A role that groups permissions together
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Role {
    /// Role name (e.g., "ProjectAdmin", "ReadOnly")
    pub name: String,

    /// Display name for UI
    pub display_name: String,

    /// Description of the role
    pub description: String,

    /// Scope where this role can be applied
    pub scope: Scope,

    /// Permissions granted by this role
    pub permissions: Vec<Permission>,

    /// Whether this is a builtin system role
    pub builtin: bool,

    /// Creation timestamp (TSO format)
    pub created_at: u64,

    /// Last update timestamp (TSO format)
    pub updated_at: u64,
}

impl Role {
    /// Create a new custom role
    pub fn new(name: impl Into<String>, scope: Scope, permissions: Vec<Permission>) -> Self {
        Self {
            name: name.into(),
            display_name: String::new(),
            description: String::new(),
            scope,
            permissions,
            builtin: false,
            created_at: 0,
            updated_at: 0,
        }
    }

    /// Create a builtin role
    pub fn builtin(name: impl Into<String>, scope: Scope, permissions: Vec<Permission>) -> Self {
        let name = name.into();
        Self {
            display_name: name.clone(),
            name,
            description: String::new(),
            scope,
            permissions,
            builtin: true,
            created_at: 0,
            updated_at: 0,
        }
    }

    /// Set display name
    pub fn with_display_name(mut self, display_name: impl Into<String>) -> Self {
        self.display_name = display_name.into();
        self
    }

    /// Set description
    pub fn with_description(mut self, description: impl Into<String>) -> Self {
        self.description = description.into();
        self
    }

    /// Get the role reference string (e.g., "roles/ProjectAdmin")
    pub fn to_ref(&self) -> String {
        format!("roles/{}", self.name)
    }
}

/// A permission within a role
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Permission {
    /// Action pattern (e.g., "compute:instances:create", "compute:*:read")
    pub action: String,

    /// Resource pattern (e.g., "org/*/project/*/instance/*")
    pub resource_pattern: String,

    /// Optional condition for this permission (ABAC)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub condition: Option<Condition>,
}

impl Permission {
    /// Create a new permission
    pub fn new(action: impl Into<String>, resource_pattern: impl Into<String>) -> Self {
        Self {
            action: action.into(),
            resource_pattern: resource_pattern.into(),
            condition: None,
        }
    }

    /// Create a permission with a condition
    pub fn with_condition(mut self, condition: Condition) -> Self {
        self.condition = Some(condition);
        self
    }

    /// Create a wildcard permission (all actions on all resources)
    pub fn wildcard() -> Self {
        Self::new("*", "*")
    }

    /// Check if this is a wildcard permission
    pub fn is_wildcard(&self) -> bool {
        self.action == "*" && self.resource_pattern == "*"
    }
}

/// Builtin roles
pub mod builtin {
    use super::*;

    /// System administrator - full access to everything
    pub fn system_admin() -> Role {
        Role::builtin("SystemAdmin", Scope::System, vec![Permission::wildcard()])
            .with_display_name("System Administrator")
            .with_description("Full access to all resources in the system")
    }

    /// Organization administrator
    pub fn org_admin() -> Role {
        Role::builtin(
            "OrgAdmin",
            Scope::org("*"),
            vec![Permission::new("*", "org/${resource.org_id}/*")],
        )
        .with_display_name("Organization Administrator")
        .with_description("Full access to all resources within the organization")
    }

    /// Project administrator
    pub fn project_admin() -> Role {
        Role::builtin(
            "ProjectAdmin",
            Scope::project("*", "*"),
            vec![Permission::new(
                "*",
                "org/${resource.org_id}/project/${resource.project_id}/*",
            )],
        )
        .with_display_name("Project Administrator")
        .with_description("Full access to all resources within the project")
    }

    /// Project member - can manage own resources
    pub fn project_member() -> Role {
        Role::builtin(
            "ProjectMember",
            Scope::project("*", "*"),
            vec![
                // Full access to own resources
                Permission::new(
                    "compute:instances:*",
                    "org/${resource.org_id}/project/${resource.project_id}/instance/*",
                )
                .with_condition(Condition::string_equals(
                    "resource.owner",
                    "${principal.id}",
                )),
                Permission::new(
                    "compute:images:*",
                    "org/${resource.org_id}/project/${resource.project_id}/image/*",
                ),
                // Read access to all project resources
                Permission::new(
                    "*:read",
                    "org/${resource.org_id}/project/${resource.project_id}/*",
                ),
                Permission::new(
                    "*:list",
                    "org/${resource.org_id}/project/${resource.project_id}/*",
                ),
            ],
        )
        .with_display_name("Project Member")
        .with_description("Can manage own resources and read all project resources")
    }

    /// Read-only access
    pub fn read_only() -> Role {
        Role::builtin(
            "ReadOnly",
            Scope::project("*", "*"),
            vec![
                Permission::new(
                    "*:read",
                    "org/${resource.org_id}/project/${resource.project_id}/*",
                ),
                Permission::new(
                    "*:list",
                    "org/${resource.org_id}/project/${resource.project_id}/*",
                ),
                Permission::new(
                    "*:get",
                    "org/${resource.org_id}/project/${resource.project_id}/*",
                ),
            ],
        )
        .with_display_name("Read Only")
        .with_description("Read-only access to all project resources")
    }

    /// Compute agent service role
    pub fn compute_agent() -> Role {
        Role::builtin(
            "ServiceRole-ComputeAgent",
            Scope::System,
            vec![Permission::new("compute:instances:*", "*").with_condition(
                Condition::string_equals("resource.node", "${principal.node_id}"),
            )],
        )
        .with_display_name("Compute Agent")
        .with_description("Service role for compute agents - can manage instances on their node")
    }

    /// Storage agent service role
    pub fn storage_agent() -> Role {
        Role::builtin(
            "ServiceRole-StorageAgent",
            Scope::System,
            vec![Permission::new("storage:volumes:*", "*").with_condition(
                Condition::string_equals("resource.node", "${principal.node_id}"),
            )],
        )
        .with_display_name("Storage Agent")
        .with_description("Service role for storage agents - can manage volumes on their node")
    }

    /// Get all builtin roles
    pub fn all() -> Vec<Role> {
        vec![
            system_admin(),
            org_admin(),
            project_admin(),
            project_member(),
            read_only(),
            compute_agent(),
            storage_agent(),
        ]
    }
}

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

    #[test]
    fn test_role_creation() {
        let role = Role::new(
            "CustomRole",
            Scope::project("my-project", "my-org"),
            vec![Permission::new(
                "compute:instances:create",
                "project/my-project/instance/*",
            )],
        );

        assert_eq!(role.name, "CustomRole");
        assert!(!role.builtin);
        assert_eq!(role.permissions.len(), 1);
    }

    #[test]
    fn test_role_ref() {
        let role = Role::new("TestRole", Scope::System, vec![]);
        assert_eq!(role.to_ref(), "roles/TestRole");
    }

    #[test]
    fn test_permission_with_condition() {
        let perm =
            Permission::new("compute:instances:delete", "project/*/instance/*").with_condition(
                Condition::string_equals("resource.owner", "${principal.id}"),
            );

        assert!(perm.condition.is_some());
    }

    #[test]
    fn test_wildcard_permission() {
        let perm = Permission::wildcard();
        assert!(perm.is_wildcard());

        let perm = Permission::new("compute:*", "*");
        assert!(!perm.is_wildcard());
    }

    #[test]
    fn test_builtin_roles() {
        let roles = builtin::all();
        assert!(roles.len() >= 5);

        for role in &roles {
            assert!(role.builtin);
        }

        let admin = builtin::system_admin();
        assert!(admin.permissions[0].is_wildcard());
    }

    #[test]
    fn test_role_serialization() {
        let role = Role::new(
            "TestRole",
            Scope::project("proj-1", "org-1"),
            vec![Permission::new("compute:instances:*", "project/proj-1/*")],
        )
        .with_display_name("Test Role")
        .with_description("A test role");

        let json = serde_json::to_string(&role).unwrap();
        let parsed: Role = serde_json::from_str(&json).unwrap();

        assert_eq!(parsed.name, "TestRole");
        assert_eq!(parsed.display_name, "Test Role");
    }
}
