//! Role storage
//!
//! Stores and retrieves roles and their permissions.

use std::sync::Arc;

use iam_types::{builtin_roles, Error, IamError, Result, Role, Scope};

use crate::backend::{Backend, CasResult, JsonStore, StorageBackend};

/// Key prefixes for role storage
mod keys {
    /// Primary key: by name
    /// Format: iam/roles/{name}
    pub const ROLES: &str = "iam/roles/";

    /// Secondary index: by scope
    /// Format: iam/roles/by-scope/{scope}/{name}
    pub const BY_SCOPE: &str = "iam/roles/by-scope/";

    /// Builtin roles marker
    /// Format: iam/roles/builtin/{name}
    pub const BUILTIN: &str = "iam/roles/builtin/";
}

/// Store for roles
pub struct RoleStore {
    backend: Arc<Backend>,
}

impl JsonStore for RoleStore {
    fn backend(&self) -> &Backend {
        &self.backend
    }
}

impl RoleStore {
    /// Create a new role store
    pub fn new(backend: Arc<Backend>) -> Self {
        Self { backend }
    }

    /// Initialize builtin roles
    pub async fn init_builtin_roles(&self) -> Result<()> {
        for role in builtin_roles::all() {
            // Check if already exists
            let key = format!("{}{}", keys::ROLES, role.name);
            if self.backend.get(key.as_bytes()).await?.is_none() {
                // Create builtin role
                self.create_internal(&role).await?;
            }
        }
        Ok(())
    }

    /// Create a new role
    pub async fn create(&self, role: &Role) -> Result<u64> {
        if role.builtin {
            return Err(Error::Iam(IamError::CannotModifyBuiltinRole(
                role.name.clone(),
            )));
        }
        self.create_internal(role).await
    }

    /// Create a role (internal use, bypasses builtin check)
    /// Used by init_builtin_roles and tests
    pub async fn create_internal(&self, role: &Role) -> Result<u64> {
        let key = format!("{}{}", keys::ROLES, role.name);

        let bytes = serde_json::to_vec(role).map_err(|e| Error::Serialization(e.to_string()))?;

        match self.backend.cas(key.as_bytes(), 0, &bytes).await? {
            CasResult::Success(version) => {
                // Create secondary indexes
                self.create_indexes(role).await?;
                Ok(version)
            }
            CasResult::Conflict { .. } => {
                Err(Error::Iam(IamError::RoleAlreadyExists(role.name.clone())))
            }
            CasResult::NotFound => Err(Error::Internal("Unexpected CAS result".into())),
        }
    }

    /// Get a role by name
    pub async fn get(&self, name: &str) -> Result<Option<Role>> {
        let key = format!("{}{}", keys::ROLES, name);
        match self.get_json::<Role>(key.as_bytes()).await? {
            Some((role, _)) => Ok(Some(role)),
            None => Ok(None),
        }
    }

    /// Get a role by reference (e.g., "roles/ProjectAdmin")
    pub async fn get_by_ref(&self, role_ref: &str) -> Result<Option<Role>> {
        let name = role_ref.strip_prefix("roles/").unwrap_or(role_ref);
        self.get(name).await
    }

    /// Get a role with version
    pub async fn get_with_version(&self, name: &str) -> Result<Option<(Role, u64)>> {
        let key = format!("{}{}", keys::ROLES, name);
        self.get_json::<Role>(key.as_bytes()).await
    }

