//! Database-agnostic RBAC (Role-Based Access Control) repository
//!
//! This module provides a unified interface for RBAC operations that works
//! with both SQLite and PostgreSQL databases.

use anyhow::Result;
use chrono::{DateTime, Utc};
use uuid::Uuid;

use crate::database::{DatabaseType, connection::DatabaseConnection};

#[derive(Debug, Clone)]
pub struct RBACRepository {
    database: DatabaseConnection,
}

#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct UserCategory {
    pub id: Uuid,
    pub name: String,
    pub description: Option<String>,
    pub parent_id: Option<Uuid>,
    pub metadata: Option<serde_json::Value>,
    pub is_active: bool,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
}

#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct UserTag {
    pub id: Uuid,
    pub name: String,
    pub description: Option<String>,
    pub color: Option<String>,
    pub metadata: Option<serde_json::Value>,
    pub is_active: bool,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
}

#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct AccessRuleRow {
    pub id: Uuid,
    pub name: String,
    pub description: Option<String>,
    pub resource_type: String,
    pub resource_name: String,
    pub action: String,
    pub priority: i32,
    pub is_active: bool,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
}

#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct PermissionCacheEntry {
    pub user_id: Uuid,
    pub resource_type: String,
    pub resource_name: String,
    pub action: String,
    pub access_result: String,
    pub cache_key: String,
    pub expires_at: DateTime<Utc>,
}

#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct AccessAuditEntry {
    pub user_id: Option<Uuid>,
    pub resource_type: String,
    pub resource_name: String,
    pub action: String,
    pub access_result: String,
    pub rule_id: Option<Uuid>,
    pub ip_address: Option<std::net::IpAddr>,
    pub user_agent: Option<String>,
    pub session_id: Option<String>,
    pub additional_context: Option<serde_json::Value>,
}

#[allow(dead_code)]
impl RBACRepository {
    pub fn new(database: DatabaseConnection) -> Self {
        Self { database }
    }

    pub fn from_pool(pool: &crate::database::DatabasePool) -> Self {
        let connection = DatabaseConnection::from_pool(pool);
        Self::new(connection)
    }

    /// Initialize RBAC tables
    pub async fn init_tables(&self) -> Result<()> {
        match self.database.database_type() {
            DatabaseType::PostgreSQL => self.init_postgres_tables().await,
            DatabaseType::SQLite => self.init_sqlite_tables().await,
        }
    }

