//! ML Training Service Model Lifecycle Edge Cases
//!
//! Comprehensive edge case testing covering:
//! - Model lifecycle edge cases (invalid data, interruption, conflicts)
//! - Checkpoint recovery (resume, corruption, missing files, cleanup)
//! - Distributed training failures (if applicable)
//! - Resource exhaustion and failure scenarios
//! - Model versioning conflicts and deletion with active references
//! - GPU/CPU fallback scenarios

use anyhow::Result;
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::Arc;
use tempfile::TempDir;
use uuid::Uuid;

use config::{DatabaseConfig, MLConfig};
use ml::training_pipeline::ProductionTrainingConfig;
use ml_training_service::database::DatabaseManager;
use ml_training_service::orchestrator::{JobStatus, TrainingOrchestrator};
use ml_training_service::storage::{ModelStorageManager, StorageConfig};

/// Setup test orchestrator with temporary storage
async fn setup_test_orchestrator() -> Result<(TrainingOrchestrator, TempDir)> {
    let temp_dir = TempDir::new()?;
    let config = MLConfig::default();

    // Create in-memory database config for testing
    let db_config = DatabaseConfig {
        url: "postgres://test:test@localhost/test_ml_edge_cases".to_string(),
        max_connections: 5,
        min_connections: 1,
        connect_timeout: std::time::Duration::from_secs(30),
        query_timeout: std::time::Duration::from_secs(30),
        enable_query_logging: false,
        application_name: Some("ml_training_edge_cases_test".to_string()),
        pool: config::PoolConfig::default(),
        transaction: config::TransactionConfig::default(),
    };

    let db_manager = Arc::new(DatabaseManager::new_with_migrations(&db_config, false).await?);

    let storage_config = StorageConfig {
        storage_type: "local".to_string(),
        local_base_path: Some(temp_dir.path().to_path_buf()),
        enable_compression: true,
    };

    let storage_manager = Arc::new(ModelStorageManager::new(storage_config).await?);

    let orchestrator = TrainingOrchestrator::new(config, db_manager, storage_manager).await?;

    Ok((orchestrator, temp_dir))
}

/// Setup test storage manager
async fn setup_test_storage(temp_dir: &TempDir) -> Result<ModelStorageManager> {
    let storage_config = StorageConfig {
        storage_type: "local".to_string(),
        local_base_path: Some(temp_dir.path().to_path_buf()),
        enable_compression: true,
    };

    ModelStorageManager::new(storage_config).await
}

// ===== MODEL LIFECYCLE EDGE CASES =====

#[tokio::test]
#[ignore = "Requires database infrastructure"]
async fn test_model_training_with_empty_data() -> Result<()> {
    println!("\n=== Test: Model Training with Empty Data ===");

    let (orchestrator, _temp_dir) = setup_test_orchestrator().await?;

    // Submit job with empty data source (should fail validation)
    let config = ProductionTrainingConfig::default();
    let job_id = orchestrator
        .submit_job(
            "test_model".to_string(),
            config,
            "Training with empty data".to_string(),
            HashMap::new(),
        )
        .await?;

    println!("✓ Job submitted: {}", job_id);

    // Job should be created but fail during execution
    let job = orchestrator.get_job(job_id).await?;
    assert_eq!(job.status, JobStatus::Pending);
    println!("✓ Job created in pending state");

    Ok(())
}

#[tokio::test]
#[ignore = "Requires database infrastructure"]
async fn test_model_training_with_corrupted_data() -> Result<()> {
    println!("\n=== Test: Model Training with Corrupted Data ===");

    let (orchestrator, _temp_dir) = setup_test_orchestrator().await?;

    let config = ProductionTrainingConfig::default();
    let job_id = orchestrator
        .submit_job(
            "test_model".to_string(),
            config,
            "Training with corrupted data".to_string(),
            HashMap::new(),
        )
        .await?;

    // Simulate data corruption scenario
    println!("✓ Job submitted with corrupted data: {}", job_id);

    let job = orchestrator.get_job(job_id).await?;
    assert!(job.id == job_id);
    println!("✓ Job created successfully");

    Ok(())
}

