//! WebSocket Tests
//!
//! Tests for WebSocket server functionality including connection management,
//! message broadcasting, and client subscriptions.

#[cfg(test)]
mod tests {
    use std::sync::Arc;

    use axum::extract::ws::{Message, WebSocket};
    use futures::{SinkExt, StreamExt};
    use serde_json;
    use tokio::sync::broadcast;
    use tokio::time::{timeout, Duration};

    use super::super::websocket::{
        ClientMessage, ContentNotification, WebSocketConfig, WebSocketServer,
    };

    /// Create a test WebSocket server
    fn create_test_server() -> Arc<WebSocketServer> {
        let config = WebSocketConfig {
            max_connections: 5,
            heartbeat_interval: 1,
            ping_timeout: 1,
            broadcast_buffer_size: 100,
        };
        Arc::new(WebSocketServer::new(config))
    }

    #[tokio::test]
    async fn test_websocket_server_creation() {
        let server = create_test_server();
        assert_eq!(server.connection_count().await, 0);
        assert!(server.get_clients_info().await.is_empty());
    }

    #[tokio::test]
    async fn test_broadcast_notification() {
        let server = create_test_server();

        let notification = ContentNotification::content_created(
            "test-id".to_string(),
            "Test Content".to_string(),
            "blog".to_string(),
            "en".to_string(),
            Some("author".to_string()),
        );

        // Should not panic even with no connected clients
        server.broadcast_notification(notification).await;
    }

    #[tokio::test]
    async fn test_content_notification_serialization() {
        let notification = ContentNotification::content_created(
            "test-id".to_string(),
            "Test Content".to_string(),
            "blog".to_string(),
            "en".to_string(),
            Some("author".to_string()),
        );

        let json = serde_json::to_string(&notification).unwrap();
        assert!(json.contains("ContentCreated"));
        assert!(json.contains("test-id"));
        assert!(json.contains("Test Content"));
        assert!(json.contains("blog"));
        assert!(json.contains("en"));
        assert!(json.contains("author"));
    }

    #[tokio::test]
    async fn test_content_updated_notification() {
        let notification = ContentNotification::content_updated(
            "test-id".to_string(),
            "Updated Content".to_string(),
            vec!["title".to_string(), "content".to_string()],
            Some("editor".to_string()),
        );

        let json = serde_json::to_string(&notification).unwrap();
        assert!(json.contains("ContentUpdated"));
        assert!(json.contains("Updated Content"));
        assert!(json.contains("title"));
        assert!(json.contains("content"));
        assert!(json.contains("editor"));
    }

    #[tokio::test]
    async fn test_content_published_notification() {
        let notification = ContentNotification::content_published(
            "test-id".to_string(),
            "Published Content".to_string(),
            true,
            Some("publisher".to_string()),
        );

        let json = serde_json::to_string(&notification).unwrap();
        assert!(json.contains("ContentPublished"));
        assert!(json.contains("Published Content"));
        assert!(json.contains("true"));
        assert!(json.contains("publisher"));
    }

    #[tokio::test]
    async fn test_cache_invalidated_notification() {
        let notification = ContentNotification::cache_invalidated(
            vec!["content".to_string(), "index".to_string()],
            vec!["en".to_string(), "es".to_string()],
            "Content updated".to_string(),
        );

        let json = serde_json::to_string(&notification).unwrap();
        assert!(json.contains("CacheInvalidated"));
        assert!(json.contains("content"));
        assert!(json.contains("index"));
        assert!(json.contains("en"));
        assert!(json.contains("es"));
        assert!(json.contains("Content updated"));
    }

    #[tokio::test]
    async fn test_system_status_notification() {
        let notification = ContentNotification::system_status(
            "healthy".to_string(),
            "System is running normally".to_string(),
        );

        let json = serde_json::to_string(&notification).unwrap();
        assert!(json.contains("SystemStatus"));
        assert!(json.contains("healthy"));
        assert!(json.contains("System is running normally"));
        assert!(json.contains("timestamp"));
    }

