#[cfg(test)]
use readur::services::file_service::FileService;
#[cfg(test)]
use readur::models::Document;
#[cfg(test)]
use std::fs;
#[cfg(test)]
use tempfile::TempDir;
#[cfg(test)]
use uuid::Uuid;

#[cfg(test)]
fn create_test_file_service() -> (FileService, TempDir) {
    let temp_dir = TempDir::new().unwrap();
    let upload_path = temp_dir.path().to_string_lossy().to_string();
    let service = FileService::new(upload_path);
    (service, temp_dir)
}

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

    #[tokio::test]
    async fn test_save_file() {
        let (service, _temp_dir) = create_test_file_service();
        let filename = "test.txt";
        let data = b"Hello, World!";
        
        let result = service.save_file(filename, data).await;
        assert!(result.is_ok());
        
        let file_path = result.unwrap();
        assert!(fs::metadata(&file_path).is_ok());
        
        let saved_content = fs::read(&file_path).unwrap();
        assert_eq!(saved_content, data);
    }

    #[tokio::test]
    async fn test_save_file_with_extension() {
        let (service, _temp_dir) = create_test_file_service();
        let filename = "document.pdf";
        let data = b"PDF content";
        
        let result = service.save_file(filename, data).await;
        assert!(result.is_ok());
        
        let file_path = result.unwrap();
        assert!(file_path.ends_with(".pdf"));
    }

    #[tokio::test]
    async fn test_save_file_without_extension() {
        let (service, _temp_dir) = create_test_file_service();
        let filename = "document";
        let data = b"Some content";
        
        let result = service.save_file(filename, data).await;
        assert!(result.is_ok());
        
        let file_path = result.unwrap();
        // Should not have an extension (check just the filename part)
        let filename_part = std::path::Path::new(&file_path)
            .file_name()
            .unwrap()
            .to_str()
            .unwrap();
        assert!(!filename_part.contains('.'));
    }

    #[test]
    fn test_create_document() {
        let (service, _temp_dir) = create_test_file_service();
        let user_id = Uuid::new_v4();
        
        let document = service.create_document(
            "saved_file.pdf",
            "original_file.pdf",
            "/path/to/saved_file.pdf",
            1024,
            "application/pdf",
            user_id,
            Some("abcd1234hash".to_string()),
            None, // original_created_at
            None, // original_modified_at
            None, // source_path
            None, // source_type
            None, // source_id
            None, // file_permissions
            None, // file_owner
            None, // file_group
            None, // source_metadata
        );
        
        assert_eq!(document.filename, "saved_file.pdf");
        assert_eq!(document.original_filename, "original_file.pdf");
        assert_eq!(document.file_path, "/path/to/saved_file.pdf");
        assert_eq!(document.file_size, 1024);
        assert_eq!(document.mime_type, "application/pdf");
        assert_eq!(document.user_id, user_id);
        assert_eq!(document.file_hash, Some("abcd1234hash".to_string()));
        assert!(document.content.is_none());
        assert!(document.ocr_text.is_none());
        assert!(document.tags.is_empty());
    }

    #[test]
    fn test_is_allowed_file_type() {
        let (service, _temp_dir) = create_test_file_service();
        let allowed_types = vec![
            "pdf".to_string(),
            "txt".to_string(),
            "png".to_string(),
            "jpg".to_string(),
        ];
        
        assert!(service.is_allowed_file_type("document.pdf", &allowed_types));
        assert!(service.is_allowed_file_type("text.txt", &allowed_types));
        assert!(service.is_allowed_file_type("image.PNG", &allowed_types)); // Case insensitive
        assert!(service.is_allowed_file_type("photo.JPG", &allowed_types)); // Case insensitive
        
        assert!(!service.is_allowed_file_type("document.doc", &allowed_types));
        assert!(!service.is_allowed_file_type("archive.zip", &allowed_types));
        assert!(!service.is_allowed_file_type("noextension", &allowed_types));
    }

    #[tokio::test]
    async fn test_read_file() {
        let (service, _temp_dir) = create_test_file_service();
        let filename = "test.txt";
        let original_data = b"Hello, World!";
        
        let file_path = service.save_file(filename, original_data).await.unwrap();
        
        let result = service.read_file(&file_path).await;
        assert!(result.is_ok());
        
        let read_data = result.unwrap();
        assert_eq!(read_data, original_data);
    }

    #[tokio::test]
    async fn test_read_nonexistent_file() {
        let (service, _temp_dir) = create_test_file_service();
        let nonexistent_path = "/path/to/nonexistent/file.txt";
        
        let result = service.read_file(nonexistent_path).await;
        assert!(result.is_err());
    }
}

