use anyhow::Result;
use chrono::{DateTime, Duration, Utc};
use rustelo_core_lib::auth::{
    AccessContext, AccessResult, Permission, RBACConfig, ResourceType, Role, User,
};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
use uuid::Uuid;

use super::repository::{AccessAuditEntry, PermissionCacheEntry, RBACRepository};

/// RBAC Service for managing access control
#[derive(Clone)]
pub struct RBACService {
    repository: Arc<RBACRepository>,
    config_cache: Arc<RwLock<HashMap<String, RBACConfig>>>,
    cache_ttl: Duration,
}

impl RBACService {
    pub fn new(repository: Arc<RBACRepository>) -> Self {
        Self {
            repository,
            config_cache: Arc::new(RwLock::new(HashMap::new())),
            cache_ttl: Duration::minutes(5),
        }
    }

    /// Check if a user has access to a resource
    pub async fn check_access(&self, context: &AccessContext) -> Result<AccessResult> {
        // Try to get from cache first
        if let Some(user) = &context.user {
            if let Some(cached_result) = self.get_cached_access(user.id, context).await? {
                return Ok(cached_result);
            }
        }

        // Evaluate access rules
        let result = self.evaluate_access_rules(context).await?;

        // Cache the result
        if let Some(user) = &context.user {
            self.cache_access_result(user.id, context, &result).await?;
        }

        // Log the access attempt
        self.log_access_attempt(context, &result).await?;

        Ok(result)
    }

    /// Evaluate access rules for a given context
    async fn evaluate_access_rules(&self, context: &AccessContext) -> Result<AccessResult> {
        let Some(user) = &context.user else {
            return Ok(AccessResult::Deny);
        };

        // Load user's categories and tags
        let user_categories = self.repository.get_user_categories(user.id).await?;
        let user_tags = self.repository.get_user_tags(user.id).await?;

        // Create enriched user with categories and tags
        let mut enriched_user = user.clone();
        enriched_user.profile.categories = user_categories;
        enriched_user.profile.tags = user_tags;

        // Get applicable access rules
        let rules = self
            .repository
            .get_access_rules_for_resource(
                &context.resource_type.to_string(),
                &context.resource_name,
                &context.action,
            )
            .await?;

        // If no rules found, check default permissions
        if rules.is_empty() {
            return self
                .check_default_permissions(context, &enriched_user)
                .await;
        }

        // Evaluate rules in priority order
        for rule in rules {
            // Load rule requirements
            let required_roles = self.repository.get_rule_required_roles(rule.id).await?;
            let required_permissions = self
                .repository
                .get_rule_required_permissions(rule.id)
                .await?;
            let required_categories = self
                .repository
                .get_rule_required_categories(rule.id)
                .await?;
            let denied_categories = self.repository.get_rule_denied_categories(rule.id).await?;
            let required_tags = self.repository.get_rule_required_tags(rule.id).await?;
            let denied_tags = self.repository.get_rule_denied_tags(rule.id).await?;

            // Check deny conditions first
            if enriched_user.is_denied_by_categories(&denied_categories)
                || enriched_user.is_denied_by_tags(&denied_tags)
            {
                continue; // Skip this rule
            }

            // Check role requirements
            if !required_roles.is_empty() {
                let has_required_role =
                    required_roles
                        .iter()
                        .any(|role_name| match role_name.as_str() {
                            "admin" => enriched_user.has_role(&Role::Admin),
                            "moderator" => enriched_user.has_role(&Role::Moderator),
                            "user" => enriched_user.has_role(&Role::User),
                            "guest" => enriched_user.has_role(&Role::Guest),
                            _ => enriched_user.has_role(&Role::Custom(role_name.clone())),
                        });
                if !has_required_role {
                    continue;
                }
            }

            // Check permission requirements
            if !required_permissions.is_empty() {
                let has_required_permission = required_permissions.iter().any(|perm_name| {
                    let permission = self.parse_permission(perm_name);
                    enriched_user.has_permission(&permission)
                });
                if !has_required_permission {
                    continue;
                }
            }

            // Check category requirements
            if !required_categories.is_empty()
                && !enriched_user.has_any_category(&required_categories)
            {
                continue;
            }

            // Check tag requirements
            if !required_tags.is_empty() && !enriched_user.has_any_tag(&required_tags) {
                continue;
            }

            // If we reach here, all conditions are met
            return Ok(AccessResult::Allow);
        }

        // No rules matched, deny access
        Ok(AccessResult::Deny)
    }

