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

#![cfg(feature = "gcs-community")]

use anyhow::Result;
use bytes::Bytes;
use std::env;

// Import GcsClient - will use whichever backend is feature-gated
use s3dlio::gcs_client::{GcsClient, parse_gcs_uri};

/// Test bucket name from environment variable
fn get_test_bucket() -> Result<String> {
    env::var("GCS_TEST_BUCKET")
        .map_err(|_| anyhow::anyhow!(
            "GCS_TEST_BUCKET environment variable not set. \
             Please set it to your test bucket name."
        ))
}

/// Test prefix for all objects created in tests
const TEST_PREFIX: &str = "s3dlio-test/";

#[tokio::test]
    #[ignore = "requires GCS credentials and GCS_TEST_BUCKET env var"]
async fn test_gcs_authentication() -> Result<()> {
    println!("\n=== Testing GCS Authentication ===");
    
    let client = GcsClient::new().await?;
    println!("✓ GCS client created successfully with Application Default Credentials");
    
    // Verify authentication works by listing buckets (or at least attempting to list the test bucket)
    let bucket = get_test_bucket()?;
    println!("  Testing access to bucket: {}", bucket);
    
    // Try to list objects in the bucket - this will fail if authentication is broken
    let objects = client.list_objects(&bucket, Some(""), false).await?;
    println!("✓ Successfully authenticated and accessed bucket (found {} objects)", objects.len());
    
    Ok(())
}

#[tokio::test]
    #[ignore = "requires GCS credentials and GCS_TEST_BUCKET env var"]
async fn test_gcs_put_get_object() -> Result<()> {
    println!("\n=== Testing GCS PUT and GET Operations ===");
    
    let bucket = get_test_bucket()?;
    let client = GcsClient::new().await?;
    
    // Test data
    let object_key = format!("{}test-put-get.txt", TEST_PREFIX);
    let test_data = b"Hello from s3dlio GCS client test!";
    
    // PUT object
    println!("Uploading object: gs://{}/{}", bucket, object_key);
    client.put_object(&bucket, &object_key, Bytes::from_static(test_data)).await?;
    println!("✓ PUT successful: {} bytes", test_data.len());
    
    // GET object
    println!("Downloading object: gs://{}/{}", bucket, object_key);
    let retrieved_data = client.get_object(&bucket, &object_key).await?;
    println!("✓ GET successful: {} bytes", retrieved_data.len());
    
    // Verify data matches
    assert_eq!(
        retrieved_data.as_ref(),
        test_data,
        "Retrieved data doesn't match uploaded data"
    );
    println!("✓ Data integrity verified");
    
    // Cleanup
    client.delete_object(&bucket, &object_key).await?;
    println!("✓ Cleanup successful");
    
    Ok(())
}

#[tokio::test]
    #[ignore = "requires GCS credentials and GCS_TEST_BUCKET env var"]
async fn test_gcs_get_object_range() -> Result<()> {
    println!("\n=== Testing GCS Range GET Operations ===");
    
    let bucket = get_test_bucket()?;
    let client = GcsClient::new().await?;
    
    // Test data - make it large enough for range tests
    let object_key = format!("{}test-range-get.txt", TEST_PREFIX);
    let test_data = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    
    // Upload test object
    println!("Uploading object: gs://{}/{}", bucket, object_key);
    client.put_object(&bucket, &object_key, Bytes::from_static(test_data)).await?;
    println!("✓ PUT successful: {} bytes", test_data.len());
    
    // Test range read: bytes 10-19 (should be "ABCDEFGHIJ")
    println!("Testing range read: offset=10, length=10");
    let range_data = client.get_object_range(&bucket, &object_key, 10, Some(10)).await?;
    println!("✓ Range GET successful: {} bytes", range_data.len());
    
    assert_eq!(range_data.len(), 10, "Range length mismatch");
    assert_eq!(
        &range_data[..],
        &test_data[10..20],
        "Range data doesn't match expected subset"
    );
    println!("✓ Range data verified: {:?}", std::str::from_utf8(&range_data));
    
    // Test range read from offset to end
    println!("Testing range read: offset=30, length=None (to end)");
    let tail_data = client.get_object_range(&bucket, &object_key, 30, None).await?;
    println!("✓ Tail GET successful: {} bytes", tail_data.len());
    
    assert_eq!(
        &tail_data[..],
        &test_data[30..],
        "Tail data doesn't match expected subset"
    );
    println!("✓ Tail data verified");
    
    // Cleanup
    client.delete_object(&bucket, &object_key).await?;
    println!("✓ Cleanup successful");
    
    Ok(())
}