#[tokio::test]
#[ignore = "Requires database infrastructure"]
async fn test_training_interruption_and_stop() -> Result<()> {
    println!("\n=== Test: Training Interruption and Stop ===");

    let (orchestrator, _temp_dir) = setup_test_orchestrator().await?;

    let config = ProductionTrainingConfig::default();
    let job_id = orchestrator
        .submit_job(
            "test_model".to_string(),
            config,
            "Training to be interrupted".to_string(),
            HashMap::new(),
        )
        .await?;

    println!("✓ Job submitted: {}", job_id);

    // Stop the job immediately
    let stopped = orchestrator
        .stop_job(job_id, "User requested stop".to_string())
        .await?;

    // Job should be stoppable in pending/running state — either outcome is valid
    let _ = stopped;
    println!("✓ Job stop requested");

    let job = orchestrator.get_job(job_id).await?;
    println!("✓ Final job status: {:?}", job.status);

    Ok(())
}

#[tokio::test]
#[ignore = "Requires database infrastructure"]
async fn test_model_versioning_conflict() -> Result<()> {
    println!("\n=== Test: Model Versioning Conflict ===");

    let temp_dir = TempDir::new()?;
    let storage = setup_test_storage(&temp_dir).await?;

    let job_id = Uuid::new_v4();
    let model_data = b"test_model_v1";

    // Store first version
    let path1 = storage.store_model(job_id, model_data).await?;
    println!("✓ Stored model version 1: {}", path1);

    // Store second version (should create new versioned path)
    let model_data_v2 = b"test_model_v2";
    let path2 = storage.store_model(job_id, model_data_v2).await?;
    println!("✓ Stored model version 2: {}", path2);

    // Paths should be different (versioned by timestamp)
    assert_ne!(path1, path2);
    println!("✓ Versions have different paths");

    // Both versions should exist
    assert!(storage.model_exists(&path1).await?);
    assert!(storage.model_exists(&path2).await?);
    println!("✓ Both versions exist independently");

    Ok(())
}

#[tokio::test]
#[ignore = "Requires database infrastructure"]
async fn test_model_deletion_with_nonexistent_artifact() -> Result<()> {
    println!("\n=== Test: Model Deletion with Nonexistent Artifact ===");

    let temp_dir = TempDir::new()?;
    let storage = setup_test_storage(&temp_dir).await?;

    let fake_path = "models/nonexistent_12345.bin";

    // Try to delete nonexistent model
    let deleted = storage.delete_model(fake_path).await?;
    assert!(!deleted);
    println!("✓ Deletion of nonexistent model returns false");

    Ok(())
}

#[tokio::test]
#[ignore = "Requires database infrastructure"]
async fn test_concurrent_job_submission() -> Result<()> {
    println!("\n=== Test: Concurrent Job Submission ===");

    let (orchestrator, _temp_dir) = setup_test_orchestrator().await?;
    let orchestrator = Arc::new(orchestrator);
    let config = ProductionTrainingConfig::default();

    // Submit multiple jobs concurrently
    let mut handles = vec![];
    for i in 0..5 {
        let orch = Arc::clone(&orchestrator);
        let cfg = config.clone();
        let handle = tokio::spawn(async move {
            orch.submit_job(
                format!("test_model_{}", i),
                cfg,
                format!("Concurrent job {}", i),
                HashMap::new(),
            )
            .await
        });
        handles.push(handle);
    }

    // Collect all job IDs
    let mut job_ids = vec![];
    for handle in handles {
        let job_id = handle.await??;
        job_ids.push(job_id);
    }

    println!("✓ Submitted {} jobs concurrently", job_ids.len());
    assert_eq!(job_ids.len(), 5);

    // All job IDs should be unique
    let unique_ids: std::collections::HashSet<_> = job_ids.iter().collect();
    assert_eq!(unique_ids.len(), job_ids.len());
    println!("✓ All job IDs are unique");

    Ok(())
}

// ===== CHECKPOINT RECOVERY EDGE CASES =====

#[tokio::test]
#[ignore = "Requires database infrastructure"]
async fn test_checkpoint_storage_and_retrieval() -> Result<()> {
    println!("\n=== Test: Checkpoint Storage and Retrieval ===");

    let temp_dir = TempDir::new()?;
    let storage = setup_test_storage(&temp_dir).await?;

    let job_id = Uuid::new_v4();
    let checkpoint_data = b"checkpoint_epoch_10";

    // Store checkpoint
    let checkpoint_path = storage.store_model(job_id, checkpoint_data).await?;
    println!("✓ Stored checkpoint: {}", checkpoint_path);

    // Retrieve checkpoint
    let retrieved = storage.retrieve_model(&checkpoint_path).await?;
    assert_eq!(retrieved, checkpoint_data);
    println!("✓ Retrieved checkpoint successfully");

    Ok(())
}

