//! Permission Checker - Access control for federation operations
//!
//! Implements a simple role-based permission model with three granularity levels:
//! - **Federation level**: control who can manage stores, rebuild indexes
//! - **Store level**: control who can read/write specific stores
//! - **Type level**: control who can access specific entity/document types

use std::collections::HashMap;

use super::error::{FederationError, Result};
use super::types::StoreType;

/// Operation permission
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Permission {
    // Federation-level
    FederationAdmin,
    RegisterStore,
    UnregisterStore,
    RebuildIndex,

    // Store-level
    StoreRead,
    StoreWrite,
    StoreBatchWrite,

    // Transaction-level
    BeginTransaction,
    CommitTransaction,

    // Query-level
    CrossStoreQuery,
    SingleStoreQuery,
}

/// Access context — describes who is making the request and what they want
///
/// Used by both RBAC (PermissionChecker) and ABAC (RecordPermissionChecker).
#[derive(Debug, Clone)]
pub struct AccessContext {
    /// Caller identifier (user ID, API key, role name, etc.)
    pub caller: String,
    /// Roles assigned to the caller
    pub roles: Vec<String>,
    /// Target store alias (if applicable)
    pub store_alias: Option<String>,
    /// Target store type (if applicable)
    pub store_type: Option<StoreType>,
    /// System override flag — bypasses ProtectionLevel::System checks.
    /// Only set to `true` for internal system calls or maintenance scripts.
    /// External REST API requests MUST always set this to `false`.
    pub system_override: bool,
}

impl AccessContext {
    /// Create a simple context with a caller and roles
    pub fn new(caller: &str, roles: Vec<String>) -> Self {
        Self {
            caller: caller.to_string(),
            roles,
            store_alias: None,
            store_type: None,
            system_override: false,
        }
    }

    /// Set the target store
    pub fn with_store(mut self, alias: &str, store_type: StoreType) -> Self {
        self.store_alias = Some(alias.to_string());
        self.store_type = Some(store_type);
        self
    }
    
    /// Enable system override (for internal system operations only)
    pub fn with_system_override(mut self) -> Self {
        self.system_override = true;
        self
    }
}

/// Permission checker
pub struct PermissionChecker {
    /// Role → set of granted permissions
    role_permissions: HashMap<String, Vec<Permission>>,
    /// Whether permission checking is enabled (can be disabled for dev/test)
    enabled: bool,
}

impl PermissionChecker {
    /// Create a new permission checker with default role definitions
    pub fn new() -> Self {
        let mut role_permissions = HashMap::new();

        // Admin: everything
        role_permissions.insert(
            "admin".to_string(),
            vec![
                Permission::FederationAdmin,
                Permission::RegisterStore,
                Permission::UnregisterStore,
                Permission::RebuildIndex,
                Permission::StoreRead,
                Permission::StoreWrite,
                Permission::StoreBatchWrite,
                Permission::BeginTransaction,
                Permission::CommitTransaction,
                Permission::CrossStoreQuery,
                Permission::SingleStoreQuery,
            ],
        );

        // Reader: read-only
        role_permissions.insert(
            "reader".to_string(),
            vec![
                Permission::StoreRead,
                Permission::SingleStoreQuery,
                Permission::CrossStoreQuery,
            ],
        );

        // Writer: read + write
        role_permissions.insert(
            "writer".to_string(),
            vec![
                Permission::StoreRead,
                Permission::StoreWrite,
                Permission::StoreBatchWrite,
                Permission::SingleStoreQuery,
                Permission::CrossStoreQuery,
                Permission::BeginTransaction,
                Permission::CommitTransaction,
            ],
        );

        Self {
            role_permissions,
            enabled: true,
        }
    }

    /// Create a disabled (pass-through) checker
    pub fn disabled() -> Self {
        Self {
            role_permissions: HashMap::new(),
            enabled: false,
        }
    }

    /// Enable or disable permission checking
    pub fn set_enabled(&mut self, enabled: bool) {
        self.enabled = enabled;
    }

    /// Add or replace a role definition
    pub fn define_role(&mut self, role: &str, permissions: Vec<Permission>) {
        self.role_permissions.insert(role.to_string(), permissions);
    }

    /// Check whether the context has the required permission
    pub fn check(&self, ctx: &AccessContext, required: Permission) -> Result<()> {
        if !self.enabled {
            return Ok(());
        }

        for role in &ctx.roles {
            if let Some(perms) = self.role_permissions.get(role) {
                if perms.contains(&required) {
                    return Ok(());
                }
            }
        }

        Err(FederationError::PermissionDenied {
            caller: ctx.caller.clone(),
            required: format!("{:?}", required),
        })
    }

    /// Check multiple permissions (all must be granted)
    pub fn check_all(&self, ctx: &AccessContext, required: &[Permission]) -> Result<()> {
        for perm in required {
            self.check(ctx, *perm)?;
        }
        Ok(())
    }

    /// Check if context has at least one of the given permissions
    pub fn check_any(&self, ctx: &AccessContext, required: &[Permission]) -> Result<()> {
        if !self.enabled {
            return Ok(());
        }

        for perm in required {
            if self.check(ctx, *perm).is_ok() {
                return Ok(());
            }
        }

        Err(FederationError::PermissionDenied {
            caller: ctx.caller.clone(),
            required: format!("any of {:?}", required),
        })
    }
}

impl Default for PermissionChecker {
    fn default() -> Self {
        Self::new()
    }
}

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

    #[test]
    fn test_admin_has_all_permissions() {
        let checker = PermissionChecker::new();
        let ctx = AccessContext::new("admin_user", vec!["admin".to_string()]);

        assert!(checker.check(&ctx, Permission::FederationAdmin).is_ok());
        assert!(checker.check(&ctx, Permission::StoreRead).is_ok());
        assert!(checker.check(&ctx, Permission::StoreWrite).is_ok());
        assert!(checker.check(&ctx, Permission::CrossStoreQuery).is_ok());
    }

    #[test]
    fn test_reader_cannot_write() {
        let checker = PermissionChecker::new();
        let ctx = AccessContext::new("reader_user", vec!["reader".to_string()]);

        assert!(checker.check(&ctx, Permission::StoreRead).is_ok());
        assert!(checker.check(&ctx, Permission::StoreWrite).is_err());
        assert!(checker.check(&ctx, Permission::BeginTransaction).is_err());
    }

    #[test]
    fn test_disabled_checker_allows_everything() {
        let checker = PermissionChecker::disabled();
        let ctx = AccessContext::new("anyone", vec![]);

        assert!(checker.check(&ctx, Permission::FederationAdmin).is_ok());
        assert!(checker.check(&ctx, Permission::StoreWrite).is_ok());
    }

    #[test]
    fn test_custom_role() {
        let mut checker = PermissionChecker::new();
        checker.define_role("indexer", vec![Permission::RebuildIndex, Permission::StoreRead]);

        let ctx = AccessContext::new("indexer_bot", vec!["indexer".to_string()]);
        assert!(checker.check(&ctx, Permission::RebuildIndex).is_ok());
        assert!(checker.check(&ctx, Permission::StoreWrite).is_err());
    }

    #[test]
    fn test_check_any() {
        let checker = PermissionChecker::new();
        let ctx = AccessContext::new("reader_user", vec!["reader".to_string()]);

        assert!(checker
            .check_any(&ctx, &[Permission::StoreWrite, Permission::StoreRead])
            .is_ok());
    }
}