    #[tokio::test]
    async fn test_error_notification() {
        let notification =
            ContentNotification::error("Test error".to_string(), Some("Error details".to_string()));

        let json = serde_json::to_string(&notification).unwrap();
        assert!(json.contains("Error"));
        assert!(json.contains("Test error"));
        assert!(json.contains("Error details"));
        assert!(json.contains("timestamp"));
    }

    #[tokio::test]
    async fn test_client_message_serialization() {
        let subscribe_msg = ClientMessage::Subscribe {
            topics: vec!["blog".to_string(), "recipes".to_string()],
        };

        let json = serde_json::to_string(&subscribe_msg).unwrap();
        assert!(json.contains("Subscribe"));
        assert!(json.contains("blog"));
        assert!(json.contains("recipes"));

        let unsubscribe_msg = ClientMessage::Unsubscribe {
            topics: vec!["blog".to_string()],
        };

        let json = serde_json::to_string(&unsubscribe_msg).unwrap();
        assert!(json.contains("Unsubscribe"));
        assert!(json.contains("blog"));

        let ping_msg = ClientMessage::Ping;
        let json = serde_json::to_string(&ping_msg).unwrap();
        assert!(json.contains("Ping"));
    }

    #[tokio::test]
    async fn test_client_message_deserialization() {
        let subscribe_json = r#"{"type":"Subscribe","data":{"topics":["blog","recipes"]}}"#;
        let subscribe_msg: ClientMessage = serde_json::from_str(subscribe_json).unwrap();

        match subscribe_msg {
            ClientMessage::Subscribe { topics } => {
                assert_eq!(topics.len(), 2);
                assert!(topics.contains(&"blog".to_string()));
                assert!(topics.contains(&"recipes".to_string()));
            }
            _ => panic!("Expected Subscribe message"),
        }

        let ping_json = r#"{"type":"Ping"}"#;
        let ping_msg: ClientMessage = serde_json::from_str(ping_json).unwrap();

        match ping_msg {
            ClientMessage::Ping => {} // Expected
            _ => panic!("Expected Ping message"),
        }
    }

    #[tokio::test]
    async fn test_send_to_subscribers() {
        let server = create_test_server();

        let notification = ContentNotification::content_created(
            "test-id".to_string(),
            "Test Content".to_string(),
            "blog".to_string(),
            "en".to_string(),
            None,
        );

        let topics = vec!["blog".to_string(), "content".to_string()];

        // Should not panic even with no connected clients
        server.send_to_subscribers(notification, &topics).await;
    }

    #[tokio::test]
    async fn test_websocket_config_validation() {
        let config = WebSocketConfig {
            max_connections: 0, // Should handle edge case
            heartbeat_interval: 0,
            ping_timeout: 0,
            broadcast_buffer_size: 1,
        };

        let server = Arc::new(WebSocketServer::new(config));
        assert_eq!(server.connection_count().await, 0);
    }

    #[tokio::test]
    async fn test_notification_helper_functions() {
        // Test content_created helper
        let created = ContentNotification::content_created(
            "id1".to_string(),
            "Title 1".to_string(),
            "blog".to_string(),
            "en".to_string(),
            Some("author1".to_string()),
        );

        match created {
            ContentNotification::ContentCreated {
                id,
                title,
                content_type,
                language,
                author,
            } => {
                assert_eq!(id, "id1");
                assert_eq!(title, "Title 1");
                assert_eq!(content_type, "blog");
                assert_eq!(language, "en");
                assert_eq!(author, Some("author1".to_string()));
            }
            _ => panic!("Expected ContentCreated"),
        }

        // Test content_updated helper
        let updated = ContentNotification::content_updated(
            "id2".to_string(),
            "Title 2".to_string(),
            vec!["title".to_string()],
            Some("editor".to_string()),
        );

        match updated {
            ContentNotification::ContentUpdated {
                id,
                title,
                fields_changed,
                author,
            } => {
                assert_eq!(id, "id2");
                assert_eq!(title, "Title 2");
                assert_eq!(fields_changed, vec!["title".to_string()]);
                assert_eq!(author, Some("editor".to_string()));
            }
            _ => panic!("Expected ContentUpdated"),
        }

        // Test system_status helper
        let status = ContentNotification::system_status("ok".to_string(), "All good".to_string());

        match status {
            ContentNotification::SystemStatus {
                status,
                message,
                timestamp,
            } => {
                assert_eq!(status, "ok");
                assert_eq!(message, "All good");
                assert!(!timestamp.is_empty());
            }
            _ => panic!("Expected SystemStatus"),
        }
    }

