//! Comprehensive tests for StorageFactory and multi-tier storage
//!
//! Tests factory pattern for creating storage instances and multi-tier
//! storage functionality (local primary + S3 secondary).

use std::sync::Arc;

use storage::local::{LocalStorage, LocalStorageConfig};
use storage::{Storage, StorageFactory, StorageProvider};
use tempfile::TempDir;

#[tokio::test]
async fn test_factory_create_local_storage() {
    let temp_dir = TempDir::new().expect("Failed to create temp dir");

    let config = LocalStorageConfig {
        base_path: temp_dir.path().to_path_buf(),
        ..Default::default()
    };

    let provider = StorageProvider::Local(config);

    let storage = StorageFactory::create(provider, None)
        .await
        .expect("Failed to create local storage");

    // Test basic operations
    storage.store("test.txt", b"data").await.unwrap();
    let data = storage.retrieve("test.txt").await.unwrap();
    assert_eq!(data, b"data");
}

#[tokio::test]
async fn test_factory_create_multi_tier_storage() {
    let temp_dir1 = TempDir::new().expect("Failed to create temp dir 1");
    let temp_dir2 = TempDir::new().expect("Failed to create temp dir 2");

    let primary_config = LocalStorageConfig {
        base_path: temp_dir1.path().to_path_buf(),
        ..Default::default()
    };

    let secondary_config = LocalStorageConfig {
        base_path: temp_dir2.path().to_path_buf(),
        ..Default::default()
    };

    let provider = StorageProvider::MultiTier {
        primary: Box::new(StorageProvider::Local(primary_config)),
        secondary: Box::new(StorageProvider::Local(secondary_config)),
    };

    let storage = StorageFactory::create(provider, None)
        .await
        .expect("Failed to create multi-tier storage");

    // Store should go to both
    storage.store("multi.txt", b"multi_data").await.unwrap();

    // Should retrieve from primary
    let data = storage.retrieve("multi.txt").await.unwrap();
    assert_eq!(data, b"multi_data");
}

#[tokio::test]
async fn test_multi_tier_fallback_to_secondary() {
    let temp_dir1 = TempDir::new().unwrap();
    let temp_dir2 = TempDir::new().unwrap();

    let config1 = LocalStorageConfig {
        base_path: temp_dir1.path().to_path_buf(),
        ..Default::default()
    };
    let config2 = LocalStorageConfig {
        base_path: temp_dir2.path().to_path_buf(),
        ..Default::default()
    };

    let primary = LocalStorage::new(config1).await.unwrap();
    let secondary = LocalStorage::new(config2).await.unwrap();

    // Store only in secondary
    secondary
        .store("secondary_only.txt", b"secondary_data")
        .await
        .unwrap();

    let multi_tier = storage::MultiTierStorage::new(Box::new(primary), Box::new(secondary));

    // Should fallback to secondary
    let data = multi_tier
        .retrieve("secondary_only.txt")
        .await
        .expect("Failed to retrieve from secondary");
    assert_eq!(data, b"secondary_data");
}

#[tokio::test]
async fn test_multi_tier_exists_primary() {
    let temp_dir1 = TempDir::new().unwrap();
    let temp_dir2 = TempDir::new().unwrap();

    let config1 = LocalStorageConfig {
        base_path: temp_dir1.path().to_path_buf(),
        ..Default::default()
    };
    let config2 = LocalStorageConfig {
        base_path: temp_dir2.path().to_path_buf(),
        ..Default::default()
    };

    let primary = LocalStorage::new(config1).await.unwrap();
    let secondary = LocalStorage::new(config2).await.unwrap();

    // Store in primary
    primary.store("primary.txt", b"data").await.unwrap();

    let multi_tier = storage::MultiTierStorage::new(Box::new(primary), Box::new(secondary));

    // Should find in primary
    let exists = multi_tier.exists("primary.txt").await.unwrap();
    assert!(exists);
}