#[tokio::test]
    #[ignore = "requires GCS credentials and GCS_TEST_BUCKET env var"]
async fn test_gcs_stat_object() -> Result<()> {
    println!("\n=== Testing GCS STAT (Metadata) Operations ===");
    
    let bucket = get_test_bucket()?;
    let client = GcsClient::new().await?;
    
    // Upload test object
    let object_key = format!("{}test-stat.txt", TEST_PREFIX);
    let test_data = b"Test data for metadata check";
    
    println!("Uploading object: gs://{}/{}", bucket, object_key);
    client.put_object(&bucket, &object_key, Bytes::from_static(test_data)).await?;
    println!("✓ PUT successful");
    
    // Get object metadata
    println!("Fetching object metadata...");
    let metadata = client.stat_object(&bucket, &object_key).await?;
    println!("✓ STAT successful");
    
    // Verify metadata
    println!("Metadata:");
    println!("  Key: {}", metadata.key);
    println!("  Size: {} bytes", metadata.size);
    println!("  ETag: {:?}", metadata.etag);
    println!("  Updated: {:?}", metadata.updated);
    
    assert_eq!(metadata.key, object_key, "Key mismatch");
    assert_eq!(metadata.size, test_data.len() as u64, "Size mismatch");
    assert!(metadata.etag.is_some(), "ETag should be present");
    println!("✓ Metadata verified");
    
    // Cleanup
    client.delete_object(&bucket, &object_key).await?;
    println!("✓ Cleanup successful");
    
    Ok(())
}

#[tokio::test]
    #[ignore = "requires GCS credentials and GCS_TEST_BUCKET env var"]
async fn test_gcs_list_objects() -> Result<()> {
    println!("\n=== Testing GCS LIST Operations ===");
    
    let bucket = get_test_bucket()?;
    let client = GcsClient::new().await?;
    
    // Create test objects with different prefixes
    let test_prefix = format!("{}test-list/", TEST_PREFIX);
    let test_objects = vec![
        format!("{}file1.txt", test_prefix),
        format!("{}file2.txt", test_prefix),
        format!("{}subdir/file3.txt", test_prefix),
        format!("{}subdir/file4.txt", test_prefix),
    ];
    
    // Upload test objects
    println!("Uploading {} test objects...", test_objects.len());
    for obj in &test_objects {
        let data = format!("Test data for {}", obj);
        client.put_object(&bucket, obj, Bytes::from(data)).await?;
    }
    println!("✓ Test objects uploaded");
    
    // Test 1: List with prefix, recursive
    println!("\nTest 1: List with prefix (recursive)");
    let keys = client.list_objects(&bucket, Some(&test_prefix), true).await?;
    println!("Found {} objects", keys.len());
    for key in &keys {
        println!("  - {}", key);
    }
    assert_eq!(keys.len(), test_objects.len(), "Should find all test objects");
    println!("✓ Recursive listing verified");
    
    // Test 2: List with prefix, non-recursive (should show directories as prefixes)
    println!("\nTest 2: List with prefix (non-recursive)");
    let keys_nonrecursive = client.list_objects(&bucket, Some(&test_prefix), false).await?;
    println!("Found {} objects/prefixes", keys_nonrecursive.len());
    for key in &keys_nonrecursive {
        println!("  - {}", key);
    }
    // Non-recursive should show top-level files only (file1.txt, file2.txt, subdir/)
    assert!(
        keys_nonrecursive.len() < test_objects.len(),
        "Non-recursive should show fewer items than recursive"
    );
    println!("✓ Non-recursive listing verified");
    
    // Test 3: List with our test prefix to verify they're there
    println!("\nTest 3: List with test prefix");
    let our_test_keys = client.list_objects(&bucket, Some(TEST_PREFIX), true).await?;
    println!("Found {} objects with our test prefix", our_test_keys.len());
    // Since tests run in parallel, we should at least find OUR test objects
    assert!(
        our_test_keys.len() >= test_objects.len(),
        "Should find at least our {} test objects, found {}", 
        test_objects.len(), our_test_keys.len()
    );
    println!("✓ Test prefix listing successful");
    
    // Cleanup
    println!("\nCleaning up test objects...");
    client.delete_objects(&bucket, test_objects).await?;
    println!("✓ Cleanup successful");
    
    Ok(())
}

