//! Integration Tests for Dynamic CMS System
//!
//! End-to-end tests that verify the complete functionality of the dynamic CMS
//! including content management, API endpoints, WebSocket notifications, and
//! caching.

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

    use axum::{http::StatusCode, Router};
    use axum_test::TestServer;
    use serde_json;
    use tempfile::TempDir;
    use tokio::fs;
    use tokio::time::{timeout, Duration};

    use super::super::api::{CreateContentRequest, UpdateContentRequest};
    use super::super::websocket::{ContentNotification, WebSocketConfig, WebSocketServer};
    use super::super::{create_content_api_routes, ContentApiState, ContentService};
    use crate::content::{ContentIndexer, MarkdownProcessor};

    /// Create a complete test environment with content service, API server, and
    /// WebSocket server
    async fn create_integrated_test_environment() -> (TestServer, Arc<WebSocketServer>, TempDir) {
        // Create temporary content directory
        let temp_dir = TempDir::new().expect("Failed to create temp directory");
        let content_path = temp_dir.path().join("content");
        fs::create_dir_all(&content_path)
            .await
            .expect("Failed to create content directory");

        // Create test content structure
        let blog_dir = content_path.join("blog").join("en");
        fs::create_dir_all(&blog_dir)
            .await
            .expect("Failed to create blog directory");

        let recipes_dir = content_path.join("recipes").join("en");
        fs::create_dir_all(&recipes_dir)
            .await
            .expect("Failed to create recipes directory");

        // Create initial test content
        let initial_content = r#"---
title: Initial Test Post
slug: initial-test-post
category: Technology
tags: ["rust", "cms"]
published: true
featured: false
---

# Initial Test Post

This is the initial test content for integration testing.
"#;

        fs::write(blog_dir.join("initial-test-post.md"), initial_content)
            .await
            .expect("Failed to write initial content");

        // Create content service
        let processor = Arc::new(MarkdownProcessor::new());
        let indexer = Arc::new(ContentIndexer::new());

        let content_service = Arc::new(
            ContentService::new(content_path.to_str().unwrap(), processor, indexer)
                .await
                .expect("Failed to create content service"),
        );

        // Create API server
        let api_state = ContentApiState {
            content_service: Arc::clone(&content_service),
        };

        let app = Router::new()
            .nest("/api/content", create_content_api_routes())
            .with_state(api_state);

        let test_server = TestServer::new(app).unwrap();

        // Create WebSocket server
        let ws_config = WebSocketConfig {
            max_connections: 10,
            heartbeat_interval: 1,
            ping_timeout: 1,
            broadcast_buffer_size: 100,
        };
        let ws_server = Arc::new(WebSocketServer::new(ws_config));

        (test_server, ws_server, temp_dir)
    }

    #[tokio::test]
    async fn test_end_to_end_content_workflow() {
        let (server, ws_server, _temp_dir) = create_integrated_test_environment().await;

        // Step 1: List initial content
        let initial_response = server.get("/api/content/blog/en").await;
        assert_eq!(initial_response.status_code(), StatusCode::OK);

        let initial_body: serde_json::Value = initial_response.json();
        let initial_items = initial_body["data"].as_array().unwrap();
        assert_eq!(initial_items.len(), 1);
        assert_eq!(initial_items[0]["title"], "Initial Test Post");

        // Step 2: Create new content via API
        let new_content = CreateContentRequest {
            title: "Integration Test Post".to_string(),
            slug: "integration-test-post".to_string(),
            content: "# Integration Test\n\nThis post was created via API.".to_string(),
            content_type: "blog".to_string(),
            language: "en".to_string(),
            published: Some(false),
            draft: Some(true),
            featured: Some(false),
            tags: vec!["integration".to_string(), "test".to_string()],
            category: Some("Testing".to_string()),
            excerpt: Some("Integration test post.".to_string()),
            description: Some("Post created for integration testing.".to_string()),
            author: Some("Test Suite".to_string()),
        };

        let create_response = server.post("/api/content").json(&new_content).await;

        assert_eq!(create_response.status_code(), StatusCode::CREATED);
        let create_body: serde_json::Value = create_response.json();
        assert!(create_body["success"].as_bool().unwrap());

        // Step 3: Verify content was created
        let list_response = server.get("/api/content/blog/en").await;
        assert_eq!(list_response.status_code(), StatusCode::OK);

        let list_body: serde_json::Value = list_response.json();
        let items = list_body["data"].as_array().unwrap();
        assert_eq!(items.len(), 2); // Initial + newly created

        // Step 4: Get specific content
        let get_response = server
            .get("/api/content/blog/en/integration-test-post")
            .await;

        assert_eq!(get_response.status_code(), StatusCode::OK);
        let get_body: serde_json::Value = get_response.json();
        assert_eq!(get_body["data"]["title"], "Integration Test Post");
        assert!(!get_body["data"]["published"].as_bool().unwrap());

        // Step 5: Update content
        let update_request = UpdateContentRequest {
            title: Some("Updated Integration Test".to_string()),
            content: Some("# Updated Integration Test\n\nThis content was updated.".to_string()),
            published: Some(true),
            featured: Some(true),
            tags: Some(vec!["updated".to_string(), "integration".to_string()]),
            category: Some("Testing".to_string()),
            excerpt: Some("Updated integration test.".to_string()),
            description: Some("Updated via integration test.".to_string()),
            author: Some("Test Suite Updated".to_string()),
        };

        let update_response = server
            .put("/api/content/blog/en/integration-test-post")
            .json(&update_request)
            .await;

        assert_eq!(update_response.status_code(), StatusCode::OK);
        let update_body: serde_json::Value = update_response.json();
        assert_eq!(update_body["data"]["title"], "Updated Integration Test");
        assert!(update_body["data"]["published"].as_bool().unwrap());

        // Step 6: Search content
        let search_response = server.get("/api/content/search?q=integration").await;

        assert_eq!(search_response.status_code(), StatusCode::OK);
        let search_body: serde_json::Value = search_response.json();
        let search_results = search_body["data"].as_array().unwrap();
        assert!(!search_results.is_empty());

        // Step 7: Test WebSocket notifications
        let mut ws_receiver = ws_server.broadcast_tx.subscribe();

        // Simulate content creation notification
        let notification = ContentNotification::content_created(
            "integration-test-post".to_string(),
            "Updated Integration Test".to_string(),
            "blog".to_string(),
            "en".to_string(),
            Some("Test Suite Updated".to_string()),
        );

        ws_server.broadcast_notification(notification).await;

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

        // Step 8: Delete content
        let delete_response = server
            .delete("/api/content/blog/en/integration-test-post")
            .await;

        assert_eq!(delete_response.status_code(), StatusCode::OK);

        // Step 9: Verify deletion
        let final_list = server.get("/api/content/blog/en").await;
        let final_body: serde_json::Value = final_list.json();
        let final_items = final_body["data"].as_array().unwrap();
        assert_eq!(final_items.len(), 1); // Back to just initial content
    }

    #[tokio::test]
    async fn test_content_caching_with_api() {
        let (server, _ws_server, _temp_dir) = create_integrated_test_environment().await;

        // First request - populate cache
        let start_time = std::time::Instant::now();
        let response1 = server.get("/api/content/blog/en/initial-test-post").await;
        let first_duration = start_time.elapsed();

        assert_eq!(response1.status_code(), StatusCode::OK);
        let body1: serde_json::Value = response1.json();
        assert_eq!(body1["data"]["title"], "Initial Test Post");

        // Second request - should use cache
        let start_time = std::time::Instant::now();
        let response2 = server.get("/api/content/blog/en/initial-test-post").await;
        let second_duration = start_time.elapsed();

        assert_eq!(response2.status_code(), StatusCode::OK);
        let body2: serde_json::Value = response2.json();
        assert_eq!(body2["data"]["title"], "Initial Test Post");

        // Both requests should return the same data
        assert_eq!(body1["data"]["content"], body2["data"]["content"]);
    }

    #[tokio::test]
    async fn test_multi_language_content_workflow() {
        let (server, _ws_server, temp_dir) = create_integrated_test_environment().await;

        // Create Spanish content structure
        let content_path = temp_dir.path().join("content");
        let es_blog_dir = content_path.join("blog").join("es");
        fs::create_dir_all(&es_blog_dir)
            .await
            .expect("Failed to create Spanish blog directory");

        // Create Spanish content
        let spanish_content = r#"---
title: Contenido de Prueba
slug: contenido-de-prueba
category: Tecnología
tags: ["rust", "cms", "español"]
published: true
featured: false
---

# Contenido de Prueba

Este es contenido de prueba en español.
"#;

        fs::write(es_blog_dir.join("contenido-de-prueba.md"), spanish_content)
            .await
            .expect("Failed to write Spanish content");

        // Wait a moment for file system watcher to pick up changes
        tokio::time::sleep(Duration::from_millis(100)).await;

        // Create Spanish content via API
        let spanish_content_request = CreateContentRequest {
            title: "Nuevo Contenido".to_string(),
            slug: "nuevo-contenido".to_string(),
            content: "# Nuevo Contenido\n\nEste es contenido nuevo en español.".to_string(),
            content_type: "blog".to_string(),
            language: "es".to_string(),
            published: Some(true),
            draft: Some(false),
            featured: Some(false),
            tags: vec!["nuevo".to_string(), "español".to_string()],
            category: Some("Tecnología".to_string()),
            excerpt: Some("Contenido nuevo en español.".to_string()),
            description: Some("Contenido creado para pruebas.".to_string()),
            author: Some("Autor de Prueba".to_string()),
        };

        let create_response = server
            .post("/api/content")
            .json(&spanish_content_request)
            .await;

        assert_eq!(create_response.status_code(), StatusCode::CREATED);

        // List Spanish content
        let spanish_list = server.get("/api/content/blog/es").await;
        assert_eq!(spanish_list.status_code(), StatusCode::OK);

        let spanish_body: serde_json::Value = spanish_list.json();
        let spanish_items = spanish_body["data"].as_array().unwrap();
        assert!(spanish_items.len() >= 1);

        // List English content (should be separate)
        let english_list = server.get("/api/content/blog/en").await;
        assert_eq!(english_list.status_code(), StatusCode::OK);

        let english_body: serde_json::Value = english_list.json();
        let english_items = english_body["data"].as_array().unwrap();
        assert_eq!(english_items.len(), 1); // Only the initial English content
    }

    #[tokio::test]
    async fn test_websocket_content_notifications() {
        let (_server, ws_server, _temp_dir) = create_integrated_test_environment().await;

        // Subscribe to notifications
        let mut receiver1 = ws_server.broadcast_tx.subscribe();
        let mut receiver2 = ws_server.broadcast_tx.subscribe();

        // Test different notification types
        let notifications = vec![
            ContentNotification::content_created(
                "test-1".to_string(),
                "Test Post 1".to_string(),
                "blog".to_string(),
                "en".to_string(),
                Some("Author 1".to_string()),
            ),
            ContentNotification::content_updated(
                "test-1".to_string(),
                "Updated Test Post 1".to_string(),
                vec!["title".to_string(), "content".to_string()],
                Some("Editor 1".to_string()),
            ),
            ContentNotification::content_published(
                "test-1".to_string(),
                "Published Test Post 1".to_string(),
                true,
                Some("Publisher 1".to_string()),
            ),
            ContentNotification::cache_invalidated(
                vec!["content".to_string()],
                vec!["en".to_string()],
                "Content updated".to_string(),
            ),
        ];

        // Send all notifications
        for notification in notifications {
            ws_server.broadcast_notification(notification).await;
        }

        // Verify both receivers got all notifications
        for _ in 0..4 {
            let recv1 = timeout(Duration::from_millis(100), receiver1.recv()).await;
            let recv2 = timeout(Duration::from_millis(100), receiver2.recv()).await;

            assert!(recv1.is_ok() && recv1.unwrap().is_ok());
            assert!(recv2.is_ok() && recv2.unwrap().is_ok());
        }
    }

    #[tokio::test]
    async fn test_concurrent_operations() {
        let (server, ws_server, _temp_dir) = create_integrated_test_environment().await;

        // Create multiple concurrent tasks
        let mut handles = vec![];

        // Concurrent API requests
        for i in 0..5 {
            let server_clone = server.clone();
            let handle = tokio::spawn(async move {
                match i % 3 {
                    0 => server_clone.get("/api/content/blog/en").await,
                    1 => {
                        server_clone
                            .get("/api/content/blog/en/initial-test-post")
                            .await
                    }
                    _ => server_clone.get("/api/content/search?q=test").await,
                }
            });
            handles.push(handle);
        }

        // Concurrent WebSocket notifications
        for i in 0..5 {
            let ws_clone = Arc::clone(&ws_server);
            let handle = tokio::spawn(async move {
                let notification = ContentNotification::system_status(
                    "test".to_string(),
                    format!("Concurrent test {}", i),
                );
                ws_clone.broadcast_notification(notification).await;
            });
            handles.push(handle);
        }

        // Wait for all operations to complete
        let results = futures::future::join_all(handles).await;

        // All operations should succeed
        for (i, result) in results.iter().enumerate() {
            assert!(result.is_ok(), "Task {} failed: {:?}", i, result);

            // Check API responses (first 5 tasks)
            if i < 5 {
                if let Ok(response) = result {
                    assert_eq!(response.status_code(), StatusCode::OK);
                }
            }
        }
    }

    #[tokio::test]
    async fn test_error_handling_and_recovery() {
        let (server, ws_server, _temp_dir) = create_integrated_test_environment().await;

        // Test 404 for non-existent content
        let not_found = server.get("/api/content/blog/en/does-not-exist").await;
        assert_eq!(not_found.status_code(), StatusCode::NOT_FOUND);

        // Test invalid content type
        let invalid_type = server.get("/api/content/invalid-type/en").await;
        assert_eq!(invalid_type.status_code(), StatusCode::BAD_REQUEST);

        // Test invalid JSON in create request
        let invalid_json = server.post("/api/content").body("{invalid json}").await;
        assert_eq!(invalid_json.status_code(), StatusCode::BAD_REQUEST);

        // Test WebSocket error notification
        let error_notification = ContentNotification::error(
            "Test error".to_string(),
            Some("This is a test error for integration testing".to_string()),
        );

        ws_server.broadcast_notification(error_notification).await;

        // Verify system continues to work after errors
        let recovery_response = server.get("/api/content/blog/en").await;
        assert_eq!(recovery_response.status_code(), StatusCode::OK);
    }

    #[tokio::test]
    async fn test_content_validation_and_sanitization() {
        let (server, _ws_server, _temp_dir) = create_integrated_test_environment().await;

        // Test creating content with potentially problematic data
        let tricky_content = CreateContentRequest {
            title: "Test <script>alert('xss')</script> Content".to_string(),
            slug: "test-content-with-script".to_string(),
            content: "# Test Content\n\n<script>alert('xss')</script>\n\nNormal content."
                .to_string(),
            content_type: "blog".to_string(),
            language: "en".to_string(),
            published: Some(true),
            draft: Some(false),
            featured: Some(false),
            tags: vec!["test".to_string(), "security".to_string()],
            category: Some("Security".to_string()),
            excerpt: Some("Testing content validation.".to_string()),
            description: Some("Content with potentially problematic data.".to_string()),
            author: Some("Security Tester".to_string()),
        };

        let create_response = server.post("/api/content").json(&tricky_content).await;

        // Content should be created (the application should handle sanitization)
        assert_eq!(create_response.status_code(), StatusCode::CREATED);

        // Verify content exists and can be retrieved
        let get_response = server
            .get("/api/content/blog/en/test-content-with-script")
            .await;

        assert_eq!(get_response.status_code(), StatusCode::OK);
        let get_body: serde_json::Value = get_response.json();

        // Title should be preserved (sanitization happens at render time)
        assert!(get_body["data"]["title"].as_str().unwrap().contains("Test"));
        assert!(get_body["data"]["content"]
            .as_str()
            .unwrap()
            .contains("Normal content"));
    }
}