#[tokio::test]
async fn test_multi_tier_exists_secondary() {
    let temp_dir1 = TempDir::new().unwrap();
    let temp_dir2 = TempDir::new().unwrap();

    let config1 = LocalStorageConfig {
        base_path: temp_dir1.path().to_path_buf(),
        ..Default::default()
    };
    let config2 = LocalStorageConfig {
        base_path: temp_dir2.path().to_path_buf(),
        ..Default::default()
    };

    let primary = LocalStorage::new(config1).await.unwrap();
    let secondary = LocalStorage::new(config2).await.unwrap();

    // Store only in secondary
    secondary.store("secondary.txt", b"data").await.unwrap();

    let multi_tier = storage::MultiTierStorage::new(Box::new(primary), Box::new(secondary));

    // Should find in secondary
    let exists = multi_tier.exists("secondary.txt").await.unwrap();
    assert!(exists);
}

#[tokio::test]
async fn test_multi_tier_exists_neither() {
    let temp_dir1 = TempDir::new().unwrap();
    let temp_dir2 = TempDir::new().unwrap();

    let config1 = LocalStorageConfig {
        base_path: temp_dir1.path().to_path_buf(),
        ..Default::default()
    };
    let config2 = LocalStorageConfig {
        base_path: temp_dir2.path().to_path_buf(),
        ..Default::default()
    };

    let primary = LocalStorage::new(config1).await.unwrap();
    let secondary = LocalStorage::new(config2).await.unwrap();

    let multi_tier = storage::MultiTierStorage::new(Box::new(primary), Box::new(secondary));

    // Should not find nonexistent file
    let exists = multi_tier.exists("nonexistent.txt").await.unwrap();
    assert!(!exists);
}

#[tokio::test]
async fn test_multi_tier_delete_from_both() {
    let temp_dir1 = TempDir::new().unwrap();
    let temp_dir2 = TempDir::new().unwrap();

    let config1 = LocalStorageConfig {
        base_path: temp_dir1.path().to_path_buf(),
        ..Default::default()
    };
    let config2 = LocalStorageConfig {
        base_path: temp_dir2.path().to_path_buf(),
        ..Default::default()
    };

    let primary = LocalStorage::new(config1).await.unwrap();
    let secondary = LocalStorage::new(config2).await.unwrap();

    // Store in both
    primary.store("delete_me.txt", b"data").await.unwrap();
    secondary.store("delete_me.txt", b"data").await.unwrap();

    let multi_tier = storage::MultiTierStorage::new(Box::new(primary), Box::new(secondary));

    // Delete should remove from both
    let deleted = multi_tier.delete("delete_me.txt").await.unwrap();
    assert!(deleted);

    // Should not exist anymore
    let exists = multi_tier.exists("delete_me.txt").await.unwrap();
    assert!(!exists);
}

#[tokio::test]
async fn test_multi_tier_delete_only_primary() {
    let temp_dir1 = TempDir::new().unwrap();
    let temp_dir2 = TempDir::new().unwrap();

    let config1 = LocalStorageConfig {
        base_path: temp_dir1.path().to_path_buf(),
        ..Default::default()
    };
    let config2 = LocalStorageConfig {
        base_path: temp_dir2.path().to_path_buf(),
        ..Default::default()
    };

    let primary = LocalStorage::new(config1).await.unwrap();
    let secondary = LocalStorage::new(config2).await.unwrap();

    // Store only in primary
    primary.store("primary_only.txt", b"data").await.unwrap();

    let multi_tier = storage::MultiTierStorage::new(Box::new(primary), Box::new(secondary));

    let deleted = multi_tier.delete("primary_only.txt").await.unwrap();
    assert!(deleted);
}

#[tokio::test]
async fn test_multi_tier_list_combines_both() {
    let temp_dir1 = TempDir::new().unwrap();
    let temp_dir2 = TempDir::new().unwrap();

    let config1 = LocalStorageConfig {
        base_path: temp_dir1.path().to_path_buf(),
        ..Default::default()
    };
    let config2 = LocalStorageConfig {
        base_path: temp_dir2.path().to_path_buf(),
        ..Default::default()
    };

    let primary = LocalStorage::new(config1).await.unwrap();
    let secondary = LocalStorage::new(config2).await.unwrap();

    // Store different files in each
    primary.store("file1.txt", b"data1").await.unwrap();
    primary.store("file2.txt", b"data2").await.unwrap();
    secondary.store("file3.txt", b"data3").await.unwrap();

    let multi_tier = storage::MultiTierStorage::new(Box::new(primary), Box::new(secondary));

    // List should combine both
    let files = multi_tier.list("").await.unwrap();
    assert_eq!(files.len(), 3);
}