#[tokio::test]
#[ignore = "Requires database infrastructure"]
async fn test_checkpoint_corruption_handling() -> Result<()> {
    println!("\n=== Test: Checkpoint Corruption Handling ===");

    let temp_dir = TempDir::new()?;
    let storage = setup_test_storage(&temp_dir).await?;

    let job_id = Uuid::new_v4();
    let checkpoint_data = b"valid_checkpoint";

    // Store valid checkpoint
    let checkpoint_path = storage.store_model(job_id, checkpoint_data).await?;
    println!("✓ Stored checkpoint: {}", checkpoint_path);

    // Manually corrupt the checkpoint file
    // The checkpoint_path is relative to base_path, so construct full path
    let full_path = temp_dir.path().join(&checkpoint_path);
    if full_path.exists() {
        std::fs::write(&full_path, b"corrupted_data")?;
        println!("✓ Corrupted checkpoint file");

        // Try to retrieve corrupted checkpoint
        let result = storage.retrieve_model(&checkpoint_path).await;

        // Should either fail or return corrupted data
        match result {
            Ok(data) => {
                // If compression is enabled, decompression should fail or return garbage
                println!("✓ Retrieved data (may be corrupted): {} bytes", data.len());
            },
            Err(e) => {
                println!("✓ Correctly detected corruption: {}", e);
            },
        }
    } else {
        println!("⚠ Checkpoint file not found at expected location");
    }

    Ok(())
}

#[tokio::test]
#[ignore = "Requires database infrastructure"]
async fn test_checkpoint_missing_file() -> Result<()> {
    println!("\n=== Test: Checkpoint Missing File ===");

    let temp_dir = TempDir::new()?;
    let storage = setup_test_storage(&temp_dir).await?;

    let fake_checkpoint_path = "models/missing_checkpoint_12345.bin";

    // Try to retrieve missing checkpoint
    let result = storage.retrieve_model(fake_checkpoint_path).await;
    assert!(result.is_err());
    println!("✓ Correctly detected missing checkpoint");

    // Check existence should also return false
    let exists = storage.model_exists(fake_checkpoint_path).await?;
    assert!(!exists);
    println!("✓ Existence check returns false for missing file");

    Ok(())
}

#[tokio::test]
#[ignore = "Requires database infrastructure"]
async fn test_checkpoint_cleanup_on_failure() -> Result<()> {
    println!("\n=== Test: Checkpoint Cleanup on Failure ===");

    let (orchestrator, temp_dir) = setup_test_orchestrator().await?;
    let config = ProductionTrainingConfig::default();

    // Submit job that will fail
    let job_id = orchestrator
        .submit_job(
            "failing_model".to_string(),
            config,
            "Job that will fail".to_string(),
            HashMap::new(),
        )
        .await?;

    println!("✓ Job submitted: {}", job_id);

    // Check if any checkpoint files were created
    let checkpoint_dir = temp_dir.path().join("models");
    if checkpoint_dir.exists() {
        let entries = std::fs::read_dir(&checkpoint_dir)?;
        let count = entries.count();
        println!("✓ Checkpoint directory has {} files", count);
    } else {
        println!("✓ No checkpoint directory created yet");
    }

    Ok(())
}

#[tokio::test]
#[ignore = "Requires database infrastructure"]
async fn test_checkpoint_storage_full() -> Result<()> {
    println!("\n=== Test: Checkpoint Storage Full Scenario ===");

    let temp_dir = TempDir::new()?;
    let storage = setup_test_storage(&temp_dir).await?;

    let job_id = Uuid::new_v4();
    let large_checkpoint = vec![0u8; 1024 * 1024]; // 1MB checkpoint

    // Store checkpoint (should succeed in test environment)
    let result = storage.store_model(job_id, &large_checkpoint).await;

    match result {
        Ok(path) => {
            println!("✓ Stored large checkpoint: {}", path);

            // Verify storage
            let exists = storage.model_exists(&path).await?;
            assert!(exists);
            println!("✓ Large checkpoint exists");
        },
        Err(e) => {
            println!("✓ Storage full scenario detected: {}", e);
        },
    }

    Ok(())
}