#[cfg(test)]
mod file_deletion_tests {
    use super::*;
    use chrono::Utc;
    use std::path::Path;
    use std::fs;

    fn create_test_document_with_files(_service: &FileService, temp_dir: &TempDir, user_id: uuid::Uuid) -> (Document, String, String, String) {
        let document_id = uuid::Uuid::new_v4();
        
        // Create main document file
        let base_path = temp_dir.path().join("documents");
        fs::create_dir_all(&base_path).unwrap();
        let main_file_path = base_path.join("test_document.pdf");
        fs::write(&main_file_path, b"PDF content").unwrap();
        
        // Create thumbnails directory and thumbnail file with correct naming
        let thumbnails_path = temp_dir.path().join("thumbnails");
        fs::create_dir_all(&thumbnails_path).unwrap();
        let thumbnail_path = thumbnails_path.join(format!("{}_thumb.jpg", document_id));
        fs::write(&thumbnail_path, b"Thumbnail content").unwrap();
        
        // Create processed_images directory and processed image file with correct naming
        let processed_dir = temp_dir.path().join("processed_images");
        fs::create_dir_all(&processed_dir).unwrap();
        let processed_path = processed_dir.join(format!("{}_processed.png", document_id));
        fs::write(&processed_path, b"Processed content").unwrap();
        
        let document = Document {
            id: document_id,
            filename: "test_document.pdf".to_string(),
            original_filename: "test_document.pdf".to_string(),
            file_path: main_file_path.to_string_lossy().to_string(),
            file_size: 1024,
            mime_type: "application/pdf".to_string(),
            content: Some("Test document content".to_string()),
            ocr_text: Some("This is extracted OCR text".to_string()),
            ocr_confidence: Some(95.5),
            ocr_word_count: Some(150),
            ocr_processing_time_ms: Some(1200),
            ocr_status: Some("completed".to_string()),
            ocr_error: None,
            ocr_completed_at: Some(Utc::now()),
            tags: vec!["test".to_string()],
            created_at: Utc::now(),
            updated_at: Utc::now(),
            user_id,
            file_hash: Some("hash123".to_string()),
            original_created_at: None,
            original_modified_at: None,
            source_path: None,
            source_type: None,
            source_id: None,
            file_permissions: None,
            file_owner: None,
            file_group: None,
            source_metadata: None,
            ocr_retry_count: None,
            ocr_failure_reason: None,
        };
        
        (
            document,
            main_file_path.to_string_lossy().to_string(),
            thumbnail_path.to_string_lossy().to_string(),
            processed_path.to_string_lossy().to_string(),
        )
    }

    #[tokio::test]
    async fn test_delete_document_files_success() {
        let (service, temp_dir) = create_test_file_service();
        let user_id = uuid::Uuid::new_v4();
        
        let (document, main_path, thumb_path, processed_path) = 
            create_test_document_with_files(&service, &temp_dir, user_id);
        
        // Verify files exist before deletion
        assert!(Path::new(&main_path).exists());
        assert!(Path::new(&thumb_path).exists());
        assert!(Path::new(&processed_path).exists());
        
        // Delete document files
        let result = service.delete_document_files(&document).await;
        assert!(result.is_ok());
        
        // Verify main file is deleted
        assert!(!Path::new(&main_path).exists());
        
        // Verify thumbnail and processed files are deleted
        assert!(!Path::new(&thumb_path).exists());
        assert!(!Path::new(&processed_path).exists());
    }