    /// Update a role
    pub async fn update(&self, role: &Role, expected_version: u64) -> Result<u64> {
        // Check if trying to modify builtin role
        let existing = self.get(&role.name).await?;
        if let Some(existing_role) = existing.as_ref() {
            if existing_role.builtin {
                return Err(Error::Iam(IamError::CannotModifyBuiltinRole(
                    role.name.clone(),
                )));
            }
        }

        let key = format!("{}{}", keys::ROLES, role.name);
        let bytes = serde_json::to_vec(role).map_err(|e| Error::Serialization(e.to_string()))?;

        match self
            .backend
            .cas(key.as_bytes(), expected_version, &bytes)
            .await?
        {
            CasResult::Success(version) => {
                // Update indexes
                if let Some(existing_role) = existing.as_ref() {
                    self.delete_indexes(existing_role).await?;
                }
                self.create_indexes(role).await?;
                Ok(version)
            }
            CasResult::Conflict { expected, actual } => {
                Err(Error::Storage(iam_types::StorageError::CasConflict {
                    expected,
                    actual,
                }))
            }
            CasResult::NotFound => Err(Error::Iam(IamError::RoleNotFound(role.name.clone()))),
        }
    }

    /// Delete a role
    pub async fn delete(&self, name: &str) -> Result<bool> {
        // Check if builtin
        if let Some(role) = self.get(name).await? {
            if role.builtin {
                return Err(Error::Iam(IamError::CannotModifyBuiltinRole(name.into())));
            }

            let key = format!("{}{}", keys::ROLES, name);
            let deleted = self.backend.delete(key.as_bytes()).await?;

            if deleted {
                self.delete_indexes(&role).await?;
            }

            Ok(deleted)
        } else {
            Ok(false)
        }
    }

    /// List all roles
    pub async fn list(&self) -> Result<Vec<Role>> {
        let pairs = self
            .backend
            .scan_prefix(keys::ROLES.as_bytes(), 10000)
            .await?;

        let mut roles = Vec::new();
        for pair in pairs {
            // Skip index keys
            let key_str = String::from_utf8_lossy(&pair.key);
            if key_str.contains("/by-scope/") || key_str.contains("/builtin/") {
                continue;
            }

            let role: Role = serde_json::from_slice(&pair.value)
                .map_err(|e| Error::Serialization(e.to_string()))?;
            roles.push(role);
        }

        Ok(roles)
    }

    /// List roles by scope
    pub async fn list_by_scope(&self, scope: &Scope) -> Result<Vec<Role>> {
        let prefix = format!("{}{}/", keys::BY_SCOPE, scope.to_key());
        let pairs = self.backend.scan_prefix(prefix.as_bytes(), 10000).await?;

        let mut roles = Vec::new();
        for pair in pairs {
            // Index stores role name
            let name = String::from_utf8_lossy(&pair.value);
            if let Some(role) = self.get(&name).await? {
                roles.push(role);
            }
        }

        Ok(roles)
    }

    /// List builtin roles
    pub async fn list_builtin(&self) -> Result<Vec<Role>> {
        let pairs = self
            .backend
            .scan_prefix(keys::BUILTIN.as_bytes(), 100)
            .await?;

        let mut roles = Vec::new();
        for pair in pairs {
            let name = String::from_utf8_lossy(&pair.value);
            if let Some(role) = self.get(&name).await? {
                roles.push(role);
            }
        }

        Ok(roles)
    }

    /// List custom (non-builtin) roles
    pub async fn list_custom(&self) -> Result<Vec<Role>> {
        let all_roles = self.list().await?;
        Ok(all_roles.into_iter().filter(|r| !r.builtin).collect())
    }

    /// Check if a role exists
    pub async fn exists(&self, name: &str) -> Result<bool> {
        let key = format!("{}{}", keys::ROLES, name);
        Ok(self.backend.get(key.as_bytes()).await?.is_some())
    }

    // Helper methods

    async fn create_indexes(&self, role: &Role) -> Result<()> {
        // Scope index
        let scope_key = format!("{}{}/{}", keys::BY_SCOPE, role.scope.to_key(), role.name);
        self.backend
            .put(scope_key.as_bytes(), role.name.as_bytes())
            .await?;

        // Builtin marker
        if role.builtin {
            let builtin_key = format!("{}{}", keys::BUILTIN, role.name);
            self.backend
                .put(builtin_key.as_bytes(), role.name.as_bytes())
                .await?;
        }

        Ok(())
    }