    async fn init_postgres_tables(&self) -> Result<()> {
        self.database
            .execute(
                r#"
                -- User Categories table
                CREATE TABLE IF NOT EXISTS user_categories (
                    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
                    name VARCHAR(255) NOT NULL UNIQUE,
                    description TEXT,
                    parent_id UUID REFERENCES user_categories(id) ON DELETE CASCADE,
                    metadata JSONB,
                    is_active BOOLEAN NOT NULL DEFAULT TRUE,
                    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
                    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
                );

                -- User Tags table
                CREATE TABLE IF NOT EXISTS user_tags (
                    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
                    name VARCHAR(255) NOT NULL UNIQUE,
                    description TEXT,
                    color VARCHAR(7), -- hex color code
                    metadata JSONB,
                    is_active BOOLEAN NOT NULL DEFAULT TRUE,
                    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
                    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
                );

                -- User Category Assignments
                CREATE TABLE IF NOT EXISTS user_category_assignments (
                    user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
                    category_id UUID NOT NULL REFERENCES user_categories(id) ON DELETE CASCADE,
                    assigned_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
                    assigned_by UUID REFERENCES users(id),
                    PRIMARY KEY (user_id, category_id)
                );

                -- User Tag Assignments
                CREATE TABLE IF NOT EXISTS user_tag_assignments (
                    user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
                    tag_id UUID NOT NULL REFERENCES user_tags(id) ON DELETE CASCADE,
                    assigned_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
                    assigned_by UUID REFERENCES users(id),
                    PRIMARY KEY (user_id, tag_id)
                );

                -- Access Rules table
                CREATE TABLE IF NOT EXISTS access_rules (
                    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
                    name VARCHAR(255) NOT NULL UNIQUE,
                    description TEXT,
                    resource_type VARCHAR(100) NOT NULL,
                    resource_name VARCHAR(255) NOT NULL,
                    action VARCHAR(100) NOT NULL,
                    priority INTEGER NOT NULL DEFAULT 0,
                    is_active BOOLEAN NOT NULL DEFAULT TRUE,
                    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
                    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
                );

                -- Permission Cache table
                CREATE TABLE IF NOT EXISTS permission_cache (
                    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
                    user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
                    resource_type VARCHAR(100) NOT NULL,
                    resource_name VARCHAR(255) NOT NULL,
                    action VARCHAR(100) NOT NULL,
                    access_result VARCHAR(20) NOT NULL,
                    cache_key VARCHAR(255) NOT NULL UNIQUE,
                    expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
                    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
                );

                -- Access Audit table
                CREATE TABLE IF NOT EXISTS access_audit (
                    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
                    user_id UUID REFERENCES users(id) ON DELETE SET NULL,
                    resource_type VARCHAR(100) NOT NULL,
                    resource_name VARCHAR(255) NOT NULL,
                    action VARCHAR(100) NOT NULL,
                    access_result VARCHAR(20) NOT NULL,
                    rule_id UUID REFERENCES access_rules(id) ON DELETE SET NULL,
                    ip_address INET,
                    user_agent TEXT,
                    session_id VARCHAR(255),
                    additional_context JSONB,
                    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
                );

                -- Create indexes
                CREATE INDEX IF NOT EXISTS idx_user_categories_parent ON user_categories(parent_id);
                CREATE INDEX IF NOT EXISTS idx_user_category_assignments_user ON user_category_assignments(user_id);
                CREATE INDEX IF NOT EXISTS idx_user_tag_assignments_user ON user_tag_assignments(user_id);
                CREATE INDEX IF NOT EXISTS idx_access_rules_resource ON access_rules(resource_type, resource_name);
                CREATE INDEX IF NOT EXISTS idx_permission_cache_user ON permission_cache(user_id);
                CREATE INDEX IF NOT EXISTS idx_permission_cache_expires ON permission_cache(expires_at);
                CREATE INDEX IF NOT EXISTS idx_access_audit_user ON access_audit(user_id);
                CREATE INDEX IF NOT EXISTS idx_access_audit_resource ON access_audit(resource_type, resource_name);
                CREATE INDEX IF NOT EXISTS idx_access_audit_created ON access_audit(created_at);
                "#,
                &[],
            )
            .await?;
        Ok(())
    }