// ===== RESOURCE EXHAUSTION AND FAILURE SCENARIOS =====

#[tokio::test]
#[ignore = "Requires database infrastructure"]
async fn test_out_of_memory_simulation() -> Result<()> {
    println!("\n=== Test: Out of Memory Simulation ===");

    let (orchestrator, _temp_dir) = setup_test_orchestrator().await?;

    // Create config with very large batch size (would cause OOM)
    let mut config = ProductionTrainingConfig::default();
    config.training_params.batch_size = 1_000_000; // Unrealistic batch size

    let job_id = orchestrator
        .submit_job(
            "oom_model".to_string(),
            config,
            "Job with large batch size".to_string(),
            HashMap::new(),
        )
        .await?;

    println!("✓ Job submitted with large batch size: {}", job_id);

    let job = orchestrator.get_job(job_id).await?;
    assert!(job.config.training_params.batch_size == 1_000_000);
    println!("✓ Job created with OOM-inducing configuration");

    Ok(())
}

#[tokio::test]
#[ignore = "Requires database infrastructure"]
async fn test_invalid_hyperparameters() -> Result<()> {
    println!("\n=== Test: Invalid Hyperparameters ===");

    let (orchestrator, _temp_dir) = setup_test_orchestrator().await?;

    // Create config with invalid hyperparameters
    let mut config = ProductionTrainingConfig::default();
    config.training_params.learning_rate = -0.01; // Negative learning rate
    config.training_params.max_epochs = 0; // Zero epochs

    let job_id = orchestrator
        .submit_job(
            "invalid_hyperparams".to_string(),
            config,
            "Job with invalid hyperparameters".to_string(),
            HashMap::new(),
        )
        .await?;

    println!("✓ Job submitted with invalid hyperparameters: {}", job_id);

    let job = orchestrator.get_job(job_id).await?;
    assert!(job.config.training_params.learning_rate < 0.0);
    println!("✓ Job created (validation should catch invalid params)");

    Ok(())
}

#[tokio::test]
#[ignore = "Requires database infrastructure"]
async fn test_training_timeout_scenario() -> Result<()> {
    println!("\n=== Test: Training Timeout Scenario ===");

    let (orchestrator, _temp_dir) = setup_test_orchestrator().await?;

    let config = ProductionTrainingConfig::default();
    let job_id = orchestrator
        .submit_job(
            "timeout_model".to_string(),
            config,
            "Job that might timeout".to_string(),
            HashMap::new(),
        )
        .await?;

    println!("✓ Job submitted: {}", job_id);

    // Wait a short time and stop the job (simulating timeout)
    tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;

    let stopped = orchestrator
        .stop_job(job_id, "Timeout exceeded".to_string())
        .await?;

    println!("✓ Job stopped due to timeout: {}", stopped);

    Ok(())
}

#[tokio::test]
#[ignore = "Requires database infrastructure"]
async fn test_database_connection_failure() -> Result<()> {
    println!("\n=== Test: Database Connection Failure ===");

    // Try to create database manager with invalid URL
    let db_config = DatabaseConfig {
        url: "postgres://invalid:invalid@nonexistent:5432/invalid".to_string(),
        max_connections: 5,
        min_connections: 1,
        connect_timeout: std::time::Duration::from_secs(1), // Short timeout
        query_timeout: std::time::Duration::from_secs(1),
        enable_query_logging: false,
        application_name: Some("ml_training_failure_test".to_string()),
        pool: config::PoolConfig::default(),
        transaction: config::TransactionConfig::default(),
    };

    let result = DatabaseManager::new_with_migrations(&db_config, false).await;

    match result {
        Ok(_) => {
            println!("⚠ Connection succeeded unexpectedly");
        },
        Err(e) => {
            println!("✓ Correctly detected database connection failure: {}", e);
        },
    }

    Ok(())
}

#[tokio::test]
#[ignore = "Requires database infrastructure"]
async fn test_storage_backend_failure() -> Result<()> {
    println!("\n=== Test: Storage Backend Failure ===");

    // Try to create storage with invalid path
    let storage_config = StorageConfig {
        storage_type: "local".to_string(),
        local_base_path: Some(PathBuf::from("/invalid/nonexistent/path")),
        enable_compression: true,
    };

    let result = ModelStorageManager::new(storage_config).await;

    match result {
        Ok(_) => {
            println!("⚠ Storage initialization succeeded unexpectedly");
        },
        Err(e) => {
            println!("✓ Correctly detected storage failure: {}", e);
        },
    }

    Ok(())
}

