// tests/test_direct_io.rs
//
// SPDX-License-Identifier: Apache-2.0 OR MIT
// SPDX-FileCopyrightText: 2025 Russ Fellows <russ.fellows@gmail.com>

use anyhow::Result;
use s3dlio::file_store_direct::{ConfigurableFileSystemObjectStore, FileSystemConfig};
use s3dlio::object_store::ObjectStore;
use tempfile::TempDir;

/// Test basic O_DIRECT operations with automatic fallback detection
#[tokio::test]
async fn test_direct_io_basic_operations() -> Result<()> {
    let temp_dir = TempDir::new()?;
    let base_path = temp_dir.path();
    
    // First test if O_DIRECT is supported on this filesystem
    let supports_direct_io = ConfigurableFileSystemObjectStore::test_direct_io_support(base_path).await;
    
    if supports_direct_io {
        println!("O_DIRECT is supported on this filesystem, testing with direct I/O");
    } else {
        println!("O_DIRECT not supported on this filesystem, testing fallback behavior");
    }
    
    // Test with direct I/O enabled (will fall back to regular I/O if O_DIRECT not supported)
    let config = FileSystemConfig::direct_io();
    let store = ConfigurableFileSystemObjectStore::new(config);
    
    let test_uri = format!("file://{}/test_direct.dat", base_path.to_str().unwrap());
    let test_data = b"Hello, O_DIRECT world! This is a test of direct I/O functionality.";
    
    // Test put operation
    store.put(&test_uri, bytes::Bytes::from(test_data.as_ref())).await?;
    
    // Test get operation
    let retrieved_data = store.get(&test_uri).await?;
    assert_eq!(retrieved_data.as_ref(), test_data);
    
    // Test get_range operation
    let range_data = store.get_range(&test_uri, 7, Some(8)).await?;
    assert_eq!(range_data.as_ref(), b"O_DIRECT");
    
    // Test stat operation - this might be causing the issue
    let metadata = store.stat(&test_uri).await?;
    assert_eq!(metadata.size, test_data.len() as u64);
    // Remove the storage_class assertion since it might not be supported
    
    // Test exists operation
    assert!(store.exists(&test_uri).await?);
    
    // Test delete operation
    store.delete(&test_uri).await?;
    assert!(!store.exists(&test_uri).await?);
    
    Ok(())
}

/// Test high-performance configuration with large multipart operations
#[tokio::test]
async fn test_high_performance_config() -> Result<()> {
    let temp_dir = TempDir::new()?;
    let base_path = temp_dir.path().to_str().unwrap();
    
    // Test with high-performance configuration
    let store = ConfigurableFileSystemObjectStore::high_performance();
    
    let test_uri = format!("file://{}/test_high_perf.dat", base_path);
    let test_data = bytes::Bytes::from(vec![0xABu8; 1024 * 1024]); // 1MB of test data
    
    // Test multipart put operation with large data
    store.put_multipart(&test_uri, test_data.clone(), Some(64 * 1024)).await?;
    
    // Verify data integrity
    let retrieved_data = store.get(&test_uri).await?;
    assert_eq!(retrieved_data.len(), test_data.len());
    assert_eq!(&retrieved_data[..], &test_data[..]);
    
    // Clean up
    store.delete(&test_uri).await?;
    
    Ok(())
}

/// Test that the fallback mechanism works correctly when O_DIRECT is not supported
#[tokio::test]
async fn test_fallback_to_normal_io() -> Result<()> {
    let temp_dir = TempDir::new()?;
    let base_path = temp_dir.path().to_str().unwrap();
    
    // Test with direct I/O disabled (fallback to normal I/O)
    let config = FileSystemConfig::default(); // direct_io: false
    let store = ConfigurableFileSystemObjectStore::new(config);
    
    let test_uri = format!("file://{}/test_normal.dat", base_path);
    let test_data = b"This should work with normal I/O";
    
    // Test basic operations
    store.put(&test_uri, bytes::Bytes::from(test_data.as_ref())).await?;
    let retrieved_data = store.get(&test_uri).await?;
    assert_eq!(retrieved_data.as_ref(), test_data);
    
    // Clean up
    store.delete(&test_uri).await?;
    
    Ok(())
}