    async fn init_sqlite_tables(&self) -> Result<()> {
        self.database
            .execute(
                r#"
                -- User Categories table
                CREATE TABLE IF NOT EXISTS user_categories (
                    id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
                    name TEXT NOT NULL UNIQUE,
                    description TEXT,
                    parent_id TEXT REFERENCES user_categories(id) ON DELETE CASCADE,
                    metadata TEXT, -- JSON as text
                    is_active INTEGER NOT NULL DEFAULT 1,
                    created_at TEXT DEFAULT (datetime('now')),
                    updated_at TEXT DEFAULT (datetime('now'))
                );

                -- User Tags table
                CREATE TABLE IF NOT EXISTS user_tags (
                    id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
                    name TEXT NOT NULL UNIQUE,
                    description TEXT,
                    color TEXT, -- hex color code
                    metadata TEXT, -- JSON as text
                    is_active INTEGER NOT NULL DEFAULT 1,
                    created_at TEXT DEFAULT (datetime('now')),
                    updated_at TEXT DEFAULT (datetime('now'))
                );

                -- User Category Assignments
                CREATE TABLE IF NOT EXISTS user_category_assignments (
                    user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
                    category_id TEXT NOT NULL REFERENCES user_categories(id) ON DELETE CASCADE,
                    assigned_at TEXT DEFAULT (datetime('now')),
                    assigned_by TEXT REFERENCES users(id),
                    PRIMARY KEY (user_id, category_id)
                );

                -- User Tag Assignments
                CREATE TABLE IF NOT EXISTS user_tag_assignments (
                    user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
                    tag_id TEXT NOT NULL REFERENCES user_tags(id) ON DELETE CASCADE,
                    assigned_at TEXT DEFAULT (datetime('now')),
                    assigned_by TEXT REFERENCES users(id),
                    PRIMARY KEY (user_id, tag_id)
                );

                -- Access Rules table
                CREATE TABLE IF NOT EXISTS access_rules (
                    id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
                    name TEXT NOT NULL UNIQUE,
                    description TEXT,
                    resource_type TEXT NOT NULL,
                    resource_name TEXT NOT NULL,
                    action TEXT NOT NULL,
                    priority INTEGER NOT NULL DEFAULT 0,
                    is_active INTEGER NOT NULL DEFAULT 1,
                    created_at TEXT DEFAULT (datetime('now')),
                    updated_at TEXT DEFAULT (datetime('now'))
                );

                -- Permission Cache table
                CREATE TABLE IF NOT EXISTS permission_cache (
                    id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
                    user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
                    resource_type TEXT NOT NULL,
                    resource_name TEXT NOT NULL,
                    action TEXT NOT NULL,
                    access_result TEXT NOT NULL,
                    cache_key TEXT NOT NULL UNIQUE,
                    expires_at TEXT NOT NULL,
                    created_at TEXT DEFAULT (datetime('now'))
                );

                -- Access Audit table
                CREATE TABLE IF NOT EXISTS access_audit (
                    id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
                    user_id TEXT REFERENCES users(id) ON DELETE SET NULL,
                    resource_type TEXT NOT NULL,
                    resource_name TEXT NOT NULL,
                    action TEXT NOT NULL,
                    access_result TEXT NOT NULL,
                    rule_id TEXT REFERENCES access_rules(id) ON DELETE SET NULL,
                    ip_address TEXT,
                    user_agent TEXT,
                    session_id TEXT,
                    additional_context TEXT, -- JSON as text
                    created_at TEXT DEFAULT (datetime('now'))
                );

                -- Create indexes
                CREATE INDEX IF NOT EXISTS idx_user_categories_parent ON user_categories(parent_id);
                CREATE INDEX IF NOT EXISTS idx_user_category_assignments_user ON user_category_assignments(user_id);
                CREATE INDEX IF NOT EXISTS idx_user_tag_assignments_user ON user_tag_assignments(user_id);
                CREATE INDEX IF NOT EXISTS idx_access_rules_resource ON access_rules(resource_type, resource_name);
                CREATE INDEX IF NOT EXISTS idx_permission_cache_user ON permission_cache(user_id);
                CREATE INDEX IF NOT EXISTS idx_permission_cache_expires ON permission_cache(expires_at);
                CREATE INDEX IF NOT EXISTS idx_access_audit_user ON access_audit(user_id);
                CREATE INDEX IF NOT EXISTS idx_access_audit_resource ON access_audit(resource_type, resource_name);
                CREATE INDEX IF NOT EXISTS idx_access_audit_created ON access_audit(created_at);
                "#,
                &[],
            )
            .await?;
        Ok(())
    }

    /// Create a new user category
    pub async fn create_category(
        &self,
        name: &str,
        description: Option<&str>,
        parent_id: Option<Uuid>,
    ) -> Result<UserCategory> {
        match self.database.database_type() {
            DatabaseType::PostgreSQL => {
                self.create_category_postgres(name, description, parent_id)
                    .await
            }
            DatabaseType::SQLite => {
                self.create_category_sqlite(name, description, parent_id)
                    .await
            }
        }
    }

    async fn create_category_postgres(
        &self,
        name: &str,
        description: Option<&str>,
        parent_id: Option<Uuid>,
    ) -> Result<UserCategory> {
        let row = self
            .database
            .fetch_one(
                r#"
                INSERT INTO user_categories (name, description, parent_id)
                VALUES ($1, $2, $3)
                RETURNING id, name, description, parent_id, metadata, is_active, created_at, updated_at
                "#,
                &[
                    name.to_string().into(),
                    description.map(|s| s.to_string()).into(),
                    parent_id.into(),
                ],
            )
            .await?;

        Ok(UserCategory {
            id: row.get_uuid("id")?,
            name: row.get_string("name")?,
            description: row.get_optional_string("description")?,
            parent_id: row.get_optional_uuid("parent_id")?,
            metadata: row
                .get_optional_string("metadata")?
                .and_then(|s| serde_json::from_str(&s).ok()),
            is_active: row.get_bool("is_active")?,
            created_at: row.get_datetime("created_at")?,
            updated_at: row.get_datetime("updated_at")?,
        })
    }