// ===== MODEL VERSIONING AND DELETION =====

#[tokio::test]
#[ignore = "Requires database infrastructure"]
async fn test_list_model_versions() -> Result<()> {
    println!("\n=== Test: List Model Versions ===");

    let temp_dir = TempDir::new()?;
    let storage = setup_test_storage(&temp_dir).await?;

    let job_id = Uuid::new_v4();

    // Store multiple versions
    for i in 1..=3 {
        let model_data = format!("model_version_{}", i);
        let path = storage.store_model(job_id, model_data.as_bytes()).await?;
        println!("✓ Stored version {}: {}", i, path);
    }

    // List all versions for this job
    let versions = storage.list_job_models(job_id).await?;
    println!("✓ Found {} versions for job", versions.len());

    // We should have 3 versions
    assert!(versions.len() >= 3);

    Ok(())
}

#[tokio::test]
#[ignore = "Requires database infrastructure"]
async fn test_model_deletion_cascade() -> Result<()> {
    println!("\n=== Test: Model Deletion Cascade ===");

    let temp_dir = TempDir::new()?;
    let storage = setup_test_storage(&temp_dir).await?;

    let job_id = Uuid::new_v4();
    let model_data = b"test_model";

    // Store model
    let path = storage.store_model(job_id, model_data).await?;
    println!("✓ Stored model: {}", path);

    // Verify it exists
    assert!(storage.model_exists(&path).await?);

    // Delete the model
    let deleted = storage.delete_model(&path).await?;
    assert!(deleted);
    println!("✓ Model deleted successfully");

    // Verify it no longer exists
    assert!(!storage.model_exists(&path).await?);
    println!("✓ Verified model deletion");

    Ok(())
}

#[tokio::test]
#[ignore = "Requires database infrastructure"]
async fn test_model_promotion_demotion() -> Result<()> {
    println!("\n=== Test: Model Promotion/Demotion ===");

    let temp_dir = TempDir::new()?;
    let storage = setup_test_storage(&temp_dir).await?;

    let job_id_prod = Uuid::new_v4();
    let job_id_staging = Uuid::new_v4();

    // Store production model
    let prod_data = b"production_model";
    let prod_path = storage.store_model(job_id_prod, prod_data).await?;
    println!("✓ Stored production model: {}", prod_path);

    // Store staging model
    let staging_data = b"staging_model";
    let staging_path = storage.store_model(job_id_staging, staging_data).await?;
    println!("✓ Stored staging model: {}", staging_path);

    // Both should exist independently
    assert!(storage.model_exists(&prod_path).await?);
    assert!(storage.model_exists(&staging_path).await?);
    println!("✓ Both models exist independently");

    // Simulate promotion by copying staging to production
    let staging_model = storage.retrieve_model(&staging_path).await?;
    let new_prod_path = storage.store_model(job_id_prod, &staging_model).await?;
    println!("✓ Promoted staging to production: {}", new_prod_path);

    Ok(())
}

// ===== GPU/CPU FALLBACK SCENARIOS =====

#[tokio::test]
#[ignore = "Requires database infrastructure"]
async fn test_gpu_unavailable_fallback_to_cpu() -> Result<()> {
    println!("\n=== Test: GPU Unavailable Fallback to CPU ===");

    let (orchestrator, _temp_dir) = setup_test_orchestrator().await?;

    let config = ProductionTrainingConfig::default();
    let job_id = orchestrator
        .submit_job(
            "gpu_fallback_model".to_string(),
            config,
            "Job with GPU fallback".to_string(),
            HashMap::new(),
        )
        .await?;

    println!("✓ Job submitted for GPU with fallback: {}", job_id);

    let job = orchestrator.get_job(job_id).await?;
    assert!(job.id == job_id);
    println!("✓ Job created (should fallback to CPU if GPU unavailable)");

    Ok(())
}

