//! Module de gestion des permissions pour le chat server
//!
//! Ce module fournit un système centralisé de vérification des permissions
//! pour les conversations, avec support des rôles (admin, moderator, member).
//!
//! # Exemple
//!
//! ```rust,no_run
//! use chat_server::security::permission::PermissionService;
//! use uuid::Uuid;
//!
//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
//! let pool = sqlx::PgPool::connect("postgresql://...").await?;
//! let permission_service = PermissionService::new(pool);
//!
//! let user_id = Uuid::new_v4();
//! let conversation_id = Uuid::new_v4();
//!
//! // Vérifier si l'utilisateur peut envoyer un message
//! permission_service.can_send_message(user_id, conversation_id).await?;
//! # Ok(())
//! # }
//! ```

use crate::error::{ChatError, Result};
use crate::permissions::Role;
use sqlx::PgPool;
use tracing::{debug, warn};
use uuid::Uuid;

/// Erreur spécifique aux permissions
#[derive(Debug, thiserror::Error)]
pub enum PermissionError {
    #[error("Utilisateur {user_id} n'est pas membre de la conversation {conversation_id}")]
    NotMember {
        user_id: Uuid,
        conversation_id: Uuid,
    },
    #[error("Permissions insuffisantes pour {action} dans la conversation {conversation_id}")]
    InsufficientPermissions {
        action: String,
        conversation_id: Uuid,
    },
    #[error("Rôle invalide: {role}")]
    InvalidRole { role: String },
    #[error("Erreur base de données: {0}")]
    Database(#[from] sqlx::Error),
}

impl From<PermissionError> for ChatError {
    fn from(err: PermissionError) -> Self {
        match err {
            PermissionError::NotMember {
                user_id,
                conversation_id,
            } => ChatError::NotMember {
                conversation_id: conversation_id.to_string(),
            },
            PermissionError::InsufficientPermissions {
                action,
                conversation_id,
            } => ChatError::InsufficientPermissions {
                action,
                conversation_id: conversation_id.to_string(),
            },
            PermissionError::InvalidRole { role } => {
                ChatError::configuration_error(&format!("Rôle invalide: {}", role))
            }
            PermissionError::Database(e) => ChatError::from_sqlx_error("permission_check", e),
        }
    }
}

/// Service centralisé de gestion des permissions
pub struct PermissionService {
    pool: PgPool,
}

impl PermissionService {
    /// Crée un nouveau service de permissions
    pub fn new(pool: PgPool) -> Self {
        Self { pool }
    }

    /// Vérifie si un utilisateur est membre d'une conversation
    ///
    /// # Arguments
    ///
    /// * `user_id` - ID de l'utilisateur
    /// * `conversation_id` - ID de la conversation
    ///
    /// # Returns
    ///
    /// `Ok(true)` si l'utilisateur est membre, `Ok(false)` sinon
    pub async fn user_in_conversation(&self, user_id: Uuid, conversation_id: Uuid) -> Result<bool> {
        let exists: bool = sqlx::query_scalar(
            r#"
            SELECT EXISTS(
                SELECT 1 FROM conversation_members
                WHERE conversation_id = $1 AND user_id = $2
            )
            "#,
        )
        .bind(conversation_id)
        .bind(user_id)
        .fetch_one(&self.pool)
        .await
        .map_err(|e| ChatError::from_sqlx_error("check_conversation_membership", e))?;

        debug!(
            user_id = %user_id,
            conversation_id = %conversation_id,
            is_member = %exists,
            "Vérification d'appartenance à la conversation"
        );

        Ok(exists)
    }

    /// Récupère le rôle d'un utilisateur dans une conversation
    ///
    /// # Arguments
    ///
    /// * `user_id` - ID de l'utilisateur
    /// * `conversation_id` - ID de la conversation
    ///
    /// # Returns
    ///
    /// Le rôle de l'utilisateur dans la conversation, ou une erreur si non membre
    pub async fn user_role_in_conversation(
        &self,
        user_id: Uuid,
        conversation_id: Uuid,
    ) -> Result<Role> {
        let role_str: Option<String> = sqlx::query_scalar(
            r#"
            SELECT role FROM conversation_members
            WHERE conversation_id = $1 AND user_id = $2
            "#,
        )
        .bind(conversation_id)
        .bind(user_id)
        .fetch_optional(&self.pool)
        .await
        .map_err(|e| ChatError::from_sqlx_error("get_conversation_role", e))?;

        let role_str = role_str.ok_or_else(|| PermissionError::NotMember {
            user_id,
            conversation_id,
        })?;

        let role = Role::from_string(&role_str)?;

        debug!(
            user_id = %user_id,
            conversation_id = %conversation_id,
            role = ?role,
            "Rôle récupéré pour la conversation"
        );

        Ok(role)
    }