    async fn create_category_sqlite(
        &self,
        name: &str,
        description: Option<&str>,
        parent_id: Option<Uuid>,
    ) -> Result<UserCategory> {
        let id = Uuid::new_v4();
        let now = Utc::now();

        self.database
            .execute(
                r#"
                INSERT INTO user_categories (id, name, description, parent_id, created_at, updated_at)
                VALUES (?, ?, ?, ?, ?, ?)
                "#,
                &[
                    id.to_string().into(),
                    name.to_string().into(),
                    description.map(|s| s.to_string()).into(),
                    parent_id.map(|id| id.to_string()).into(),
                    now.to_rfc3339().into(),
                    now.to_rfc3339().into(),
                ],
            )
            .await?;

        Ok(UserCategory {
            id,
            name: name.to_string(),
            description: description.map(|s| s.to_string()),
            parent_id,
            metadata: None,
            is_active: true,
            created_at: now,
            updated_at: now,
        })
    }

    /// Create a new user tag
    pub async fn create_tag(
        &self,
        name: &str,
        description: Option<&str>,
        color: Option<&str>,
    ) -> Result<UserTag> {
        match self.database.database_type() {
            DatabaseType::PostgreSQL => self.create_tag_postgres(name, description, color).await,
            DatabaseType::SQLite => self.create_tag_sqlite(name, description, color).await,
        }
    }

    async fn create_tag_postgres(
        &self,
        name: &str,
        description: Option<&str>,
        color: Option<&str>,
    ) -> Result<UserTag> {
        let row = self
            .database
            .fetch_one(
                r#"
                INSERT INTO user_tags (name, description, color)
                VALUES ($1, $2, $3)
                RETURNING id, name, description, color, metadata, is_active, created_at, updated_at
                "#,
                &[
                    name.to_string().into(),
                    description.map(|s| s.to_string()).into(),
                    color.map(|s| s.to_string()).into(),
                ],
            )
            .await?;

        Ok(UserTag {
            id: row.get_uuid("id")?,
            name: row.get_string("name")?,
            description: row.get_optional_string("description")?,
            color: row.get_optional_string("color")?,
            metadata: row
                .get_optional_string("metadata")?
                .and_then(|s| serde_json::from_str(&s).ok()),
            is_active: row.get_bool("is_active")?,
            created_at: row.get_datetime("created_at")?,
            updated_at: row.get_datetime("updated_at")?,
        })
    }

    async fn create_tag_sqlite(
        &self,
        name: &str,
        description: Option<&str>,
        color: Option<&str>,
    ) -> Result<UserTag> {
        let id = Uuid::new_v4();
        let now = Utc::now();

        self.database
            .execute(
                r#"
                INSERT INTO user_tags (id, name, description, color, created_at, updated_at)
                VALUES (?, ?, ?, ?, ?, ?)
                "#,
                &[
                    id.to_string().into(),
                    name.to_string().into(),
                    description.map(|s| s.to_string()).into(),
                    color.map(|s| s.to_string()).into(),
                    now.to_rfc3339().into(),
                    now.to_rfc3339().into(),
                ],
            )
            .await?;

        Ok(UserTag {
            id,
            name: name.to_string(),
            description: description.map(|s| s.to_string()),
            color: color.map(|s| s.to_string()),
            metadata: None,
            is_active: true,
            created_at: now,
            updated_at: now,
        })
    }

    /// Assign a category to a user
    pub async fn assign_category_to_user(
        &self,
        user_id: Uuid,
        category_id: Uuid,
        assigned_by: Option<Uuid>,
    ) -> Result<()> {
        match self.database.database_type() {
            DatabaseType::PostgreSQL => {
                self.assign_category_to_user_postgres(user_id, category_id, assigned_by)
                    .await
            }
            DatabaseType::SQLite => {
                self.assign_category_to_user_sqlite(user_id, category_id, assigned_by)
                    .await
            }
        }
    }

    async fn assign_category_to_user_postgres(
        &self,
        user_id: Uuid,
        category_id: Uuid,
        assigned_by: Option<Uuid>,
    ) -> Result<()> {
        self.database
            .execute(
                r#"
                INSERT INTO user_category_assignments (user_id, category_id, assigned_by)
                VALUES ($1, $2, $3)
                ON CONFLICT (user_id, category_id) DO NOTHING
                "#,
                &[user_id.into(), category_id.into(), assigned_by.into()],
            )
            .await?;
        Ok(())
    }