    /// Check default permissions when no specific rules are found
    async fn check_default_permissions(
        &self,
        context: &AccessContext,
        user: &User,
    ) -> Result<AccessResult> {
        let config = self.get_rbac_config("default").await?;

        if let Some(default_perms) = config
            .default_permissions
            .get(&context.resource_type.to_string())
        {
            for permission in default_perms {
                if user.has_permission(permission) {
                    return Ok(AccessResult::Allow);
                }
            }
        }

        // Check if user has admin role (admin can access everything by default)
        if user.is_admin() {
            return Ok(AccessResult::Allow);
        }

        Ok(AccessResult::Deny)
    }

    /// Get cached access result
    async fn get_cached_access(
        &self,
        user_id: Uuid,
        context: &AccessContext,
    ) -> Result<Option<AccessResult>> {
        let cache_key = self.generate_cache_key(user_id, context);

        if let Some(entry) = self
            .repository
            .get_cached_permission(user_id, &cache_key)
            .await?
        {
            return Ok(Some(match entry.access_result.as_str() {
                "allow" => AccessResult::Allow,
                "deny" => AccessResult::Deny,
                "require_additional_auth" => AccessResult::RequireAdditionalAuth,
                _ => AccessResult::Deny,
            }));
        }

        Ok(None)
    }

    /// Cache access result
    async fn cache_access_result(
        &self,
        user_id: Uuid,
        context: &AccessContext,
        result: &AccessResult,
    ) -> Result<()> {
        let cache_key = self.generate_cache_key(user_id, context);
        let expires_at = Utc::now() + self.cache_ttl;

        let entry = PermissionCacheEntry {
            user_id,
            resource_type: context.resource_type.to_string(),
            resource_name: context.resource_name.clone(),
            action: context.action.clone(),
            access_result: match result {
                AccessResult::Allow => "allow".to_string(),
                AccessResult::Deny => "deny".to_string(),
                AccessResult::RequireAdditionalAuth => "require_additional_auth".to_string(),
            },
            cache_key,
            expires_at,
        };

        self.repository.cache_permission(&entry).await?;
        Ok(())
    }

    /// Generate cache key for permission
    fn generate_cache_key(&self, user_id: Uuid, context: &AccessContext) -> String {
        format!(
            "{}:{}:{}:{}",
            user_id,
            context.resource_type.to_string(),
            context.resource_name,
            context.action
        )
    }

    /// Log access attempt
    async fn log_access_attempt(
        &self,
        context: &AccessContext,
        result: &AccessResult,
    ) -> Result<()> {
        let entry = AccessAuditEntry {
            user_id: context.user.as_ref().map(|u| u.id),
            resource_type: context.resource_type.to_string(),
            resource_name: context.resource_name.clone(),
            action: context.action.clone(),
            access_result: match result {
                AccessResult::Allow => "allow".to_string(),
                AccessResult::Deny => "deny".to_string(),
                AccessResult::RequireAdditionalAuth => "require_additional_auth".to_string(),
            },
            rule_id: None,    // TODO: Track which rule was used
            ip_address: None, // TODO: Extract from context
            user_agent: None, // TODO: Extract from context
            session_id: None, // TODO: Extract from context
            additional_context: Some(serde_json::to_value(&context.additional_context)?),
        };

        self.repository.log_access_attempt(&entry).await?;
        Ok(())
    }

    /// Parse permission string into Permission enum
    fn parse_permission(&self, perm_name: &str) -> Permission {
        match perm_name {
            "read_users" => Permission::ReadUsers,
            "write_users" => Permission::WriteUsers,
            "delete_users" => Permission::DeleteUsers,
            "read_content" => Permission::ReadContent,
            "write_content" => Permission::WriteContent,
            "delete_content" => Permission::DeleteContent,
            "manage_roles" => Permission::ManageRoles,
            "manage_system" => Permission::ManageSystem,
            _ => {
                if perm_name.starts_with("read_database:") {
                    Permission::ReadDatabase(
                        perm_name
                            .strip_prefix("read_database:")
                            .unwrap_or("")
                            .to_string(),
                    )
                } else if perm_name.starts_with("write_database:") {
                    Permission::WriteDatabase(
                        perm_name
                            .strip_prefix("write_database:")
                            .unwrap_or("")
                            .to_string(),
                    )
                } else if perm_name.starts_with("delete_database:") {
                    Permission::DeleteDatabase(
                        perm_name
                            .strip_prefix("delete_database:")
                            .unwrap_or("")
                            .to_string(),
                    )
                } else if perm_name.starts_with("read_file:") {
                    Permission::ReadFile(
                        perm_name
                            .strip_prefix("read_file:")
                            .unwrap_or("")
                            .to_string(),
                    )
                } else if perm_name.starts_with("write_file:") {
                    Permission::WriteFile(
                        perm_name
                            .strip_prefix("write_file:")
                            .unwrap_or("")
                            .to_string(),
                    )
                } else if perm_name.starts_with("delete_file:") {
                    Permission::DeleteFile(
                        perm_name
                            .strip_prefix("delete_file:")
                            .unwrap_or("")
                            .to_string(),
                    )
                } else if perm_name.starts_with("access_category:") {
                    Permission::AccessCategory(
                        perm_name
                            .strip_prefix("access_category:")
                            .unwrap_or("")
                            .to_string(),
                    )
                } else if perm_name.starts_with("access_tag:") {
                    Permission::AccessTag(
                        perm_name
                            .strip_prefix("access_tag:")
                            .unwrap_or("")
                            .to_string(),
                    )
                } else {
                    Permission::Custom(perm_name.to_string())
                }
            }
        }
    }