    /// Récupère le rôle global d'un utilisateur depuis la table users
    ///
    /// # Arguments
    ///
    /// * `user_id` - ID de l'utilisateur
    ///
    /// # Returns
    ///
    /// Le rôle global de l'utilisateur, ou User par défaut
    pub async fn user_global_role(&self, user_id: Uuid) -> Result<Role> {
        let role_str: Option<String> = sqlx::query_scalar(
            r#"
            SELECT role FROM users
            WHERE id = $1
            "#,
        )
        .bind(user_id)
        .fetch_optional(&self.pool)
        .await
        .map_err(|e| ChatError::from_sqlx_error("get_user_role", e))?;

        // Si pas de rôle défini ou colonne inexistante, retourner User par défaut
        let role = match role_str {
            Some(r) => Role::from_string(&r).unwrap_or(Role::User),
            None => Role::User,
        };

        debug!(
            user_id = %user_id,
            role = ?role,
            "Rôle global récupéré"
        );

        Ok(role)
    }

    /// Vérifie si un utilisateur peut envoyer un message dans une conversation
    ///
    /// # Arguments
    ///
    /// * `user_id` - ID de l'utilisateur
    /// * `conversation_id` - ID de la conversation
    ///
    /// # Returns
    ///
    /// `Ok(())` si autorisé, erreur sinon
    pub async fn can_send_message(&self, user_id: Uuid, conversation_id: Uuid) -> Result<()> {
        // Vérifier d'abord si l'utilisateur est membre
        let is_member = self.user_in_conversation(user_id, conversation_id).await?;

        if !is_member {
            // Vérifier si l'utilisateur est admin global (peut envoyer partout)
            let global_role = self.user_global_role(user_id).await?;
            match global_role {
                Role::Admin | Role::SuperAdmin => {
                    debug!(
                        user_id = %user_id,
                        conversation_id = %conversation_id,
                        "Admin autorisé à envoyer un message sans être membre"
                    );
                    return Ok(());
                }
                _ => {
                    warn!(
                        user_id = %user_id,
                        conversation_id = %conversation_id,
                        "Tentative d'envoi de message par un non-membre"
                    );
                    return Err(PermissionError::NotMember {
                        user_id,
                        conversation_id,
                    }
                    .into());
                }
            }
        }

        // Récupérer le rôle dans la conversation
        let role = self
            .user_role_in_conversation(user_id, conversation_id)
            .await?;

        // Tous les membres peuvent envoyer des messages
        // Les admins et modérateurs ont des permissions supplémentaires
        match role {
            Role::Admin | Role::Moderator | Role::User => Ok(()),
            _ => {
                warn!(
                    user_id = %user_id,
                    conversation_id = %conversation_id,
                    role = ?role,
                    "Rôle invalide pour envoyer un message"
                );
                Err(PermissionError::InsufficientPermissions {
                    action: "send_message".to_string(),
                    conversation_id,
                }
                .into())
            }
        }
    }

    /// Vérifie si un utilisateur peut lire une conversation
    ///
    /// # Arguments
    ///
    /// * `user_id` - ID de l'utilisateur
    /// * `conversation_id` - ID de la conversation
    ///
    /// # Returns
    ///
    /// `Ok(())` si autorisé, erreur sinon
    pub async fn can_read_conversation(&self, user_id: Uuid, conversation_id: Uuid) -> Result<()> {
        // Vérifier d'abord si l'utilisateur est membre
        let is_member = self.user_in_conversation(user_id, conversation_id).await?;

        if !is_member {
            // Vérifier si l'utilisateur est admin global (peut lire partout)
            let global_role = self.user_global_role(user_id).await?;
            match global_role {
                Role::Admin | Role::SuperAdmin => {
                    debug!(
                        user_id = %user_id,
                        conversation_id = %conversation_id,
                        "Admin autorisé à lire la conversation sans être membre"
                    );
                    return Ok(());
                }
                _ => {
                    warn!(
                        user_id = %user_id,
                        conversation_id = %conversation_id,
                        "Tentative de lecture d'une conversation par un non-membre"
                    );
                    return Err(PermissionError::NotMember {
                        user_id,
                        conversation_id,
                    }
                    .into());
                }
            }
        }

        // Tous les membres peuvent lire
        Ok(())
    }