    async fn assign_category_to_user_sqlite(
        &self,
        user_id: Uuid,
        category_id: Uuid,
        assigned_by: Option<Uuid>,
    ) -> Result<()> {
        self.database
            .execute(
                r#"
                INSERT OR IGNORE INTO user_category_assignments (user_id, category_id, assigned_by)
                VALUES (?, ?, ?)
                "#,
                &[
                    user_id.to_string().into(),
                    category_id.to_string().into(),
                    assigned_by.map(|id| id.to_string()).into(),
                ],
            )
            .await?;
        Ok(())
    }

    /// Assign a tag to a user
    pub async fn assign_tag_to_user(
        &self,
        user_id: Uuid,
        tag_id: Uuid,
        assigned_by: Option<Uuid>,
    ) -> Result<()> {
        match self.database.database_type() {
            DatabaseType::PostgreSQL => {
                self.assign_tag_to_user_postgres(user_id, tag_id, assigned_by)
                    .await
            }
            DatabaseType::SQLite => {
                self.assign_tag_to_user_sqlite(user_id, tag_id, assigned_by)
                    .await
            }
        }
    }

    async fn assign_tag_to_user_postgres(
        &self,
        user_id: Uuid,
        tag_id: Uuid,
        assigned_by: Option<Uuid>,
    ) -> Result<()> {
        self.database
            .execute(
                r#"
                INSERT INTO user_tag_assignments (user_id, tag_id, assigned_by)
                VALUES ($1, $2, $3)
                ON CONFLICT (user_id, tag_id) DO NOTHING
                "#,
                &[user_id.into(), tag_id.into(), assigned_by.into()],
            )
            .await?;
        Ok(())
    }

    async fn assign_tag_to_user_sqlite(
        &self,
        user_id: Uuid,
        tag_id: Uuid,
        assigned_by: Option<Uuid>,
    ) -> Result<()> {
        self.database
            .execute(
                r#"
                INSERT OR IGNORE INTO user_tag_assignments (user_id, tag_id, assigned_by)
                VALUES (?, ?, ?)
                "#,
                &[
                    user_id.to_string().into(),
                    tag_id.to_string().into(),
                    assigned_by.map(|id| id.to_string()).into(),
                ],
            )
            .await?;
        Ok(())
    }

    /// Create an access rule
    pub async fn create_access_rule(
        &self,
        name: &str,
        description: Option<&str>,
        resource_type: &str,
        resource_name: &str,
        action: &str,
        priority: i32,
    ) -> Result<AccessRuleRow> {
        match self.database.database_type() {
            DatabaseType::PostgreSQL => {
                self.create_access_rule_postgres(
                    name,
                    description,
                    resource_type,
                    resource_name,
                    action,
                    priority,
                )
                .await
            }
            DatabaseType::SQLite => {
                self.create_access_rule_sqlite(
                    name,
                    description,
                    resource_type,
                    resource_name,
                    action,
                    priority,
                )
                .await
            }
        }
    }

    async fn create_access_rule_postgres(
        &self,
        name: &str,
        description: Option<&str>,
        resource_type: &str,
        resource_name: &str,
        action: &str,
        priority: i32,
    ) -> Result<AccessRuleRow> {
        let row = self
            .database
            .fetch_one(
                r#"
                INSERT INTO access_rules (name, description, resource_type, resource_name, action, priority)
                VALUES ($1, $2, $3, $4, $5, $6)
                RETURNING id, name, description, resource_type, resource_name, action, priority, is_active, created_at, updated_at
                "#,
                &[
                    name.to_string().into(),
                    description.map(|s| s.to_string()).into(),
                    resource_type.to_string().into(),
                    resource_name.to_string().into(),
                    action.to_string().into(),
                    priority.into(),
                ],
            )
            .await?;

        Ok(AccessRuleRow {
            id: row.get_uuid("id")?,
            name: row.get_string("name")?,
            description: row.get_optional_string("description")?,
            resource_type: row.get_string("resource_type")?,
            resource_name: row.get_string("resource_name")?,
            action: row.get_string("action")?,
            priority: row.get_i32("priority")?,
            is_active: row.get_bool("is_active")?,
            created_at: row.get_datetime("created_at")?,
            updated_at: row.get_datetime("updated_at")?,
        })
    }

