//! Module de gestion des read receipts (marquage de messages comme lus)
//!
//! Ce module fournit un système complet pour tracker quels messages
//! ont été lus par quels utilisateurs dans quelles conversations.

use serde::{Deserialize, Serialize};
use sqlx::types::chrono::{DateTime, Utc};
use sqlx::{FromRow, Pool, Postgres};
use tracing::{debug, info, instrument};
use uuid::Uuid;

/// Représente un read receipt pour un message
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
pub struct ReadReceipt {
    pub id: Uuid,
    pub message_id: Uuid,
    pub user_id: Uuid,
    pub conversation_id: Uuid,
    pub read_at: DateTime<Utc>,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
}

/// État de lecture d'un message
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MessageReadStatus {
    Sent,
    Delivered,
    Read,
}

/// Manager pour gérer les read receipts
pub struct ReadReceiptManager {
    pool: Pool<Postgres>,
}

impl ReadReceiptManager {
    /// Crée un nouveau ReadReceiptManager
    pub fn new(pool: Pool<Postgres>) -> Self {
        Self { pool }
    }

    /// Vérifie si un utilisateur est membre d'une conversation
    #[instrument(skip(self))]
    pub async fn is_user_in_conversation(
        &self,
        user_id: Uuid,
        conversation_id: Uuid,
    ) -> Result<bool, sqlx::Error> {
        let exists: bool = sqlx::query_scalar(
            "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?;

        Ok(exists)
    }

    /// Marquer un message comme lu par un utilisateur
    ///
    /// Si le read receipt existe déjà, met à jour le timestamp `read_at`.
    /// Retourne le read receipt créé ou mis à jour.
    #[instrument(skip(self))]
    pub async fn mark_as_read(
        &self,
        user_id: Uuid,
        message_id: Uuid,
        conversation_id: Uuid,
    ) -> Result<ReadReceipt, sqlx::Error> {
        // Vérifier si le read receipt existe déjà
        let existing: Option<ReadReceipt> = sqlx::query_as::<_, ReadReceipt>(
            "SELECT id, message_id, user_id, conversation_id, read_at, created_at, updated_at
             FROM read_receipts 
             WHERE message_id = $1 AND user_id = $2",
        )
        .bind(message_id)
        .bind(user_id)
        .fetch_optional(&self.pool)
        .await?;

        if let Some(mut receipt) = existing {
            // Mettre à jour le timestamp de lecture
            let updated = sqlx::query_as::<_, ReadReceipt>(
                "UPDATE read_receipts 
                 SET read_at = NOW(), updated_at = NOW()
                 WHERE id = $1
                 RETURNING id, message_id, user_id, conversation_id, read_at, created_at, updated_at"
            )
            .bind(receipt.id)
            .fetch_one(&self.pool)
            .await?;

            debug!(
                message_id = %message_id,
                user_id = %user_id,
                conversation_id = %conversation_id,
                "Read receipt updated"
            );

            return Ok(updated);
        }

        // Créer un nouveau read receipt
        let receipt = sqlx::query_as::<_, ReadReceipt>(
            "INSERT INTO read_receipts (message_id, user_id, conversation_id, read_at, created_at, updated_at) 
             VALUES ($1, $2, $3, NOW(), NOW(), NOW())
             RETURNING id, message_id, user_id, conversation_id, read_at, created_at, updated_at"
        )
        .bind(message_id)
        .bind(user_id)
        .bind(conversation_id)
        .fetch_one(&self.pool)
        .await?;

        info!(
            message_id = %message_id,
            user_id = %user_id,
            conversation_id = %conversation_id,
            "Message marked as read"
        );

        Ok(receipt)
    }

    /// Marquer plusieurs messages comme lus (batch operation pour performance)
    #[instrument(skip(self, message_ids))]
    pub async fn mark_multiple_as_read(
        &self,
        message_ids: &[Uuid],
        user_id: Uuid,
        conversation_id: Uuid,
    ) -> Result<Vec<ReadReceipt>, sqlx::Error> {
        if message_ids.is_empty() {
            return Ok(Vec::new());
        }

        let mut tx = self.pool.begin().await?;

        // Récupérer les read receipts déjà existants pour éviter les doublons
        let existing: Vec<Uuid> = sqlx::query_scalar(
            "SELECT message_id FROM read_receipts 
             WHERE message_id = ANY($1) AND user_id = $2",
        )
        .bind(message_ids)
        .bind(user_id)
        .fetch_all(&mut *tx)
        .await?;

        let to_insert: Vec<Uuid> = message_ids
            .iter()
            .filter(|id| !existing.contains(id))
            .copied()
            .collect();

        let mut receipts = Vec::new();

        // Mettre à jour les existants
        if !existing.is_empty() {
            let updated: Vec<ReadReceipt> = sqlx::query_as::<_, ReadReceipt>(
                "UPDATE read_receipts 
                 SET read_at = NOW(), updated_at = NOW()
                 WHERE message_id = ANY($1) AND user_id = $2
                 RETURNING id, message_id, user_id, conversation_id, read_at, created_at, updated_at"
            )
            .bind(&existing)
            .bind(user_id)
            .fetch_all(&mut *tx)
            .await?;

            receipts.extend(updated);
        }

        // Insérer les nouveaux
        if !to_insert.is_empty() {
            // Pour UUID, on doit utiliser une approche différente de UNNEST
            for message_id in &to_insert {
                let receipt: ReadReceipt = sqlx::query_as::<_, ReadReceipt>(
                    "INSERT INTO read_receipts (message_id, user_id, conversation_id, read_at, created_at, updated_at) 
                     VALUES ($1, $2, $3, NOW(), NOW(), NOW())
                     RETURNING id, message_id, user_id, conversation_id, read_at, created_at, updated_at"
                )
                .bind(message_id)
                .bind(user_id)
                .bind(conversation_id)
                .fetch_one(&mut *tx)
                .await?;

                receipts.push(receipt);
            }
        }

        tx.commit().await?;

        info!(
            count = receipts.len(),
            user_id = %user_id,
            conversation_id = %conversation_id,
            "Multiple messages marked as read"
        );

        Ok(receipts)
    }

    /// Obtenir le statut de lecture d'un message pour un utilisateur
    #[instrument(skip(self))]
    pub async fn get_message_status(
        &self,
        message_id: Uuid,
        user_id: Uuid,
    ) -> Result<MessageReadStatus, sqlx::Error> {
        // Vérifier si le message a un read receipt
        let read_at: Option<DateTime<Utc>> = sqlx::query_scalar(
            "SELECT read_at FROM read_receipts WHERE message_id = $1 AND user_id = $2",
        )
        .bind(message_id)
        .bind(user_id)
        .fetch_optional(&self.pool)
        .await?;

        if read_at.is_some() {
            Ok(MessageReadStatus::Read)
        } else {
            // Pour l'instant, on suppose que si le message n'a pas de read receipt,
            // il est soit sent soit delivered
            // TODO: Implémenter un système de tracking "delivered" si nécessaire
            Ok(MessageReadStatus::Sent)
        }
    }

    /// Obtenir tous les read receipts pour un message
    #[instrument(skip(self))]
    pub async fn get_receipts_for_message(
        &self,
        message_id: Uuid,
    ) -> Result<Vec<ReadReceipt>, sqlx::Error> {
        let receipts = sqlx::query_as::<_, ReadReceipt>(
            "SELECT id, message_id, user_id, conversation_id, read_at, created_at, updated_at
             FROM read_receipts 
             WHERE message_id = $1
             ORDER BY read_at ASC",
        )
        .bind(message_id)
        .fetch_all(&self.pool)
        .await?;

        Ok(receipts)
    }

    /// Obtenir la dernière lecture d'un utilisateur dans une conversation
    #[instrument(skip(self))]
    pub async fn get_last_read_message(
        &self,
        conversation_id: Uuid,
        user_id: Uuid,
    ) -> Result<Option<Uuid>, sqlx::Error> {
        let last_message_id: Option<Uuid> = sqlx::query_scalar(
            "SELECT message_id FROM read_receipts 
             WHERE conversation_id = $1 AND user_id = $2 
             ORDER BY read_at DESC LIMIT 1",
        )
        .bind(conversation_id)
        .bind(user_id)
        .fetch_optional(&self.pool)
        .await?;

        Ok(last_message_id)
    }

    /// Obtenir le nombre de messages non lus pour un utilisateur dans une conversation
    #[instrument(skip(self))]
    pub async fn get_unread_count(
        &self,
        conversation_id: Uuid,
        user_id: Uuid,
        last_read_message_id: Option<Uuid>,
    ) -> Result<i64, sqlx::Error> {
        let count: Option<i64> = if let Some(last_id) = last_read_message_id {
            // Compter les messages après le dernier lu (qui ne sont pas de l'utilisateur)
            sqlx::query_scalar(
                "SELECT COUNT(*) FROM messages 
                 WHERE conversation_id = $1 AND id > $2 AND sender_id != $3 AND is_deleted = false",
            )
            .bind(conversation_id)
            .bind(last_id)
            .bind(user_id)
            .fetch_optional(&self.pool)
            .await?
        } else {
            // Pas de dernière lecture, compter tous les messages de la conversation
            // (qui ne sont pas de l'utilisateur)
            sqlx::query_scalar(
                "SELECT COUNT(*) FROM messages 
                 WHERE conversation_id = $1 AND sender_id != $2 AND is_deleted = false",
            )
            .bind(conversation_id)
            .bind(user_id)
            .fetch_optional(&self.pool)
            .await?
        };

        Ok(count.unwrap_or(0))
    }

    /// Obtenir un read receipt spécifique
    #[instrument(skip(self))]
    pub async fn get_receipt(
        &self,
        message_id: Uuid,
        user_id: Uuid,
    ) -> Result<Option<ReadReceipt>, sqlx::Error> {
        let receipt = sqlx::query_as::<_, ReadReceipt>(
            "SELECT id, message_id, user_id, conversation_id, read_at, created_at, updated_at
             FROM read_receipts 
             WHERE message_id = $1 AND user_id = $2",
        )
        .bind(message_id)
        .bind(user_id)
        .fetch_optional(&self.pool)
        .await?;

        Ok(receipt)
    }
}

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