    async fn delete_indexes(&self, role: &Role) -> Result<()> {
        // Scope index
        let scope_key = format!("{}{}/{}", keys::BY_SCOPE, role.scope.to_key(), role.name);
        self.backend.delete(scope_key.as_bytes()).await?;

        // Builtin marker (shouldn't delete builtin roles, but just in case)
        if role.builtin {
            let builtin_key = format!("{}{}", keys::BUILTIN, role.name);
            self.backend.delete(builtin_key.as_bytes()).await?;
        }

        Ok(())
    }
}

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

    fn test_backend() -> Arc<Backend> {
        Arc::new(Backend::memory())
    }

    #[tokio::test]
    async fn test_role_crud() {
        let store = RoleStore::new(test_backend());

        let role = Role::new(
            "CustomViewer",
            Scope::project("*", "*"),
            vec![Permission::new("*:read", "project/${project}/*")],
        )
        .with_display_name("Custom Viewer")
        .with_description("Read-only access");

        // Create
        let version = store.create(&role).await.unwrap();
        assert!(version > 0);

        // Get
        let fetched = store.get("CustomViewer").await.unwrap();
        assert!(fetched.is_some());
        assert_eq!(fetched.unwrap().display_name, "Custom Viewer");

        // Get by ref
        let fetched = store.get_by_ref("roles/CustomViewer").await.unwrap();
        assert!(fetched.is_some());

        // Delete
        let deleted = store.delete("CustomViewer").await.unwrap();
        assert!(deleted);

        // Verify deleted
        let fetched = store.get("CustomViewer").await.unwrap();
        assert!(fetched.is_none());
    }

    #[tokio::test]
    async fn test_builtin_roles() {
        let store = RoleStore::new(test_backend());

        // Initialize builtin roles
        store.init_builtin_roles().await.unwrap();

        // Verify they exist
        let admin = store.get("SystemAdmin").await.unwrap();
        assert!(admin.is_some());
        assert!(admin.unwrap().builtin);

        // Try to delete builtin role
        let result = store.delete("SystemAdmin").await;
        assert!(result.is_err());

        // Try to create role marked as builtin
        let fake_builtin =
            Role::builtin("FakeBuiltin", Scope::System, vec![Permission::wildcard()]);
        let result = store.create(&fake_builtin).await;
        assert!(result.is_err());
    }

    #[tokio::test]
    async fn test_list_roles() {
        let store = RoleStore::new(test_backend());

        // Init builtin
        store.init_builtin_roles().await.unwrap();

        // Create custom role
        let custom = Role::new(
            "MyRole",
            Scope::project("*", "*"),
            vec![Permission::new("compute:*", "*")],
        );
        store.create(&custom).await.unwrap();

        // List all
        let all = store.list().await.unwrap();
        assert!(all.len() > 1);

        // List custom only
        let custom_roles = store.list_custom().await.unwrap();
        assert_eq!(custom_roles.len(), 1);
        assert_eq!(custom_roles[0].name, "MyRole");

        // List builtin only
        let builtin_roles = store.list_builtin().await.unwrap();
        assert!(!builtin_roles.is_empty());
        assert!(builtin_roles.iter().all(|r| r.builtin));
    }

    #[tokio::test]
    async fn test_list_by_scope() {
        let store = RoleStore::new(test_backend());

        store.init_builtin_roles().await.unwrap();

        // List system scope roles
        let system_roles = store.list_by_scope(&Scope::System).await.unwrap();
        assert!(!system_roles.is_empty());

        // SystemAdmin should be in system scope
        assert!(system_roles.iter().any(|r| r.name == "SystemAdmin"));
    }

    #[tokio::test]
    async fn test_duplicate_role() {
        let store = RoleStore::new(test_backend());

        let role = Role::new("TestRole", Scope::System, vec![]);
        store.create(&role).await.unwrap();

        // Try to create again
        let result = store.create(&role).await;
        assert!(result.is_err());
    }
}