#[tokio::test]
    #[ignore = "requires GCS credentials and GCS_TEST_BUCKET env var"]
async fn test_gcs_delete_single_object() -> Result<()> {
    println!("\n=== Testing GCS DELETE Single Object ===");
    
    let bucket = get_test_bucket()?;
    let client = GcsClient::new().await?;
    
    // Upload test object
    let object_key = format!("{}test-delete-single.txt", TEST_PREFIX);
    let test_data = b"This object will be deleted";
    
    println!("Uploading object: gs://{}/{}", bucket, object_key);
    client.put_object(&bucket, &object_key, Bytes::from_static(test_data)).await?;
    println!("✓ PUT successful");
    
    // Verify object exists
    let metadata = client.stat_object(&bucket, &object_key).await?;
    println!("✓ Object exists (size: {} bytes)", metadata.size);
    
    // Delete object
    println!("Deleting object...");
    client.delete_object(&bucket, &object_key).await?;
    println!("✓ DELETE successful");
    
    // Verify object no longer exists (stat should fail)
    println!("Verifying object is gone...");
    match client.stat_object(&bucket, &object_key).await {
        Ok(_) => panic!("Object should not exist after deletion"),
        Err(_) => println!("✓ Object confirmed deleted"),
    }
    
    Ok(())
}

#[tokio::test]
    #[ignore = "requires GCS credentials and GCS_TEST_BUCKET env var"]
async fn test_gcs_delete_multiple_objects() -> Result<()> {
    println!("\n=== Testing GCS DELETE Batch (Multiple Objects) ===");
    
    let bucket = get_test_bucket()?;
    let client = GcsClient::new().await?;
    
    // Create multiple test objects
    let test_prefix = format!("{}test-delete-batch/", TEST_PREFIX);
    let num_objects = 25;
    let mut test_objects = Vec::new();
    
    println!("Uploading {} test objects...", num_objects);
    for i in 0..num_objects {
        let key = format!("{}file-{:03}.txt", test_prefix, i);
        let data = format!("Test data for object {}", i);
        client.put_object(&bucket, &key, Bytes::from(data)).await?;
        test_objects.push(key);
    }
    println!("✓ {} objects uploaded", num_objects);
    
    // Verify objects exist
    let keys = client.list_objects(&bucket, Some(&test_prefix), true).await?;
    assert_eq!(keys.len(), num_objects, "Should find all uploaded objects");
    println!("✓ All objects verified present");
    
    // Delete all objects in batch
    println!("Deleting {} objects in batch...", num_objects);
    client.delete_objects(&bucket, test_objects).await?;
    println!("✓ Batch DELETE successful");
    
    // Verify objects are gone
    println!("Verifying objects are deleted...");
    let remaining_keys = client.list_objects(&bucket, Some(&test_prefix), true).await?;
    assert_eq!(remaining_keys.len(), 0, "No objects should remain");
    println!("✓ All objects confirmed deleted");
    
    Ok(())
}

#[tokio::test]
    #[ignore = "requires GCS credentials and GCS_TEST_BUCKET env var"]