    /// Vérifie si un utilisateur peut marquer un message comme lu
    ///
    /// # Arguments
    ///
    /// * `user_id` - ID de l'utilisateur
    /// * `conversation_id` - ID de la conversation
    ///
    /// # Returns
    ///
    /// `Ok(())` si autorisé, erreur sinon
    pub async fn can_mark_read(&self, user_id: Uuid, conversation_id: Uuid) -> Result<()> {
        // Même logique que can_read_conversation
        self.can_read_conversation(user_id, conversation_id).await
    }

    /// Vérifie si un utilisateur peut rejoindre une conversation
    ///
    /// # Arguments
    ///
    /// * `user_id` - ID de l'utilisateur
    /// * `conversation_id` - ID de la conversation
    ///
    /// # Returns
    ///
    /// `Ok(())` si autorisé, erreur sinon
    pub async fn can_join_conversation(&self, user_id: Uuid, conversation_id: Uuid) -> Result<()> {
        // Vérifier si la conversation est privée
        let is_private: Option<bool> = sqlx::query_scalar(
            r#"
            SELECT is_private FROM conversations
            WHERE id = $1
            "#,
        )
        .bind(conversation_id)
        .fetch_optional(&self.pool)
        .await
        .map_err(|e| ChatError::from_sqlx_error("check_conversation_privacy", e))?;

        let is_private = is_private.unwrap_or(true);

        // Si la conversation est publique, tout le monde peut rejoindre
        if !is_private {
            return Ok(());
        }

        // Si privée, vérifier si l'utilisateur est déjà membre ou admin
        let is_member = self.user_in_conversation(user_id, conversation_id).await?;
        if is_member {
            return Ok(());
        }

        let global_role = self.user_global_role(user_id).await?;
        match global_role {
            Role::Admin | Role::SuperAdmin => {
                debug!(
                    user_id = %user_id,
                    conversation_id = %conversation_id,
                    "Admin autorisé à rejoindre une conversation privée"
                );
                Ok(())
            }
            _ => {
                warn!(
                    user_id = %user_id,
                    conversation_id = %conversation_id,
                    "Tentative de rejoindre une conversation privée par un non-membre"
                );
                Err(PermissionError::NotMember {
                    user_id,
                    conversation_id,
                }
                .into())
            }
        }
    }

    /// Vérifie si un utilisateur peut éditer un message
    ///
    /// # Arguments
    ///
    /// * `user_id` - ID de l'utilisateur
    /// * `message_id` - ID du message
    ///
    /// # Returns
    ///
    /// `Ok(())` si autorisé, erreur sinon
    ///
    /// # Règles
    ///
    /// * L'auteur du message peut toujours éditer son message
    /// * Un admin ou modérateur de la conversation peut éditer n'importe quel message
    /// * Un message supprimé ne peut pas être édité
    pub async fn can_edit_message(&self, user_id: Uuid, message_id: Uuid) -> Result<()> {
        // Récupérer le message pour vérifier l'auteur et l'état
        let message_row: Option<(Uuid, Uuid, bool)> = sqlx::query_as(
            r#"
            SELECT sender_id, conversation_id, is_deleted
            FROM messages
            WHERE id = $1
            "#,
        )
        .bind(message_id)
        .fetch_optional(&self.pool)
        .await
        .map_err(|e| ChatError::from_sqlx_error("get_message_for_edit", e))?;

        let (sender_id, conversation_id, is_deleted) =
            message_row.ok_or_else(|| ChatError::not_found("Message", &message_id.to_string()))?;

        // Un message supprimé ne peut pas être édité
        if is_deleted {
            return Err(ChatError::validation_error(
                "Un message supprimé ne peut pas être édité",
            ));
        }

        // L'auteur peut toujours éditer son message
        if sender_id == user_id {
            debug!(
                user_id = %user_id,
                message_id = %message_id,
                "Auteur autorisé à éditer son message"
            );
            return Ok(());
        }

        // Vérifier si l'utilisateur est admin ou modérateur de la conversation
        let role = self
            .user_role_in_conversation(user_id, conversation_id)
            .await?;
        match role {
            Role::Admin | Role::Moderator | Role::SuperAdmin => {
                debug!(
                    user_id = %user_id,
                    message_id = %message_id,
                    conversation_id = %conversation_id,
                    role = ?role,
                    "Admin/Modérateur autorisé à éditer le message"
                );
                Ok(())
            }
            _ => {
                warn!(
                    user_id = %user_id,
                    message_id = %message_id,
                    conversation_id = %conversation_id,
                    "Tentative d'édition d'un message par un non-auteur sans permissions"
                );
                Err(PermissionError::InsufficientPermissions {
                    action: "edit_message".to_string(),
                    conversation_id,
                }
                .into())
            }
        }
    }