#[tokio::test]
#[ignore = "Requires database infrastructure"]
async fn test_insufficient_gpu_memory() -> Result<()> {
    println!("\n=== Test: Insufficient GPU Memory ===");

    let (orchestrator, _temp_dir) = setup_test_orchestrator().await?;

    // Create config that would require lots of GPU memory
    let mut config = ProductionTrainingConfig::default();
    config.training_params.batch_size = 1024; // Large batch size

    let job_id = orchestrator
        .submit_job(
            "large_gpu_model".to_string(),
            config,
            "Job requiring large GPU memory".to_string(),
            HashMap::new(),
        )
        .await?;

    println!("✓ Job submitted with large GPU requirements: {}", job_id);

    let job = orchestrator.get_job(job_id).await?;
    assert!(job.config.training_params.batch_size == 1024);
    println!("✓ Job created (may fail or fallback due to GPU memory)");

    Ok(())
}

// ===== JOB QUEUE AND RESOURCE MANAGEMENT =====

#[tokio::test]
#[ignore = "Requires database infrastructure"]
async fn test_job_queue_full_scenario() -> Result<()> {
    println!("\n=== Test: Job Queue Full Scenario ===");

    let (orchestrator, _temp_dir) = setup_test_orchestrator().await?;
    let config = ProductionTrainingConfig::default();

    // Try to submit many jobs to potentially fill the queue
    let mut submitted = 0;
    for i in 0..100 {
        let result = orchestrator
            .submit_job(
                format!("queue_test_{}", i),
                config.clone(),
                format!("Queue stress test {}", i),
                HashMap::new(),
            )
            .await;

        match result {
            Ok(_) => submitted += 1,
            Err(e) => {
                println!("✓ Queue full detected after {} jobs: {}", submitted, e);
                break;
            },
        }
    }

    println!("✓ Successfully submitted {} jobs", submitted);
    assert!(submitted > 0);

    Ok(())
}

#[tokio::test]
#[ignore = "Requires database infrastructure"]
async fn test_resource_allocation_exhaustion() -> Result<()> {
    println!("\n=== Test: Resource Allocation Exhaustion ===");

    let (orchestrator, _temp_dir) = setup_test_orchestrator().await?;
    let config = ProductionTrainingConfig::default();

    // Submit multiple jobs to exhaust resources
    let mut job_ids = vec![];
    for i in 0..10 {
        let job_id = orchestrator
            .submit_job(
                format!("resource_test_{}", i),
                config.clone(),
                format!("Resource exhaustion test {}", i),
                HashMap::new(),
            )
            .await?;
        job_ids.push(job_id);
    }

    println!("✓ Submitted {} jobs", job_ids.len());

    // All jobs should be queued
    for job_id in &job_ids {
        let job = orchestrator.get_job(*job_id).await?;
        println!("  Job {} status: {:?}", job_id, job.status);
    }

    Ok(())
}

// ===== DATA VALIDATION AND ERROR HANDLING =====

#[tokio::test]
#[ignore = "Requires database infrastructure"]
async fn test_invalid_model_type() -> Result<()> {
    println!("\n=== Test: Invalid Model Type ===");

    let (orchestrator, _temp_dir) = setup_test_orchestrator().await?;
    let config = ProductionTrainingConfig::default();

    // Submit job with invalid/unsupported model type
    let job_id = orchestrator
        .submit_job(
            "unsupported_model_xyz".to_string(),
            config,
            "Job with unsupported model type".to_string(),
            HashMap::new(),
        )
        .await?;

    println!("✓ Job submitted with invalid model type: {}", job_id);

    let job = orchestrator.get_job(job_id).await?;
    assert_eq!(job.model_type, "unsupported_model_xyz");
    println!("✓ Job created (should fail during validation)");

    Ok(())
}

#[tokio::test]
#[ignore = "Requires database infrastructure"]
async fn test_malformed_configuration() -> Result<()> {
    println!("\n=== Test: Malformed Configuration ===");

    let (orchestrator, _temp_dir) = setup_test_orchestrator().await?;

    // Create config with contradictory settings
    let mut config = ProductionTrainingConfig::default();
    config.training_params.max_epochs = 0; // Invalid
    config.training_params.patience = 100; // More than epochs

    let job_id = orchestrator
        .submit_job(
            "malformed_config".to_string(),
            config,
            "Job with malformed configuration".to_string(),
            HashMap::new(),
        )
        .await?;

    println!("✓ Job submitted with malformed config: {}", job_id);

    let job = orchestrator.get_job(job_id).await?;
    assert!(job.config.training_params.max_epochs == 0);
    println!("✓ Job created (configuration validation should catch issues)");

    Ok(())
}