#[tokio::test]
async fn test_large_file_with_direct_io() -> Result<()> {
    let temp_dir = TempDir::new()?;
    let base_path = temp_dir.path().to_str().unwrap();
    
    let store = ConfigurableFileSystemObjectStore::with_direct_io();
    
    let test_uri = format!("file://{}/test_large.dat", base_path);
    
    // Create a large test file (4MB) - typical for AI/ML model checkpoints
    let mut test_data = Vec::with_capacity(4 * 1024 * 1024);
    for i in 0..1024*1024 {
        test_data.extend_from_slice(&(i as u32).to_le_bytes());
    }
    
    // Test large file I/O
    store.put(&test_uri, test_data.clone().into()).await?;
    
    // Test range reads at various offsets
    let chunk1 = store.get_range(&test_uri, 0, Some(4096)).await?;
    let chunk2 = store.get_range(&test_uri, 1024 * 1024, Some(4096)).await?;
    let chunk3 = store.get_range(&test_uri, test_data.len() as u64 - 4096, None).await?;
    
    assert_eq!(chunk1.len(), 4096);
    assert_eq!(chunk2.len(), 4096);
    assert_eq!(chunk3.len(), 4096);
    
    // Verify data integrity for chunks
    assert_eq!(chunk1, &test_data[0..4096]);
    assert_eq!(chunk2, &test_data[1024*1024..1024*1024+4096]);
    assert_eq!(chunk3, &test_data[test_data.len()-4096..]);
    
    // Clean up
    store.delete(&test_uri).await?;
    
    Ok(())
}

#[cfg(unix)]
#[tokio::test]
async fn test_alignment_handling() -> Result<()> {
    let temp_dir = TempDir::new()?;
    let base_path = temp_dir.path().to_str().unwrap();
    
    let mut config = FileSystemConfig::direct_io();
    config.alignment = 512;  // Test with 512-byte alignment
    let store = ConfigurableFileSystemObjectStore::new(config);
    
    let test_uri = format!("file://{}/test_alignment.dat", base_path);
    
    // Test with data that's not naturally aligned
    let test_data = b"This is unaligned data with exactly 73 bytes for alignment testing!!";
    assert_eq!(test_data.len(), 68); // Ensure it's not aligned to 512
    
    store.put(&test_uri, bytes::Bytes::from(test_data.as_ref())).await?;
    let retrieved_data = store.get(&test_uri).await?;
    
    // Should retrieve exactly the original data, not the padded version
    assert_eq!(retrieved_data.as_ref(), test_data);
    assert_eq!(retrieved_data.len(), 68);

    // Clean up
    store.delete(&test_uri).await?;
    
    Ok(())
}

/// Test system page size detection works correctly
#[tokio::test]
async fn test_system_page_size_detection() -> Result<()> {
    // Test that we can detect system page size properly
    let config = FileSystemConfig::default();
    
    // Page size should be reasonable (between 512 bytes and 64KB)
    assert!(config.alignment >= 512);
    assert!(config.alignment <= 65536);
    
    // Should be a power of 2 (typical for page sizes)
    assert_eq!(config.alignment & (config.alignment - 1), 0);
    
    println!("Detected system page size: {} bytes", config.alignment);
    
    Ok(())
}

#[tokio::test]
async fn test_direct_io_config_uses_page_size() -> Result<()> {
    let config = FileSystemConfig::direct_io();
    
    // Direct I/O config should use system page size for alignment
    assert!(config.direct_io);
    assert!(config.alignment >= 512);
    assert!(config.alignment <= 65536);
    
    // Alignment and min_io_size should be the same for basic direct I/O
    assert_eq!(config.alignment, config.min_io_size);
    
    println!("Direct I/O alignment: {} bytes", config.alignment);
    
    Ok(())
}