    /// Vérifie si un utilisateur peut supprimer un message
    ///
    /// # Arguments
    ///
    /// * `user_id` - ID de l'utilisateur
    /// * `message_id` - ID du message
    ///
    /// # Returns
    ///
    /// `Ok(())` si autorisé, erreur sinon
    ///
    /// # Règles
    ///
    /// * L'auteur du message peut toujours supprimer son message
    /// * Un admin ou modérateur de la conversation peut supprimer n'importe quel message
    pub async fn can_delete_message(&self, user_id: Uuid, message_id: Uuid) -> Result<()> {
        // Récupérer le message pour vérifier l'auteur
        let message_row: Option<(Uuid, Uuid)> = sqlx::query_as(
            r#"
            SELECT sender_id, conversation_id
            FROM messages
            WHERE id = $1
            "#,
        )
        .bind(message_id)
        .fetch_optional(&self.pool)
        .await
        .map_err(|e| ChatError::from_sqlx_error("get_message_for_delete", e))?;

        let (sender_id, conversation_id) =
            message_row.ok_or_else(|| ChatError::not_found("Message", &message_id.to_string()))?;

        // L'auteur peut toujours supprimer son message
        if sender_id == user_id {
            debug!(
                user_id = %user_id,
                message_id = %message_id,
                "Auteur autorisé à supprimer son message"
            );
            return Ok(());
        }

        // Vérifier si l'utilisateur est admin ou modérateur de la conversation
        let role = self
            .user_role_in_conversation(user_id, conversation_id)
            .await?;
        match role {
            Role::Admin | Role::Moderator | Role::SuperAdmin => {
                debug!(
                    user_id = %user_id,
                    message_id = %message_id,
                    conversation_id = %conversation_id,
                    role = ?role,
                    "Admin/Modérateur autorisé à supprimer le message"
                );
                Ok(())
            }
            _ => {
                warn!(
                    user_id = %user_id,
                    message_id = %message_id,
                    conversation_id = %conversation_id,
                    "Tentative de suppression d'un message par un non-auteur sans permissions"
                );
                Err(PermissionError::InsufficientPermissions {
                    action: "delete_message".to_string(),
                    conversation_id,
                }
                .into())
            }
        }
    }
}

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

    // Note: Les tests nécessitent une base de données de test
    // Ils sont marqués avec #[ignore] car ils nécessitent une configuration spécifique

    #[tokio::test]
    #[ignore] // Nécessite une base de données de test
    async fn test_user_in_conversation() {
        // Ce test nécessite un pool de test et des données de test
        // let pool = create_test_pool().await;
        // let service = PermissionService::new(pool);
        // let user_id = Uuid::new_v4();
        // let conversation_id = Uuid::new_v4();
        //
        // // Ajouter l'utilisateur à la conversation
        // sqlx::query("INSERT INTO conversation_members (conversation_id, user_id, role) VALUES ($1, $2, 'member')")
        //     .bind(conversation_id)
        //     .bind(user_id)
        //     .execute(&pool)
        //     .await
        //     .unwrap();
        //
        // // Vérifier
        // let is_member = service.user_in_conversation(user_id, conversation_id).await.unwrap();
        // assert!(is_member);
    }

    #[tokio::test]
    #[ignore] // Nécessite une base de données de test
    async fn test_can_send_message_non_member() {
        // Ce test nécessite un pool de test
        // let pool = create_test_pool().await;
        // let service = PermissionService::new(pool);
        // let user_id = Uuid::new_v4();
        // let conversation_id = Uuid::new_v4();
        //
        // // Un non-membre ne peut pas envoyer de message
        // let result = service.can_send_message(user_id, conversation_id).await;
        // assert!(result.is_err());
    }
}