    #[tokio::test]
    async fn test_multiple_notifications() {
        let server = create_test_server();

        let notifications = vec![
            ContentNotification::content_created(
                "id1".to_string(),
                "Post 1".to_string(),
                "blog".to_string(),
                "en".to_string(),
                None,
            ),
            ContentNotification::content_updated(
                "id2".to_string(),
                "Post 2".to_string(),
                vec!["title".to_string()],
                None,
            ),
            ContentNotification::system_status("healthy".to_string(), "System OK".to_string()),
        ];

        for notification in notifications {
            server.broadcast_notification(notification).await;
        }

        // All broadcasts should complete without error
        assert_eq!(server.connection_count().await, 0);
    }

    #[tokio::test]
    async fn test_websocket_config_default() {
        let config = WebSocketConfig::default();
        assert_eq!(config.max_connections, 100);
        assert_eq!(config.heartbeat_interval, 30);
        assert_eq!(config.ping_timeout, 10);
        assert_eq!(config.broadcast_buffer_size, 1000);
    }

    #[tokio::test]
    async fn test_broadcast_channel_overflow() {
        let config = WebSocketConfig {
            max_connections: 10,
            heartbeat_interval: 30,
            ping_timeout: 10,
            broadcast_buffer_size: 2, // Very small buffer
        };

        let server = Arc::new(WebSocketServer::new(config));

        // Send more messages than buffer size
        for i in 0..5 {
            let notification =
                ContentNotification::system_status("test".to_string(), format!("Message {}", i));
            server.broadcast_notification(notification).await;
        }

        // Should handle buffer overflow gracefully
        assert_eq!(server.connection_count().await, 0);
    }

    // Mock WebSocket connection for testing
    struct MockWebSocket {
        pub receiver: broadcast::Receiver<String>,
        pub sender: tokio::sync::mpsc::UnboundedSender<String>,
    }

    impl MockWebSocket {
        fn new() -> (
            Self,
            tokio::sync::mpsc::UnboundedReceiver<String>,
            broadcast::Sender<String>,
        ) {
            let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
            let (broadcast_tx, broadcast_rx) = broadcast::channel(100);

            (
                Self {
                    receiver: broadcast_rx,
                    sender: tx,
                },
                rx,
                broadcast_tx,
            )
        }
    }

    #[tokio::test]
    async fn test_notification_flow() {
        let server = create_test_server();

        // Create a mock subscriber to the broadcast channel
        let mut broadcast_rx = server.broadcast_tx.subscribe();

        // Send a notification
        let notification = ContentNotification::content_created(
            "test-flow".to_string(),
            "Flow Test".to_string(),
            "blog".to_string(),
            "en".to_string(),
            None,
        );

        server.broadcast_notification(notification.clone()).await;

        // Verify notification was received
        let received = timeout(Duration::from_millis(100), broadcast_rx.recv()).await;
        assert!(received.is_ok());

        let received_notification = received.unwrap().unwrap();

        match received_notification {
            ContentNotification::ContentCreated {
                id,
                title,
                content_type,
                language,
                ..
            } => {
                assert_eq!(id, "test-flow");
                assert_eq!(title, "Flow Test");
                assert_eq!(content_type, "blog");
                assert_eq!(language, "en");
            }
            _ => panic!("Expected ContentCreated notification"),
        }
    }
}