    async fn create_access_rule_sqlite(
        &self,
        name: &str,
        description: Option<&str>,
        resource_type: &str,
        resource_name: &str,
        action: &str,
        priority: i32,
    ) -> Result<AccessRuleRow> {
        let id = Uuid::new_v4();
        let now = Utc::now();

        self.database
            .execute(
                r#"
                INSERT INTO access_rules (id, name, description, resource_type, resource_name, action, priority, created_at, updated_at)
                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
                "#,
                &[
                    id.to_string().into(),
                    name.to_string().into(),
                    description.map(|s| s.to_string()).into(),
                    resource_type.to_string().into(),
                    resource_name.to_string().into(),
                    action.to_string().into(),
                    priority.into(),
                    now.to_rfc3339().into(),
                    now.to_rfc3339().into(),
                ],
            )
            .await?;

        Ok(AccessRuleRow {
            id,
            name: name.to_string(),
            description: description.map(|s| s.to_string()),
            resource_type: resource_type.to_string(),
            resource_name: resource_name.to_string(),
            action: action.to_string(),
            priority,
            is_active: true,
            created_at: now,
            updated_at: now,
        })
    }

    /// Get user categories
    pub async fn get_user_categories(&self, user_id: Uuid) -> Result<Vec<UserCategory>> {
        match self.database.database_type() {
            DatabaseType::PostgreSQL => self.get_user_categories_postgres(user_id).await,
            DatabaseType::SQLite => self.get_user_categories_sqlite(user_id).await,
        }
    }

    async fn get_user_categories_postgres(&self, user_id: Uuid) -> Result<Vec<UserCategory>> {
        let rows = self
            .database
            .fetch_all(
                r#"
                SELECT c.id, c.name, c.description, c.parent_id, c.metadata, c.is_active, c.created_at, c.updated_at
                FROM user_categories c
                INNER JOIN user_category_assignments uca ON c.id = uca.category_id
                WHERE uca.user_id = $1 AND c.is_active = true
                ORDER BY c.name
                "#,
                &[user_id.into()],
            )
            .await?;

        let mut categories = Vec::new();
        for row in rows {
            categories.push(UserCategory {
                id: row.get_uuid("id")?,
                name: row.get_string("name")?,
                description: row.get_optional_string("description")?,
                parent_id: row.get_optional_uuid("parent_id")?,
                metadata: row
                    .get_optional_string("metadata")?
                    .and_then(|s| serde_json::from_str(&s).ok()),
                is_active: row.get_bool("is_active")?,
                created_at: row.get_datetime("created_at")?,
                updated_at: row.get_datetime("updated_at")?,
            });
        }

        Ok(categories)
    }

    async fn get_user_categories_sqlite(&self, user_id: Uuid) -> Result<Vec<UserCategory>> {
        let rows = self
            .database
            .fetch_all(
                r#"
                SELECT c.id, c.name, c.description, c.parent_id, c.metadata, c.is_active, c.created_at, c.updated_at
                FROM user_categories c
                INNER JOIN user_category_assignments uca ON c.id = uca.category_id
                WHERE uca.user_id = ? AND c.is_active = 1
                ORDER BY c.name
                "#,
                &[user_id.to_string().into()],
            )
            .await?;

        let mut categories = Vec::new();
        for row in rows {
            categories.push(UserCategory {
                id: row.get_uuid("id")?,
                name: row.get_string("name")?,
                description: row.get_optional_string("description")?,
                parent_id: row.get_optional_uuid("parent_id")?,
                metadata: row
                    .get_optional_string("metadata")?
                    .and_then(|s| serde_json::from_str(&s).ok()),
                is_active: row.get_bool("is_active")?,
                created_at: row.get_datetime("created_at")?,
                updated_at: row.get_datetime("updated_at")?,
            });
        }

        Ok(categories)
    }

    /// Get user tags
    pub async fn get_user_tags(&self, user_id: Uuid) -> Result<Vec<UserTag>> {
        match self.database.database_type() {
            DatabaseType::PostgreSQL => self.get_user_tags_postgres(user_id).await,
            DatabaseType::SQLite => self.get_user_tags_sqlite(user_id).await,
        }
    }