    /// Setup une base de données de test
    async fn setup_test_db() -> PgPool {
        let database_url =
            std::env::var("DATABASE_URL").expect("DATABASE_URL must be set for tests");

        sqlx::PgPool::connect(&database_url)
            .await
            .expect("Failed to connect to test database")
    }

    #[tokio::test]
    #[ignore] // Nécessite une base de données de test
    async fn test_mark_as_read_creates_receipt() {
        let pool = setup_test_db().await;
        let manager = ReadReceiptManager::new(pool);

        // Créer des UUIDs de test
        let user_id = Uuid::new_v4();
        let message_id = Uuid::new_v4();
        let conversation_id = Uuid::new_v4();

        // Marquer comme lu
        let receipt = manager
            .mark_as_read(user_id, message_id, conversation_id)
            .await
            .expect("Should mark message as read");

        assert_eq!(receipt.message_id, message_id);
        assert_eq!(receipt.user_id, user_id);
        assert_eq!(receipt.conversation_id, conversation_id);
    }

    #[tokio::test]
    #[ignore] // Nécessite une base de données de test
    async fn test_mark_as_read_updates_existing() {
        let pool = setup_test_db().await;
        let manager = ReadReceiptManager::new(pool);

        let user_id = Uuid::new_v4();
        let message_id = Uuid::new_v4();
        let conversation_id = Uuid::new_v4();

        // Première lecture
        let receipt1 = manager
            .mark_as_read(user_id, message_id, conversation_id)
            .await
            .expect("Should mark message as read");

        // Attendre un peu pour que le timestamp change
        tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;

        // Deuxième lecture (devrait mettre à jour)
        let receipt2 = manager
            .mark_as_read(user_id, message_id, conversation_id)
            .await
            .expect("Should update existing receipt");

        // Le read_at devrait être mis à jour
        assert!(receipt2.read_at >= receipt1.read_at);
        assert_eq!(receipt1.id, receipt2.id); // Même ID
    }