#[tokio::test]
async fn test_multi_tier_list_with_prefix() {
    let temp_dir1 = TempDir::new().unwrap();
    let temp_dir2 = TempDir::new().unwrap();

    let config1 = LocalStorageConfig {
        base_path: temp_dir1.path().to_path_buf(),
        ..Default::default()
    };
    let config2 = LocalStorageConfig {
        base_path: temp_dir2.path().to_path_buf(),
        ..Default::default()
    };

    let primary = LocalStorage::new(config1).await.unwrap();
    let secondary = LocalStorage::new(config2).await.unwrap();

    primary.store("models/v1.txt", b"data1").await.unwrap();
    secondary.store("models/v2.txt", b"data2").await.unwrap();
    secondary.store("data/test.txt", b"data3").await.unwrap();

    let multi_tier = storage::MultiTierStorage::new(Box::new(primary), Box::new(secondary));

    let files = multi_tier.list("models/").await.unwrap();
    assert_eq!(files.len(), 2);
}

#[tokio::test]
async fn test_multi_tier_metadata_from_primary() {
    let temp_dir1 = TempDir::new().unwrap();
    let temp_dir2 = TempDir::new().unwrap();

    let config1 = LocalStorageConfig {
        base_path: temp_dir1.path().to_path_buf(),
        ..Default::default()
    };
    let config2 = LocalStorageConfig {
        base_path: temp_dir2.path().to_path_buf(),
        ..Default::default()
    };

    let primary = LocalStorage::new(config1).await.unwrap();
    let secondary = LocalStorage::new(config2).await.unwrap();

    primary.store("primary.txt", b"primary_data").await.unwrap();

    let multi_tier = storage::MultiTierStorage::new(Box::new(primary), Box::new(secondary));

    let metadata = multi_tier.metadata("primary.txt").await.unwrap();
    assert_eq!(metadata.size, 12);
}

#[tokio::test]
async fn test_multi_tier_metadata_from_secondary() {
    let temp_dir1 = TempDir::new().unwrap();
    let temp_dir2 = TempDir::new().unwrap();

    let config1 = LocalStorageConfig {
        base_path: temp_dir1.path().to_path_buf(),
        ..Default::default()
    };
    let config2 = LocalStorageConfig {
        base_path: temp_dir2.path().to_path_buf(),
        ..Default::default()
    };

    let primary = LocalStorage::new(config1).await.unwrap();
    let secondary = LocalStorage::new(config2).await.unwrap();

    secondary
        .store("secondary.txt", b"secondary_data")
        .await
        .unwrap();

    let multi_tier = storage::MultiTierStorage::new(Box::new(primary), Box::new(secondary));

    let metadata = multi_tier.metadata("secondary.txt").await.unwrap();
    assert_eq!(metadata.size, 14);
}

#[tokio::test]
async fn test_multi_tier_store_writes_to_both() {
    let temp_dir1 = TempDir::new().unwrap();
    let temp_dir2 = TempDir::new().unwrap();

    let config1 = LocalStorageConfig {
        base_path: temp_dir1.path().to_path_buf(),
        ..Default::default()
    };
    let config2 = LocalStorageConfig {
        base_path: temp_dir2.path().to_path_buf(),
        ..Default::default()
    };

    let primary = LocalStorage::new(config1).await.unwrap();
    let secondary = LocalStorage::new(config2).await.unwrap();

    let multi_tier = storage::MultiTierStorage::new(Box::new(primary), Box::new(secondary));

    // Store via multi-tier
    multi_tier.store("both.txt", b"data").await.unwrap();

    // Should exist in both
    assert!(multi_tier.exists("both.txt").await.unwrap());
}