    async fn get_user_tags_postgres(&self, user_id: Uuid) -> Result<Vec<UserTag>> {
        let rows = self
            .database
            .fetch_all(
                r#"
                SELECT t.id, t.name, t.description, t.color, t.metadata, t.is_active, t.created_at, t.updated_at
                FROM user_tags t
                INNER JOIN user_tag_assignments uta ON t.id = uta.tag_id
                WHERE uta.user_id = $1 AND t.is_active = true
                ORDER BY t.name
                "#,
                &[user_id.into()],
            )
            .await?;

        let mut tags = Vec::new();
        for row in rows {
            tags.push(UserTag {
                id: row.get_uuid("id")?,
                name: row.get_string("name")?,
                description: row.get_optional_string("description")?,
                color: row.get_optional_string("color")?,
                metadata: row
                    .get_optional_string("metadata")?
                    .and_then(|s| serde_json::from_str(&s).ok()),
                is_active: row.get_bool("is_active")?,
                created_at: row.get_datetime("created_at")?,
                updated_at: row.get_datetime("updated_at")?,
            });
        }

        Ok(tags)
    }

    async fn get_user_tags_sqlite(&self, user_id: Uuid) -> Result<Vec<UserTag>> {
        let rows = self
            .database
            .fetch_all(
                r#"
                SELECT t.id, t.name, t.description, t.color, t.metadata, t.is_active, t.created_at, t.updated_at
                FROM user_tags t
                INNER JOIN user_tag_assignments uta ON t.id = uta.tag_id
                WHERE uta.user_id = ? AND t.is_active = 1
                ORDER BY t.name
                "#,
                &[user_id.to_string().into()],
            )
            .await?;

        let mut tags = Vec::new();
        for row in rows {
            tags.push(UserTag {
                id: row.get_uuid("id")?,
                name: row.get_string("name")?,
                description: row.get_optional_string("description")?,
                color: row.get_optional_string("color")?,
                metadata: row
                    .get_optional_string("metadata")?
                    .and_then(|s| serde_json::from_str(&s).ok()),
                is_active: row.get_bool("is_active")?,
                created_at: row.get_datetime("created_at")?,
                updated_at: row.get_datetime("updated_at")?,
            });
        }

        Ok(tags)
    }

    /// Check if a user has access to a resource
    pub async fn check_access(
        &self,
        user_id: Uuid,
        resource_type: &str,
        resource_name: &str,
        action: &str,
    ) -> Result<bool> {
        // This is a simplified implementation
        // In a real system, you would implement complex rule evaluation
        match self.database.database_type() {
            DatabaseType::PostgreSQL => {
                self.check_access_postgres(user_id, resource_type, resource_name, action)
                    .await
            }
            DatabaseType::SQLite => {
                self.check_access_sqlite(user_id, resource_type, resource_name, action)
                    .await
            }
        }
    }