    #[tokio::test]
    #[ignore] // Nécessite une base de données de test
    async fn test_get_receipt() {
        let pool = setup_test_db().await;
        let manager = ReadReceiptManager::new(pool);

        let user_id = Uuid::new_v4();
        let message_id = Uuid::new_v4();
        let conversation_id = Uuid::new_v4();

        // Créer un read receipt
        manager
            .mark_as_read(user_id, message_id, conversation_id)
            .await
            .expect("Should mark message as read");

        // Récupérer le read receipt
        let receipt = manager
            .get_receipt(message_id, user_id)
            .await
            .expect("Should get receipt")
            .expect("Receipt should exist");

        assert_eq!(receipt.message_id, message_id);
        assert_eq!(receipt.user_id, user_id);
    }

    #[tokio::test]
    #[ignore] // Nécessite une base de données de test
    async fn test_get_message_status() {
        let pool = setup_test_db().await;
        let manager = ReadReceiptManager::new(pool);

        let user_id = Uuid::new_v4();
        let message_id = Uuid::new_v4();
        let conversation_id = Uuid::new_v4();

        // Avant le marquage
        let status_before = manager
            .get_message_status(message_id, user_id)
            .await
            .expect("Should get status");
        assert_eq!(status_before, MessageReadStatus::Sent);

        // Après le marquage
        manager
            .mark_as_read(user_id, message_id, conversation_id)
            .await
            .expect("Should mark message as read");

        let status_after = manager
            .get_message_status(message_id, user_id)
            .await
            .expect("Should get status");
        assert_eq!(status_after, MessageReadStatus::Read);
    }

    #[tokio::test]
    #[ignore] // Nécessite une base de données de test
    async fn test_get_receipts_for_message() {
        let pool = setup_test_db().await;
        let manager = ReadReceiptManager::new(pool);

        let message_id = Uuid::new_v4();
        let conversation_id = Uuid::new_v4();
        let user1 = Uuid::new_v4();
        let user2 = Uuid::new_v4();

        // Marquer comme lu par deux utilisateurs
        manager
            .mark_as_read(user1, message_id, conversation_id)
            .await
            .expect("Should mark as read");
        manager
            .mark_as_read(user2, message_id, conversation_id)
            .await
            .expect("Should mark as read");

        // Récupérer tous les read receipts
        let receipts = manager
            .get_receipts_for_message(message_id)
            .await
            .expect("Should get receipts");

        assert_eq!(receipts.len(), 2);
        assert!(receipts.iter().any(|r| r.user_id == user1));
        assert!(receipts.iter().any(|r| r.user_id == user2));
    }
}