#[tokio::test]
async fn test_multi_tier_large_file() {
    let temp_dir1 = TempDir::new().unwrap();
    let temp_dir2 = TempDir::new().unwrap();

    let config1 = LocalStorageConfig {
        base_path: temp_dir1.path().to_path_buf(),
        ..Default::default()
    };
    let config2 = LocalStorageConfig {
        base_path: temp_dir2.path().to_path_buf(),
        ..Default::default()
    };

    let primary = LocalStorage::new(config1).await.unwrap();
    let secondary = LocalStorage::new(config2).await.unwrap();

    let multi_tier = storage::MultiTierStorage::new(Box::new(primary), Box::new(secondary));

    // Store 1MB file
    let large_data = vec![0xAAu8; 1_048_576];
    multi_tier
        .store("large.bin", &large_data)
        .await
        .expect("Failed to store large file");

    // Retrieve and verify
    let retrieved = multi_tier.retrieve("large.bin").await.unwrap();
    assert_eq!(retrieved.len(), large_data.len());
}

#[tokio::test]
async fn test_multi_tier_concurrent_operations() {
    let temp_dir1 = TempDir::new().unwrap();
    let temp_dir2 = TempDir::new().unwrap();

    let config1 = LocalStorageConfig {
        base_path: temp_dir1.path().to_path_buf(),
        ..Default::default()
    };
    let config2 = LocalStorageConfig {
        base_path: temp_dir2.path().to_path_buf(),
        ..Default::default()
    };

    let primary = LocalStorage::new(config1).await.unwrap();
    let secondary = LocalStorage::new(config2).await.unwrap();

    let multi_tier = Arc::new(storage::MultiTierStorage::new(
        Box::new(primary),
        Box::new(secondary),
    ));

    let mut handles = vec![];

    for i in 0..10 {
        let multi_tier = multi_tier.clone();
        let handle = tokio::spawn(async move {
            let path = format!("concurrent_{}.txt", i);
            let data = format!("data_{}", i);
            multi_tier.store(&path, data.as_bytes()).await.unwrap();
            let retrieved = multi_tier.retrieve(&path).await.unwrap();
            assert_eq!(retrieved, data.as_bytes());
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.await.unwrap();
    }
}

#[tokio::test]
async fn test_storage_provider_local_variant() {
    let temp_dir = TempDir::new().unwrap();

    let config = LocalStorageConfig {
        base_path: temp_dir.path().to_path_buf(),
        ..Default::default()
    };

    let _provider = StorageProvider::Local(config);
    // Successfully created local provider variant
}

#[tokio::test]
async fn test_storage_provider_multi_tier_variant() {
    let temp_dir1 = TempDir::new().unwrap();
    let temp_dir2 = TempDir::new().unwrap();

    let config1 = LocalStorageConfig {
        base_path: temp_dir1.path().to_path_buf(),
        ..Default::default()
    };

    let config2 = LocalStorageConfig {
        base_path: temp_dir2.path().to_path_buf(),
        ..Default::default()
    };

    let _provider = StorageProvider::MultiTier {
        primary: Box::new(StorageProvider::Local(config1)),
        secondary: Box::new(StorageProvider::Local(config2)),
    };
    // Successfully created multi-tier provider variant
}

#[tokio::test]
async fn test_multi_tier_overwrite_in_both() {
    let temp_dir1 = TempDir::new().unwrap();
    let temp_dir2 = TempDir::new().unwrap();

    let config1 = LocalStorageConfig {
        base_path: temp_dir1.path().to_path_buf(),
        ..Default::default()
    };
    let config2 = LocalStorageConfig {
        base_path: temp_dir2.path().to_path_buf(),
        ..Default::default()
    };

    let primary = LocalStorage::new(config1).await.unwrap();
    let secondary = LocalStorage::new(config2).await.unwrap();

    let multi_tier = storage::MultiTierStorage::new(Box::new(primary), Box::new(secondary));

    // Store initial data
    multi_tier.store("overwrite.txt", b"initial").await.unwrap();

    // Overwrite
    multi_tier.store("overwrite.txt", b"updated").await.unwrap();

    // Retrieve should get updated data
    let data = multi_tier.retrieve("overwrite.txt").await.unwrap();
    assert_eq!(data, b"updated");
}