    #[allow(dead_code)]
    async fn check_access_postgres(
        &self,
        _user_id: Uuid,
        resource_type: &str,
        resource_name: &str,
        action: &str,
    ) -> Result<bool> {
        let row = self
            .database
            .fetch_optional(
                r#"
                SELECT COUNT(*) as count FROM access_rules ar
                WHERE ar.resource_type = $1
                AND ar.resource_name = $2
                AND ar.action = $3
                AND ar.is_active = true
                "#,
                &[
                    resource_type.to_string().into(),
                    resource_name.to_string().into(),
                    action.to_string().into(),
                ],
            )
            .await?;

        // For now, just check if any rule exists
        // In a real implementation, you would evaluate rules based on user categories, tags, etc.
        Ok(row
            .map(|r| r.get_i64("count").unwrap_or(0) > 0)
            .unwrap_or(false))
    }
    #[allow(dead_code)]
    async fn check_access_sqlite(
        &self,
        _user_id: Uuid,
        resource_type: &str,
        resource_name: &str,
        action: &str,
    ) -> Result<bool> {
        let row = self
            .database
            .fetch_optional(
                r#"
                SELECT COUNT(*) as count FROM access_rules ar
                WHERE ar.resource_type = ?
                AND ar.resource_name = ?
                AND ar.action = ?
                AND ar.is_active = 1
                "#,
                &[
                    resource_type.to_string().into(),
                    resource_name.to_string().into(),
                    action.to_string().into(),
                ],
            )
            .await?;

        // For now, just check if any rule exists
        // In a real implementation, you would evaluate rules based on user categories, tags, etc.
        Ok(row
            .map(|r| r.get_i64("count").unwrap_or(0) > 0)
            .unwrap_or(false))
    }
    #[allow(dead_code)]
    /// Log an access attempt for auditing
    pub async fn log_access_attempt(
        &self,
        user_id: Option<Uuid>,
        resource_type: &str,
        resource_name: &str,
        action: &str,
        access_result: &str,
        rule_id: Option<Uuid>,
        ip_address: Option<std::net::IpAddr>,
        user_agent: Option<&str>,
        session_id: Option<&str>,
        additional_context: Option<serde_json::Value>,
    ) -> Result<()> {
        match self.database.database_type() {
            DatabaseType::PostgreSQL => {
                self.log_access_attempt_postgres(
                    user_id,
                    resource_type,
                    resource_name,
                    action,
                    access_result,
                    rule_id,
                    ip_address,
                    user_agent,
                    session_id,
                    additional_context,
                )
                .await
            }
            DatabaseType::SQLite => {
                self.log_access_attempt_sqlite(
                    user_id,
                    resource_type,
                    resource_name,
                    action,
                    access_result,
                    rule_id,
                    ip_address,
                    user_agent,
                    session_id,
                    additional_context,
                )
                .await
            }
        }
    }
    #[allow(dead_code)]
    async fn log_access_attempt_postgres(
        &self,
        user_id: Option<Uuid>,
        resource_type: &str,
        resource_name: &str,
        action: &str,
        access_result: &str,
        rule_id: Option<Uuid>,
        ip_address: Option<std::net::IpAddr>,
        user_agent: Option<&str>,
        session_id: Option<&str>,
        additional_context: Option<serde_json::Value>,
    ) -> Result<()> {
        self.database
            .execute(
                r#"
                INSERT INTO access_audit (
                    user_id, resource_type, resource_name, action, access_result,
                    rule_id, ip_address, user_agent, session_id, additional_context
                ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
                "#,
                &[
                    user_id.into(),
                    resource_type.to_string().into(),
                    resource_name.to_string().into(),
                    action.to_string().into(),
                    access_result.to_string().into(),
                    rule_id.into(),
                    ip_address.map(|ip| ip.to_string()).into(),
                    user_agent.map(|ua| ua.to_string()).into(),
                    session_id.map(|s| s.to_string()).into(),
                    additional_context.map(|c| c.to_string()).into(),
                ],
            )
            .await?;
        Ok(())
    }

    async fn log_access_attempt_sqlite(
        &self,
        user_id: Option<Uuid>,
        resource_type: &str,
        resource_name: &str,
        action: &str,
        access_result: &str,
        rule_id: Option<Uuid>,
        ip_address: Option<std::net::IpAddr>,
        user_agent: Option<&str>,
        session_id: Option<&str>,
        additional_context: Option<serde_json::Value>,
    ) -> Result<()> {
        self.database
            .execute(
                r#"
                INSERT INTO access_audit (
                    user_id, resource_type, resource_name, action, access_result,
                    rule_id, ip_address, user_agent, session_id, additional_context
                ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                "#,
                &[
                    user_id.map(|id| id.to_string()).into(),
                    resource_type.to_string().into(),
                    resource_name.to_string().into(),
                    action.to_string().into(),
                    access_result.to_string().into(),
                    rule_id.map(|id| id.to_string()).into(),
                    ip_address.map(|ip| ip.to_string()).into(),
                    user_agent.map(|ua| ua.to_string()).into(),
                    session_id.map(|s| s.to_string()).into(),
                    additional_context.map(|c| c.to_string()).into(),
                ],
            )
            .await?;
        Ok(())
    }

    /// Clean up expired permission cache entries
    #[allow(dead_code)]
    pub async fn cleanup_expired_cache(&self) -> Result<u64> {
        match self.database.database_type() {
            DatabaseType::PostgreSQL => self.cleanup_expired_cache_postgres().await,
            DatabaseType::SQLite => self.cleanup_expired_cache_sqlite().await,
        }
    }

    #[allow(dead_code)]
    async fn cleanup_expired_cache_postgres(&self) -> Result<u64> {
        let result = self
            .database
            .execute("DELETE FROM permission_cache WHERE expires_at < NOW()", &[])
            .await?;
        Ok(result)
    }

    #[allow(dead_code)]
    async fn cleanup_expired_cache_sqlite(&self) -> Result<u64> {
        let result = self
            .database
            .execute(
                "DELETE FROM permission_cache WHERE expires_at < datetime('now')",
                &[],
            )
            .await?;
        Ok(result)
    }
}

#[cfg(test)]
mod tests {
    #[test]
    fn test_rbac_repository_creation() {
        // This test would require a database connection
        // For now, just test that the struct can be created
        assert!(true);
    }
}