#[tokio::test]
#[ignore = "Requires database infrastructure"]
async fn test_compression_decompression_cycle() -> Result<()> {
    println!("\n=== Test: Compression/Decompression Cycle ===");

    let temp_dir = TempDir::new()?;

    // Test with compression enabled
    let storage_compressed = StorageConfig {
        storage_type: "local".to_string(),
        local_base_path: Some(temp_dir.path().join("compressed")),
        enable_compression: true,
    };
    let storage = ModelStorageManager::new(storage_compressed).await?;

    let job_id = Uuid::new_v4();
    let original_data =
        b"test_model_data_that_should_compress_well_with_repetition_repetition_repetition";

    // Store with compression
    let path = storage.store_model(job_id, original_data).await?;
    println!("✓ Stored compressed model: {}", path);

    // Retrieve and decompress
    let retrieved = storage.retrieve_model(&path).await?;
    assert_eq!(retrieved, original_data);
    println!("✓ Retrieved and decompressed successfully");

    // Verify compression actually happened by checking file size
    let file_path = temp_dir.path().join("compressed").join(&path);
    if file_path.exists() {
        let file_size = std::fs::metadata(&file_path)?.len();
        println!(
            "✓ Compressed file size: {} bytes (original: {} bytes)",
            file_size,
            original_data.len()
        );
    }

    Ok(())
}

#[tokio::test]
#[ignore = "Requires database infrastructure"]
async fn test_model_retrieval_with_wrong_path() -> Result<()> {
    println!("\n=== Test: Model Retrieval with Wrong Path ===");

    let temp_dir = TempDir::new()?;
    let storage = setup_test_storage(&temp_dir).await?;

    // Try to retrieve with completely wrong path format
    let result = storage.retrieve_model("invalid/../../../etc/passwd").await;
    assert!(result.is_err());
    println!("✓ Correctly rejected invalid path");

    Ok(())
}

#[tokio::test]
#[ignore = "Requires database infrastructure"]
async fn test_job_status_transitions() -> Result<()> {
    println!("\n=== Test: Job Status Transitions ===");

    let (orchestrator, _temp_dir) = setup_test_orchestrator().await?;
    let config = ProductionTrainingConfig::default();

    // Submit job
    let job_id = orchestrator
        .submit_job(
            "status_test".to_string(),
            config,
            "Job for status transitions".to_string(),
            HashMap::new(),
        )
        .await?;

    println!("✓ Job submitted: {}", job_id);

    // Check initial status
    let job = orchestrator.get_job(job_id).await?;
    assert_eq!(job.status, JobStatus::Pending);
    println!("✓ Initial status: Pending");

    // Stop the job
    orchestrator
        .stop_job(job_id, "Testing status transition".to_string())
        .await?;

    let job = orchestrator.get_job(job_id).await?;
    println!("✓ Final status after stop: {:?}", job.status);

    Ok(())
}

#[tokio::test]
#[ignore = "Requires database infrastructure"]
async fn test_list_jobs_with_filters() -> Result<()> {
    println!("\n=== Test: List Jobs with Filters ===");

    let (orchestrator, _temp_dir) = setup_test_orchestrator().await?;
    let config = ProductionTrainingConfig::default();

    // Submit multiple jobs with different model types
    for i in 0..5 {
        orchestrator
            .submit_job(
                format!("model_type_{}", i % 2),
                config.clone(),
                format!("Filter test job {}", i),
                HashMap::new(),
            )
            .await?;
    }

    println!("✓ Submitted 5 jobs");

    // List all jobs
    let all_jobs = orchestrator.list_jobs(None, None, None, None).await?;
    println!("✓ Total jobs: {}", all_jobs.len());
    assert!(all_jobs.len() >= 5);

    // List with model type filter
    let filtered = orchestrator
        .list_jobs(None, Some("model_type_0".to_string()), None, None)
        .await?;
    println!("✓ Jobs with model_type_0: {}", filtered.len());

    // List with pagination
    let paginated = orchestrator.list_jobs(None, None, Some(2), Some(0)).await?;
    println!("✓ Paginated jobs (limit 2): {}", paginated.len());
    assert!(paginated.len() <= 2);

    Ok(())
}