    /// Get RBAC configuration
    pub async fn get_rbac_config(&self, name: &str) -> Result<RBACConfig> {
        // Check cache first
        {
            let cache = self.config_cache.read().await;
            if let Some(config) = cache.get(name) {
                return Ok(config.clone());
            }
        }

        // Load from database
        let config_data = self.repository.get_rbac_config(name).await?;
        let config = if let Some(data) = config_data {
            serde_json::from_value(data)?
        } else {
            RBACConfig::default()
        };

        // Cache the config
        {
            let mut cache = self.config_cache.write().await;
            cache.insert(name.to_string(), config.clone());
        }

        Ok(config)
    }

    /// Save RBAC configuration
    pub async fn save_rbac_config(
        &self,
        name: &str,
        config: &RBACConfig,
        description: Option<&str>,
    ) -> Result<()> {
        let config_data = serde_json::to_value(config)?;
        self.repository
            .save_rbac_config(name, description, &config_data)
            .await?;

        // Update cache
        {
            let mut cache = self.config_cache.write().await;
            cache.insert(name.to_string(), config.clone());
        }

        Ok(())
    }

    /// Load RBAC configuration from TOML file
    pub async fn load_config_from_toml(&self, toml_content: &str) -> Result<RBACConfig> {
        let config: RBACConfig = toml::from_str(toml_content)?;
        Ok(config)
    }

    /// Assign category to user
    pub async fn assign_category_to_user(
        &self,
        user_id: Uuid,
        category_name: &str,
        assigned_by: Option<Uuid>,
        expires_at: Option<DateTime<Utc>>,
    ) -> Result<()> {
        let category = self.repository.get_category_by_name(category_name).await?;
        let category =
            category.ok_or_else(|| anyhow::anyhow!("Category not found: {}", category_name))?;

        self.repository
            .assign_category_to_user(user_id, category.id, assigned_by, expires_at)
            .await?;

        // Invalidate user's permission cache
        self.invalidate_user_cache(user_id).await?;

        Ok(())
    }

    /// Remove category from user
    pub async fn remove_category_from_user(
        &self,
        user_id: Uuid,
        category_name: &str,
    ) -> Result<()> {
        let category = self.repository.get_category_by_name(category_name).await?;
        let category =
            category.ok_or_else(|| anyhow::anyhow!("Category not found: {}", category_name))?;

        self.repository
            .remove_category_from_user(user_id, category.id)
            .await?;

        // Invalidate user's permission cache
        self.invalidate_user_cache(user_id).await?;

        Ok(())
    }

    /// Assign tag to user
    pub async fn assign_tag_to_user(
        &self,
        user_id: Uuid,
        tag_name: &str,
        assigned_by: Option<Uuid>,
        expires_at: Option<DateTime<Utc>>,
    ) -> Result<()> {
        let tag = self.repository.get_tag_by_name(tag_name).await?;
        let tag = tag.ok_or_else(|| anyhow::anyhow!("Tag not found: {}", tag_name))?;

        self.repository
            .assign_tag_to_user(user_id, tag.id, assigned_by, expires_at)
            .await?;

        // Invalidate user's permission cache
        self.invalidate_user_cache(user_id).await?;

        Ok(())
    }

    /// Remove tag from user
    pub async fn remove_tag_from_user(&self, user_id: Uuid, tag_name: &str) -> Result<()> {
        let tag = self.repository.get_tag_by_name(tag_name).await?;
        let tag = tag.ok_or_else(|| anyhow::anyhow!("Tag not found: {}", tag_name))?;

        self.repository
            .remove_tag_from_user(user_id, tag.id)
            .await?;

        // Invalidate user's permission cache
        self.invalidate_user_cache(user_id).await?;

        Ok(())
    }