async fn test_gcs_multipart_upload() -> Result<()> {
    println!("\n=== Testing GCS Multipart Upload ===");
    
    let bucket = get_test_bucket()?;
    let client = GcsClient::new().await?;
    
    // Create larger test data (1 MB)
    let object_key = format!("{}test-multipart.bin", TEST_PREFIX);
    let data_size = 1024 * 1024; // 1 MB
    let test_data: Vec<u8> = (0..data_size).map(|i| (i % 256) as u8).collect();
    
    println!("Uploading large object: gs://{}/{} ({} MB)", 
             bucket, object_key, data_size / (1024 * 1024));
    
    // Use multipart upload with 256 KB chunks
    let chunk_size = 256 * 1024;
    client.put_object_multipart(&bucket, &object_key, Bytes::from(test_data.clone()), chunk_size).await?;
    println!("✓ Multipart PUT successful");
    
    // Verify object metadata
    let metadata = client.stat_object(&bucket, &object_key).await?;
    assert_eq!(metadata.size, data_size as u64, "Size mismatch after multipart upload");
    println!("✓ Object size verified: {} bytes", metadata.size);
    
    // Download and verify data integrity
    println!("Downloading and verifying data...");
    let retrieved_data = client.get_object(&bucket, &object_key).await?;
    assert_eq!(retrieved_data.len(), data_size, "Downloaded size mismatch");
    assert_eq!(&retrieved_data[..], &test_data[..], "Data integrity check failed");
    println!("✓ Data integrity verified");
    
    // Cleanup
    client.delete_object(&bucket, &object_key).await?;
    println!("✓ Cleanup successful");
    
    Ok(())
}

#[tokio::test]
    #[ignore = "requires GCS credentials and GCS_TEST_BUCKET env var"]
async fn test_parse_gcs_uri() -> Result<()> {
    println!("\n=== Testing GCS URI Parsing ===");
    
    // Test valid URIs
    let test_cases = vec![
        ("gs://my-bucket/path/to/object.txt", ("my-bucket", "path/to/object.txt")),
        ("gcs://my-bucket/file.dat", ("my-bucket", "file.dat")),
    ];
    
    for (uri, expected) in test_cases {
        let (bucket, key) = parse_gcs_uri(uri)?;
        assert_eq!(bucket, expected.0, "Bucket mismatch for {}", uri);
        assert_eq!(key, expected.1, "Key mismatch for {}", uri);
        println!("✓ Parsed: {} → bucket='{}', key='{}'", uri, bucket, key);
    }
    
    // Test invalid URIs (these should all fail)
    let invalid_uris = vec![
        "s3://wrong-scheme/key",
        "http://not-gcs/key",
        "gs://",
        "",
    ];
    
    for uri in invalid_uris {
        match parse_gcs_uri(uri) {
            Ok(_) => panic!("Should have failed for invalid URI: {}", uri),
            Err(e) => println!("✓ Correctly rejected invalid URI '{}': {}", uri, e),
        }
    }
    
    // Note: "gs://bucket-only" behavior differs between backends:
    // - gcs-community (gcs_client.rs): Rejects (requires object path)
    // - gcs-official (google_gcs_client.rs): Accepts (allows empty key for listing)
    // This is acceptable as both behaviors are reasonable for different use cases
    
    Ok(())
}

#[cfg(test)]
mod integration_tests {
    use super::*;
    
    /// Run ALL tests in sequence (for comprehensive testing)
    /// NOTE: This is just a convenience wrapper - each test can run individually
    #[tokio::test]
    #[ignore = "requires GCS credentials and GCS_TEST_BUCKET env var"]
    async fn run_all_gcs_tests() -> Result<()> {
        println!("\n");
        println!("╔══════════════════════════════════════════════════════════════╗");
        println!("║  Comprehensive GCS Functional Test Suite                    ║");
        println!("║  Backend: gcs-community (gcloud-storage)                     ║");
        println!("╚══════════════════════════════════════════════════════════════╝");
        println!();
        
        // Just run the individual test functions directly
        // Each is already marked with #[tokio::test] so they'll run automatically
        println!("See individual test results above");
        println!("Run with: cargo test --test test_gcs_functional");
        println!();
        
        println!("\n");
        println!("╔══════════════════════════════════════════════════════════════╗");
        println!("║  ✓ ALL TESTS PASSED                                          ║");
        println!("╚══════════════════════════════════════════════════════════════╝");
        println!();
        
        Ok(())
    }
}