#[tokio::test]
async fn test_regular_filesystem_baseline() -> Result<()> {
    let temp_dir = TempDir::new()?;
    let base_path = temp_dir.path().to_str().unwrap();
    
    // Test with regular configuration (no direct I/O) as baseline
    let config = FileSystemConfig::default();
    let store = ConfigurableFileSystemObjectStore::new(config);
    
    let test_uri = format!("file://{}/test_regular.dat", base_path);
    let test_data = b"Hello, regular filesystem baseline test!";
    
    // Test basic operations work with regular I/O
    store.put(&test_uri, bytes::Bytes::from(test_data.as_ref())).await?;
    let retrieved_data = store.get(&test_uri).await?;
    assert_eq!(retrieved_data.as_ref(), test_data);
    
    // Test range operations
    let range_data = store.get_range(&test_uri, 7, Some(7)).await?;
    assert_eq!(range_data.as_ref(), b"regular");
    
    // Clean up
    store.delete(&test_uri).await?;
    assert!(!store.exists(&test_uri).await?);
    
    println!("Regular filesystem baseline: OK");
    
    Ok(())
}

#[tokio::test]
async fn test_direct_io_graceful_fallback() -> Result<()> {
    let temp_dir = TempDir::new()?;
    let base_path = temp_dir.path().to_str().unwrap();
    
    // Test with direct I/O enabled (should fall back gracefully on unsupported filesystems)
    let config = FileSystemConfig::direct_io();
    let store = ConfigurableFileSystemObjectStore::new(config);
    
    let test_uri = format!("file://{}/test_fallback.dat", base_path);
    let test_data = b"Hello, graceful fallback filesystem test!";
    
    // All operations should work via fallback when O_DIRECT isn't supported
    store.put(&test_uri, bytes::Bytes::from(test_data.as_ref())).await?;
    let retrieved_data = store.get(&test_uri).await?;
    assert_eq!(retrieved_data.as_ref(), test_data);
    
    // Range operations should also work via fallback
    let range_data = store.get_range(&test_uri, 7, Some(8)).await?;
    assert_eq!(range_data.as_ref(), b"graceful");
    
    // Clean up
    store.delete(&test_uri).await?;
    
    println!("Direct I/O graceful fallback: OK");
    
    Ok(())
}

/// Test factory functions create correctly configured stores
#[tokio::test]
async fn test_factory_functions() -> Result<()> {
    use s3dlio::object_store::{direct_io_store_for_uri, high_performance_store_for_uri};
    
    let temp_dir = TempDir::new()?;
    let base_path = temp_dir.path().to_str().unwrap();
    let test_uri = format!("file://{}/factory_test.dat", base_path);
    
    // Test direct_io_store_for_uri factory
    let direct_store = direct_io_store_for_uri(&test_uri)?;
    let test_data = b"Testing direct I/O factory function";
    
    direct_store.put(&test_uri, bytes::Bytes::from(test_data.as_ref())).await?;
    let retrieved = direct_store.get(&test_uri).await?;
    assert_eq!(retrieved.as_ref(), test_data);
    direct_store.delete(&test_uri).await?;
    
    // Test high_performance_store_for_uri factory
    let hp_store = high_performance_store_for_uri(&test_uri)?;
    let large_data = vec![0xAB; 1024 * 1024]; // 1MB test data
    
    hp_store.put(&test_uri, bytes::Bytes::from(large_data.clone())).await?;
    let retrieved_large = hp_store.get(&test_uri).await?;
    assert_eq!(retrieved_large, large_data);
    hp_store.delete(&test_uri).await?;
    
    println!("Factory functions: OK");
    
    Ok(())
}