    #[tokio::test]
    async fn test_delete_document_files_main_file_missing() {
        let (service, temp_dir) = create_test_file_service();
        let user_id = uuid::Uuid::new_v4();
        
        let (document, main_path, thumb_path, processed_path) = 
            create_test_document_with_files(&service, &temp_dir, user_id);
        
        // Delete main file manually before test
        fs::remove_file(&main_path).unwrap();
        assert!(!Path::new(&main_path).exists());
        
        // Verify thumbnail and processed files still exist
        assert!(Path::new(&thumb_path).exists());
        assert!(Path::new(&processed_path).exists());
        
        // Try to delete document files (should still clean up other files)
        let result = service.delete_document_files(&document).await;
        assert!(result.is_ok());
        
        // Verify thumbnail and processed files are deleted despite main file missing
        assert!(!Path::new(&thumb_path).exists());
        assert!(!Path::new(&processed_path).exists());
    }

    #[tokio::test]
    async fn test_delete_document_files_thumbnail_missing() {
        let (service, temp_dir) = create_test_file_service();
        let user_id = uuid::Uuid::new_v4();
        
        let (document, main_path, thumb_path, processed_path) = 
            create_test_document_with_files(&service, &temp_dir, user_id);
        
        // Delete thumbnail file manually before test
        fs::remove_file(&thumb_path).unwrap();
        assert!(!Path::new(&thumb_path).exists());
        
        // Verify other files still exist
        assert!(Path::new(&main_path).exists());
        assert!(Path::new(&processed_path).exists());
        
        // Delete document files
        let result = service.delete_document_files(&document).await;
        assert!(result.is_ok());
        
        // Verify main and processed files are deleted
        assert!(!Path::new(&main_path).exists());
        assert!(!Path::new(&processed_path).exists());
    }

    #[tokio::test]
    async fn test_delete_document_files_processed_missing() {
        let (service, temp_dir) = create_test_file_service();
        let user_id = uuid::Uuid::new_v4();
        
        let (document, main_path, thumb_path, processed_path) = 
            create_test_document_with_files(&service, &temp_dir, user_id);
        
        // Delete processed file manually before test
        fs::remove_file(&processed_path).unwrap();
        assert!(!Path::new(&processed_path).exists());
        
        // Verify other files still exist
        assert!(Path::new(&main_path).exists());
        assert!(Path::new(&thumb_path).exists());
        
        // Delete document files
        let result = service.delete_document_files(&document).await;
        assert!(result.is_ok());
        
        // Verify main and thumbnail files are deleted
        assert!(!Path::new(&main_path).exists());
        assert!(!Path::new(&thumb_path).exists());
    }

    #[tokio::test]
    async fn test_delete_document_files_all_missing() {
        let (service, _temp_dir) = create_test_file_service();
        let user_id = uuid::Uuid::new_v4();
        
        let document = Document {
            id: uuid::Uuid::new_v4(),
            filename: "nonexistent.pdf".to_string(),
            original_filename: "nonexistent.pdf".to_string(),
            file_path: "/nonexistent/path/nonexistent.pdf".to_string(),
            file_size: 1024,
            mime_type: "application/pdf".to_string(),
            content: None,
            ocr_text: None,
            ocr_confidence: None,
            ocr_word_count: None,
            ocr_processing_time_ms: None,
            ocr_status: Some("pending".to_string()),
            ocr_error: None,
            ocr_completed_at: None,
            tags: vec![],
            created_at: Utc::now(),
            updated_at: Utc::now(),
            user_id,
            file_hash: None,
            original_created_at: None,
            original_modified_at: None,
            source_path: None,
            source_type: None,
            source_id: None,
            file_permissions: None,
            file_owner: None,
            file_group: None,
            source_metadata: None,
            ocr_retry_count: None,
            ocr_failure_reason: None,
        };
        
        // Try to delete nonexistent files (should not fail)
        let result = service.delete_document_files(&document).await;
        assert!(result.is_ok());
    }