    /// Invalidate user's permission cache
    async fn invalidate_user_cache(&self, user_id: Uuid) -> Result<()> {
        // For now, we'll rely on cache expiration
        // In a production system, you might want to implement selective cache invalidation
        Ok(())
    }

    /// Get user's access history
    pub async fn get_user_access_history(
        &self,
        user_id: Uuid,
        limit: i64,
    ) -> Result<Vec<AccessAuditEntry>> {
        self.repository
            .get_user_access_history(user_id, limit)
            .await
    }

    /// Clean up expired cache entries
    pub async fn cleanup_expired_cache(&self) -> Result<u64> {
        self.repository.cleanup_expired_cache().await
    }

    /// Check if user has access to database
    pub async fn check_database_access(
        &self,
        user: &User,
        database_name: &str,
        action: &str,
    ) -> Result<AccessResult> {
        let context = AccessContext {
            user: Some(user.clone()),
            resource_type: ResourceType::Database,
            resource_name: database_name.to_string(),
            action: action.to_string(),
            additional_context: HashMap::new(),
        };

        self.check_access(&context).await
    }

    /// Check if user has access to file
    pub async fn check_file_access(
        &self,
        user: &User,
        file_path: &str,
        action: &str,
    ) -> Result<AccessResult> {
        let context = AccessContext {
            user: Some(user.clone()),
            resource_type: ResourceType::File,
            resource_name: file_path.to_string(),
            action: action.to_string(),
            additional_context: HashMap::new(),
        };

        self.check_access(&context).await
    }

    /// Check if user has access to content
    pub async fn check_content_access(
        &self,
        user: &User,
        content_id: &str,
        action: &str,
    ) -> Result<AccessResult> {
        let context = AccessContext {
            user: Some(user.clone()),
            resource_type: ResourceType::Content,
            resource_name: content_id.to_string(),
            action: action.to_string(),
            additional_context: HashMap::new(),
        };

        self.check_access(&context).await
    }
}

impl ResourceType {
    pub fn to_string(&self) -> String {
        match self {
            ResourceType::Database => "Database".to_string(),
            ResourceType::File => "File".to_string(),
            ResourceType::Directory => "Directory".to_string(),
            ResourceType::Content => "Content".to_string(),
            ResourceType::Api => "Api".to_string(),
            ResourceType::Custom(name) => name.clone(),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use rustelo_core_lib::auth::{Role, UserProfile};
    use std::collections::HashMap;

    fn create_test_user() -> User {
        User {
            id: Uuid::new_v4(),
            email: "test@example.com".to_string(),
            username: "testuser".to_string(),
            display_name: Some("Test User".to_string()),
            avatar_url: None,
            roles: vec![Role::User],
            is_active: true,
            email_verified: true,
            created_at: Utc::now(),
            updated_at: Utc::now(),
            last_login: None,
            two_factor_enabled: false,
            profile: UserProfile {
                first_name: None,
                last_name: None,
                bio: None,
                timezone: None,
                locale: None,
                preferences: HashMap::new(),
                categories: vec!["editor".to_string()],
                tags: vec!["internal".to_string()],
            },
        }
    }

    #[test]
    fn test_cache_key_generation() {
        let user_id = Uuid::new_v4();
        let context = AccessContext {
            user: None,
            resource_type: ResourceType::Database,
            resource_name: "test_db".to_string(),
            action: "read".to_string(),
            additional_context: HashMap::new(),
        };

        // This would be called in a real service instance
        let expected_key = format!("{}:Database:test_db:read", user_id);

        // We can't test the actual method without creating a service instance
        // but we can verify the expected format
        assert!(expected_key.contains(&user_id.to_string()));
        assert!(expected_key.contains("Database"));
        assert!(expected_key.contains("test_db"));
        assert!(expected_key.contains("read"));
    }

    #[test]
    fn test_permission_parsing() {
        let service = RBACService::new(Arc::new(RBACRepository::new(
            sqlx::Pool::connect("postgres://test")
                .await
                .unwrap_or_else(|_| panic!("DB connection failed")),
        )));

        let perm = service.parse_permission("read_users");
        assert_eq!(perm, Permission::ReadUsers);

        let perm = service.parse_permission("read_database:test_db");
        assert_eq!(perm, Permission::ReadDatabase("test_db".to_string()));

        let perm = service.parse_permission("custom_permission");
        assert_eq!(perm, Permission::Custom("custom_permission".to_string()));
    }
}