    #[tokio::test]
    async fn test_delete_document_files_with_different_extensions() {
        let (service, temp_dir) = create_test_file_service();
        let user_id = uuid::Uuid::new_v4();
        let document_id = uuid::Uuid::new_v4();
        
        // Create main document file in documents directory
        let documents_path = temp_dir.path().join("documents");
        fs::create_dir_all(&documents_path).unwrap();
        let main_file_path = documents_path.join("test_image.png");
        fs::write(&main_file_path, b"PNG content").unwrap();
        
        // Create thumbnail in thumbnails directory with correct naming
        let thumbnails_path = temp_dir.path().join("thumbnails");
        fs::create_dir_all(&thumbnails_path).unwrap();
        let thumbnail_path = thumbnails_path.join(format!("{}_thumb.jpg", document_id));
        fs::write(&thumbnail_path, b"Thumbnail content").unwrap();
        
        // Create processed image in processed_images directory with correct naming
        let processed_dir = temp_dir.path().join("processed_images");
        fs::create_dir_all(&processed_dir).unwrap();
        let processed_path = processed_dir.join(format!("{}_processed.png", document_id));
        fs::write(&processed_path, b"Processed content").unwrap();
        
        let document = Document {
            id: document_id,
            filename: "test_image.png".to_string(),
            original_filename: "test_image.png".to_string(),
            file_path: main_file_path.to_string_lossy().to_string(),
            file_size: 2048,
            mime_type: "image/png".to_string(),
            content: None,
            ocr_text: Some("Image OCR text".to_string()),
            ocr_confidence: Some(88.2),
            ocr_word_count: Some(25),
            ocr_processing_time_ms: Some(800),
            ocr_status: Some("completed".to_string()),
            ocr_error: None,
            ocr_completed_at: Some(Utc::now()),
            tags: vec!["image".to_string()],
            created_at: Utc::now(),
            updated_at: Utc::now(),
            user_id,
            file_hash: Some("imagehash456".to_string()),
            original_created_at: None,
            original_modified_at: None,
            source_path: None,
            source_type: None,
            source_id: None,
            file_permissions: None,
            file_owner: None,
            file_group: None,
            source_metadata: None,
            ocr_retry_count: None,
            ocr_failure_reason: None,
        };
        
        // Verify files exist
        assert!(Path::new(&main_file_path).exists());
        assert!(Path::new(&thumbnail_path).exists());
        assert!(Path::new(&processed_path).exists());
        
        // Delete document files
        let result = service.delete_document_files(&document).await;
        assert!(result.is_ok());
        
        // Verify all files are deleted
        assert!(!Path::new(&main_file_path).exists());
        assert!(!Path::new(&thumbnail_path).exists());
        assert!(!Path::new(&processed_path).exists());
    }

    #[tokio::test]
    async fn test_delete_document_files_partial_failure_continues() {
        let (service, temp_dir) = create_test_file_service();
        let user_id = uuid::Uuid::new_v4();
        let document_id = uuid::Uuid::new_v4();
        
        // Create main file in documents directory
        let documents_path = temp_dir.path().join("documents");
        fs::create_dir_all(&documents_path).unwrap();
        let main_file_path = documents_path.join("readonly_document.pdf");
        fs::write(&main_file_path, b"PDF content").unwrap();
        
        // Create thumbnail file in thumbnails directory with correct naming
        let thumbnails_path = temp_dir.path().join("thumbnails");
        fs::create_dir_all(&thumbnails_path).unwrap();
        let thumbnail_path = thumbnails_path.join(format!("{}_thumb.jpg", document_id));
        fs::write(&thumbnail_path, b"Thumbnail content").unwrap();
        
        let document = Document {
            id: document_id,
            filename: "readonly_document.pdf".to_string(),
            original_filename: "readonly_document.pdf".to_string(),
            file_path: main_file_path.to_string_lossy().to_string(),
            file_size: 1024,
            mime_type: "application/pdf".to_string(),
            content: Some("Test content".to_string()),
            ocr_text: None,
            ocr_confidence: None,
            ocr_word_count: None,
            ocr_processing_time_ms: None,
            ocr_status: Some("pending".to_string()),
            ocr_error: None,
            ocr_completed_at: None,
            tags: vec![],
            created_at: Utc::now(),
            updated_at: Utc::now(),
            user_id,
            file_hash: Some("hash789".to_string()),
            original_created_at: None,
            original_modified_at: None,
            source_path: None,
            source_type: None,
            source_id: None,
            file_permissions: None,
            file_owner: None,
            file_group: None,
            source_metadata: None,
            ocr_retry_count: None,
            ocr_failure_reason: None,
        };
        
        // Verify files exist
        assert!(Path::new(&main_file_path).exists());
        assert!(Path::new(&thumbnail_path).exists());
        
        // Delete document files (should succeed even if some files can't be deleted)
        let result = service.delete_document_files(&document).await;
        assert!(result.is_ok());
        
        // At minimum, the function should attempt to delete all files
        // and not fail completely if one file can't be deleted
    }

    #[tokio::test]
    async fn test_delete_document_files_with_no_extension() {
        let (service, temp_dir) = create_test_file_service();
        let user_id = uuid::Uuid::new_v4();
        
        let base_path = temp_dir.path().join("documents");
        fs::create_dir_all(&base_path).unwrap();
        
        // Create document with no extension
        let main_file_path = base_path.join("document_no_ext");
        fs::write(&main_file_path, b"Content without extension").unwrap();
        
        let document = Document {
            id: uuid::Uuid::new_v4(),
            filename: "document_no_ext".to_string(),
            original_filename: "document_no_ext".to_string(),
            file_path: main_file_path.to_string_lossy().to_string(),
            file_size: 512,
            mime_type: "text/plain".to_string(),
            content: Some("Plain text content".to_string()),
            ocr_text: None,
            ocr_confidence: None,
            ocr_word_count: None,
            ocr_processing_time_ms: None,
            ocr_status: Some("not_applicable".to_string()),
            ocr_error: None,
            ocr_completed_at: None,
            tags: vec!["text".to_string()],
            created_at: Utc::now(),
            updated_at: Utc::now(),
            user_id,
            file_hash: Some("texthash".to_string()),
            original_created_at: None,
            original_modified_at: None,
            source_path: None,
            source_type: None,
            source_id: None,
            file_permissions: None,
            file_owner: None,
            file_group: None,
            source_metadata: None,
            ocr_retry_count: None,
            ocr_failure_reason: None,
        };
        
        // Verify file exists
        assert!(Path::new(&main_file_path).exists());
        
        // Delete document files
        let result = service.delete_document_files(&document).await;
        assert!(result.is_ok());
        
        // Verify file is deleted
        assert!(!Path::new(&main_file_path).exists());
    }

    #[tokio::test]
    async fn test_delete_document_files_concurrent_calls() {
        let (service, temp_dir) = create_test_file_service();
        let user_id = uuid::Uuid::new_v4();
        
        let (document, main_path, thumb_path, processed_path) = 
            create_test_document_with_files(&service, &temp_dir, user_id);
        
        // Verify files exist
        assert!(Path::new(&main_path).exists());
        assert!(Path::new(&thumb_path).exists());
        assert!(Path::new(&processed_path).exists());
        
        // Call delete_document_files concurrently
        let service_clone = service.clone();
        let document_clone = document.clone();
        
        let task1 = tokio::spawn(async move {
            service.delete_document_files(&document).await
        });
        
        let task2 = tokio::spawn(async move {
            service_clone.delete_document_files(&document_clone).await
        });
        
        // Both calls should complete successfully now that FileService handles concurrent deletions
        let result1 = task1.await.expect("Task 1 should complete");
        let result2 = task2.await.expect("Task 2 should complete");
        
        // Both deletion attempts should succeed - the improved FileService handles 
        // "file not found" errors gracefully as they indicate successful deletion by another task
        assert!(result1.is_ok(), "First deletion task should succeed: {:?}", result1);
        assert!(result2.is_ok(), "Second deletion task should succeed: {:?}", result2);
        
        // Verify files are deleted
        assert!(!Path::new(&main_path).exists());
        assert!(!Path::new(&thumb_path).exists());
        assert!(!Path::new(&processed_path).exists());
    }
}