#![allow(
    clippy::assertions_on_constants,
    clippy::assertions_on_result_states,
    clippy::clone_on_copy,
    clippy::decimal_literal_representation,
    clippy::doc_markdown,
    clippy::empty_line_after_doc_comments,
    clippy::field_reassign_with_default,
    clippy::get_unwrap,
    clippy::identity_op,
    clippy::inconsistent_digit_grouping,
    clippy::indexing_slicing,
    clippy::integer_division,
    clippy::len_zero,
    clippy::let_underscore_must_use,
    clippy::manual_div_ceil,
    clippy::manual_let_else,
    clippy::manual_range_contains,
    clippy::modulo_arithmetic,
    clippy::needless_range_loop,
    clippy::non_ascii_literal,
    clippy::redundant_clone,
    clippy::shadow_reuse,
    clippy::shadow_same,
    clippy::shadow_unrelated,
    clippy::single_match_else,
    clippy::str_to_string,
    clippy::string_slice,
    clippy::tests_outside_test_module,
    clippy::too_many_lines,
    clippy::unnecessary_wraps,
    clippy::unseparated_literal_suffix,
    clippy::use_debug,
    clippy::useless_vec,
    clippy::wildcard_enum_match_arm,
    clippy::else_if_without_else,
    clippy::expect_used,
    clippy::missing_const_for_fn,
    clippy::similar_names,
    clippy::type_complexity,
    clippy::collapsible_else_if,
    clippy::doc_lazy_continuation,
    clippy::items_after_test_module,
    clippy::map_clone,
    clippy::multiple_unsafe_ops_per_block,
    clippy::unwrap_or_default,
    clippy::assign_op_pattern,
    clippy::needless_borrow,
    clippy::println_empty_string,
    clippy::unnecessary_cast,
    clippy::used_underscore_binding,
    clippy::create_dir,
    clippy::implicit_saturating_sub,
    clippy::exit,
    clippy::expect_fun_call,
    clippy::too_many_arguments,
    clippy::unnecessary_map_or,
    clippy::unwrap_used,
    dead_code,
    unused_imports,
    unused_variables,
    clippy::cloned_ref_to_slice_refs,
    clippy::neg_multiply,
    clippy::while_let_loop,
    clippy::bool_assert_comparison,
    clippy::excessive_precision,
    clippy::trivially_copy_pass_by_ref,
    clippy::op_ref,
    clippy::redundant_closure,
    clippy::unnecessary_lazy_evaluations,
    clippy::if_then_some_else_none,
    clippy::unnecessary_to_owned,
    clippy::single_component_path_imports,
)]
//! Comprehensive edge case tests for ML model training loops
//!
//! This module tests edge cases across DQN, PPO, and Liquid Neural Networks:
//! - Training iteration with NaN/Inf loss values
//! - Gradient explosion/vanishing scenarios
//! - Convergence check boundary conditions
//! - Batch size edge cases (size=1, size=max)
//! - Learning rate edge cases (zero, negative, very large)
//! - Training loop early stopping conditions
//! - Model state save/load during training
//!
//! Target: +20% coverage increase for ml crate

#![allow(unused_crate_dependencies)]

use common::trading::MarketRegime;
use ml::dqn::agent::{DQNAgent, TradingAction};
use ml_core::common::action::{ExposureLevel, FactoredAction, OrderType, Urgency};
use ml::dqn::DQNConfig;
use ml::dqn::experience::Experience;
use ml::liquid::network::{LiquidNetwork, OutputLayerConfig};
use ml::liquid::training::{LiquidTrainer, LiquidTrainingConfig, TrainingBatch, TrainingSample};
use ml::liquid::{ActivationType, FixedPoint, LiquidNetworkConfig, NetworkType, PRECISION};
use ml::ppo::ppo::{PPOConfig, PPO};
use ml::ppo::trajectories::{Trajectory, TrajectoryBatch, TrajectoryStep};
use ml::MLError;

// ============================================================================
// DQN Training Edge Cases
// ============================================================================

#[tokio::test]
async fn test_dqn_training_with_insufficient_experiences() -> Result<(), Box<dyn std::error::Error>>
{
    let config = DQNConfig {
        batch_size: 32,
        ..Default::default()
    };
    let mut agent = DQNAgent::new(config)?;

    // Add fewer experiences than batch size
    for i in 0..10 {
        let experience = Experience::new(
            vec![i as f32; 52],
            TradingAction::Hold.to_int(),
            1.0,
            vec![i as f32 + 0.1; 52],
            false,
        );
        agent.store_experience(experience)?;
    }

    // Training should fail with insufficient experiences
    let result = agent.train();
    assert!(result.is_err());
    match result {
        Err(MLError::TrainingError(msg)) => {
            assert!(msg.contains("Not enough experiences"));
        },
        _ => panic!("Expected TrainingError for insufficient experiences"),
    }

    Ok(())
}

#[tokio::test]
async fn test_dqn_training_with_batch_size_one() -> Result<(), Box<dyn std::error::Error>> {
    let config = DQNConfig {
        state_dim: 52,
        batch_size: 1,
        replay_buffer_capacity: 1000,
        ..Default::default()
    };
    let mut agent = DQNAgent::new(config)?;

    // Add minimum experiences
    for i in 0..20 {
        let experience = Experience::new(
            vec![i as f32; 52],
            TradingAction::Buy.to_int(),
            (i as f32) * 0.1,
            vec![i as f32 + 0.5; 52],
            i % 5 == 0,
        );
        agent.store_experience(experience)?;
    }

    // Training with batch size 1 should succeed
    let loss = agent.train()?;
    assert!(loss.is_finite());
    assert!(loss >= 0.0);

    Ok(())
}

#[tokio::test]
async fn test_dqn_training_with_large_batch_size() -> Result<(), Box<dyn std::error::Error>> {
    let config = DQNConfig {
        state_dim: 52,
        batch_size: 512,
        replay_buffer_capacity: 10_000,
        ..Default::default()
    };
    let mut agent = DQNAgent::new(config)?;

    // Add enough experiences for large batch
    for i in 0..1000 {
        let experience = Experience::new(
            vec![(i % 100) as f32; 52],
            TradingAction::from_int((i % 3) as u8).unwrap().to_int(),
            ((i % 20) as f32) - 10.0,
            vec![((i + 1) % 100) as f32; 52],
            i % 50 == 0,
        );
        agent.store_experience(experience)?;
    }

    // Training with large batch should succeed
    let loss = agent.train()?;
    assert!(loss.is_finite());

    Ok(())
}

#[tokio::test]
async fn test_dqn_training_with_extreme_rewards() -> Result<(), Box<dyn std::error::Error>> {
    let config = DQNConfig::default();
    let mut agent = DQNAgent::new(config)?;

    // Add experiences with extreme reward values
    let extreme_rewards = vec![f32::MAX / 1000.0, -f32::MAX / 1000.0, 1e6, -1e6, 0.0];

    for (i, &reward) in extreme_rewards.iter().enumerate() {
        for j in 0..100 {
            let experience = Experience::new(
                vec![(i * 100 + j) as f32; 52],
                TradingAction::Hold.to_int(),
                reward,
                vec![(i * 100 + j + 1) as f32; 52],
                false,
            );
            agent.store_experience(experience)?;
        }
    }

    // Training should handle extreme rewards gracefully
    let loss = agent.train()?;
    assert!(
        loss.is_finite(),
        "Loss should be finite with extreme rewards"
    );

    Ok(())
}

#[tokio::test]
async fn test_dqn_learning_rate_zero() -> Result<(), Box<dyn std::error::Error>> {
    let config = DQNConfig {
        learning_rate: 0.0,
        ..Default::default()
    };
    let mut agent = DQNAgent::new(config)?;

    // Add experiences
    for i in 0..400 {
        let experience = Experience::new(
            vec![i as f32; 52],
            TradingAction::Buy.to_int(),
            1.0,
            vec![i as f32 + 0.1; 52],
            false,
        );
        agent.store_experience(experience)?;
    }

    // Training with zero learning rate should succeed but not change weights
    let loss_1 = agent.train()?;
    let loss_2 = agent.train()?;

    // Loss should remain similar (within 10% due to sampling randomness)
    assert!((loss_1 - loss_2).abs() / loss_1 < 0.1);

    Ok(())
}

#[tokio::test]
async fn test_dqn_learning_rate_very_large() -> Result<(), Box<dyn std::error::Error>> {
    let config = DQNConfig {
        learning_rate: 10.0, // Very large learning rate
        ..Default::default()
    };
    let mut agent = DQNAgent::new(config)?;

    // Add experiences
    for i in 0..400 {
        let experience = Experience::new(
            vec![i as f32; 52],
            TradingAction::Sell.to_int(),
            (i % 10) as f32,
            vec![i as f32 + 0.1; 52],
            i % 100 == 0,
        );
        agent.store_experience(experience)?;
    }

    // Training with very large learning rate should still produce finite loss
    let loss = agent.train()?;
    assert!(
        loss.is_finite(),
        "Loss should be finite even with large learning rate"
    );

    Ok(())
}

#[tokio::test]
async fn test_dqn_learning_rate_decay() -> Result<(), Box<dyn std::error::Error>> {
    let config = DQNConfig {
        learning_rate: 0.001,
        ..Default::default()
    };
    let mut agent = DQNAgent::new(config)?;

    let initial_lr = agent.get_learning_rate();

    // Apply learning rate decay
    agent.update_learning_rate(0.5)?;

    let new_lr = agent.get_learning_rate();
    assert_eq!(new_lr, initial_lr * 0.5);

    // Apply multiple decays
    agent.update_learning_rate(0.1)?;
    let final_lr = agent.get_learning_rate();
    assert_eq!(final_lr, initial_lr * 0.5 * 0.1);

    Ok(())
}

#[tokio::test]
async fn test_dqn_target_network_update_frequency() -> Result<(), Box<dyn std::error::Error>> {
    let config = DQNConfig {
        target_update_freq: 5,
        ..Default::default()
    };
    let mut agent = DQNAgent::new(config)?;

    // Add experiences
    for i in 0..500 {
        let experience = Experience::new(
            vec![i as f32; 52],
            TradingAction::Hold.to_int(),
            1.0,
            vec![i as f32 + 0.1; 52],
            false,
        );
        agent.store_experience(experience)?;
    }

    // Train multiple times to trigger target network update
    for _ in 0..10 {
        let loss = agent.train()?;
        assert!(loss.is_finite());
    }

    Ok(())
}

#[tokio::test]
async fn test_dqn_checkpoint_save_load_during_training() -> Result<(), Box<dyn std::error::Error>> {
    let config = DQNConfig::default();
    let mut agent = DQNAgent::new(config)?;

    // Add experiences and train
    for i in 0..400 {
        let experience = Experience::new(
            vec![i as f32; 52],
            TradingAction::Buy.to_int(),
            (i % 10) as f32,
            vec![i as f32 + 0.1; 52],
            false,
        );
        agent.store_experience(experience)?;
    }

    agent.train()?;
    let metrics_before = agent.get_metrics().clone();

    // Save checkpoint
    let temp_dir = std::env::temp_dir();
    let checkpoint_path = temp_dir.join("dqn_training_checkpoint.json");
    agent.save_checkpoint(&checkpoint_path)?;

    // Create new agent and load checkpoint
    let config = DQNConfig::default();
    let mut new_agent = DQNAgent::new(config)?;
    new_agent.load_checkpoint(&checkpoint_path)?;

    let metrics_after = new_agent.get_metrics();

    // Verify metrics are restored
    assert_eq!(metrics_before.total_episodes, metrics_after.total_episodes);
    assert_eq!(metrics_before.total_steps, metrics_after.total_steps);

    // Cleanup
    std::fs::remove_file(checkpoint_path).ok();

    Ok(())
}

#[tokio::test]
async fn test_dqn_reward_statistics_tracking() -> Result<(), Box<dyn std::error::Error>> {
    let config = DQNConfig::default();
    let mut agent = DQNAgent::new(config)?;

    // Update reward statistics with various scenarios
    agent.update_reward_stats(100.0, true);
    agent.update_reward_stats(-50.0, false);
    agent.update_reward_stats(0.0, false);
    agent.update_reward_stats(200.0, true);

    let stats = agent.get_training_stats();
    assert_eq!(stats["total_episodes"], 4.0);
    assert!(stats["avg_reward"] != 0.0);
    assert!(stats["win_rate"] > 0.0);
    assert!(stats["win_rate"] < 1.0);

    Ok(())
}

// ============================================================================
// PPO Training Edge Cases
// ============================================================================

#[tokio::test]
async fn test_ppo_training_with_empty_trajectory_batch() -> Result<(), Box<dyn std::error::Error>> {
    let config = PPOConfig::default();
    let mut ppo = PPO::new(config)?;

    let mut empty_batch = TrajectoryBatch::from_trajectories(vec![], vec![], vec![]);

    // Training with empty batch should handle gracefully
    let result = ppo.update(&mut empty_batch);

    // Empty batch should either succeed with zero updates or fail gracefully
    match result {
        Ok((policy_loss, value_loss)) => {
            assert!(policy_loss.is_finite());
            assert!(value_loss.is_finite());
        },
        Err(_) => {
            // Empty batch error is acceptable
        },
    }

    Ok(())
}

#[tokio::test]
async fn test_ppo_training_with_single_step_trajectory() -> Result<(), Box<dyn std::error::Error>> {
    let config = PPOConfig {
        state_dim: 2,
        mini_batch_size: 1,
        batch_size: 1,
        ..Default::default()
    };
    let _ppo = PPO::new(config)?;

    // Create trajectory with single step
    let mut trajectory = Trajectory::new();
    trajectory.add_step(TrajectoryStep::new(
        vec![1.0, 2.0],
        FactoredAction::new(ExposureLevel::Long100, OrderType::Market, Urgency::Normal),
        -0.5,
        10.0,
        1.0,
        true,
    ));

    assert_eq!(trajectory.steps.len(), 1);

    Ok(())
}

#[tokio::test]
async fn test_ppo_training_with_all_terminal_states() -> Result<(), Box<dyn std::error::Error>> {
    let config = PPOConfig {
        state_dim: 2,
        ..Default::default()
    };
    let _ppo = PPO::new(config)?;

    // Create trajectory with all terminal states
    let mut trajectory = Trajectory::new();
    for i in 0..10 {
        trajectory.add_step(TrajectoryStep::new(
            vec![i as f32, i as f32 + 1.0],
            FactoredAction::new(ExposureLevel::Flat, OrderType::Market, Urgency::Normal),
            -0.5,
            5.0,
            0.0,
            true, // All steps are terminal
        ));
    }

    // Compute returns with all terminal states
    let returns = trajectory.compute_returns(0.99);
    assert_eq!(returns.len(), 10);

    // All returns should be zero since all states are terminal
    for ret in returns {
        assert_eq!(ret, 0.0);
    }

    Ok(())
}

#[tokio::test]
async fn test_ppo_clip_epsilon_boundary() -> Result<(), Box<dyn std::error::Error>> {
    // Test with very small epsilon (should still clip)
    let config_small = PPOConfig {
        clip_epsilon: 0.001,
        ..Default::default()
    };
    let _ppo_small = PPO::new(config_small)?;

    // Test with large epsilon (less aggressive clipping)
    let config_large = PPOConfig {
        clip_epsilon: 0.9,
        ..Default::default()
    };
    let _ppo_large = PPO::new(config_large)?;

    Ok(())
}

#[tokio::test]
async fn test_ppo_zero_entropy_coefficient() -> Result<(), Box<dyn std::error::Error>> {
    let config = PPOConfig {
        entropy_coeff: 0.0,
        ..Default::default()
    };
    let _ppo = PPO::new(config)?;

    // PPO with zero entropy should work (deterministic policy)
    Ok(())
}

#[tokio::test]
async fn test_ppo_high_entropy_coefficient() -> Result<(), Box<dyn std::error::Error>> {
    let config = PPOConfig {
        entropy_coeff: 1.0, // Very high entropy = more exploration
        ..Default::default()
    };
    let _ppo = PPO::new(config)?;

    Ok(())
}

#[tokio::test]
async fn test_ppo_training_epochs_boundary() -> Result<(), Box<dyn std::error::Error>> {
    // Test with 1 epoch
    let config_single = PPOConfig {
        num_epochs: 1,
        ..Default::default()
    };
    let _ppo_single = PPO::new(config_single)?;

    // Test with many epochs
    let config_many = PPOConfig {
        num_epochs: 100,
        ..Default::default()
    };
    let _ppo_many = PPO::new(config_many)?;

    Ok(())
}

// ============================================================================
// Liquid Neural Network Training Edge Cases
// ============================================================================

#[tokio::test]
async fn test_liquid_training_with_nan_loss() -> Result<(), Box<dyn std::error::Error>> {
    let network_config = LiquidNetworkConfig {
        network_type: NetworkType::LTC,
        input_size: 2,
        output_size: 1,
        layer_configs: vec![],
        output_layer: OutputLayerConfig {
            use_linear_output: true,
            output_activation: Some(ActivationType::Linear),
            dropout_rate: None,
        },
        default_dt: FixedPoint::from_f64(0.01),
        market_regime_adaptation: false,
    };
    let mut network = LiquidNetwork::new(network_config)?;

    let training_config = LiquidTrainingConfig {
        batch_size: 2,
        max_epochs: 5,
        ..Default::default()
    };
    let mut trainer = LiquidTrainer::new(training_config);

    // Create batch with extreme values that could cause NaN
    let inputs = vec![
        vec![FixedPoint(i64::MAX / 2), FixedPoint(i64::MAX / 2)],
        vec![FixedPoint(i64::MIN / 2), FixedPoint(i64::MIN / 2)],
    ];
    let targets = vec![vec![FixedPoint(PRECISION)], vec![FixedPoint(PRECISION / 2)]];

    let batch = TrainingBatch::from_arrays(&inputs, &targets)?;
    let training_data = vec![batch];

    // Training should handle extreme values gracefully
    let result = trainer.train(&mut network, &training_data, None);

    // Either succeeds or fails with TrainingError (not panic)
    match result {
        Ok(_) => {},
        Err(ml::liquid::LiquidError::TrainingError(_)) => {},
        Err(e) => panic!("Unexpected error type: {:?}", e),
    }

    Ok(())
}

#[tokio::test]
async fn test_liquid_training_with_zero_learning_rate() -> Result<(), Box<dyn std::error::Error>> {
    let network_config = LiquidNetworkConfig {
        network_type: NetworkType::LTC,
        input_size: 2,
        output_size: 1,
        layer_configs: vec![],
        output_layer: OutputLayerConfig {
            use_linear_output: true,
            output_activation: Some(ActivationType::Linear),
            dropout_rate: None,
        },
        default_dt: FixedPoint::from_f64(0.01),
        market_regime_adaptation: false,
    };
    let mut network = LiquidNetwork::new(network_config)?;

    let training_config = LiquidTrainingConfig {
        learning_rate: FixedPoint(0), // Zero learning rate
        batch_size: 2,
        max_epochs: 3,
        ..Default::default()
    };
    let mut trainer = LiquidTrainer::new(training_config);

    let inputs = vec![
        vec![FixedPoint(PRECISION / 2), FixedPoint(PRECISION / 4)],
        vec![FixedPoint(PRECISION / 3), FixedPoint(PRECISION / 5)],
    ];
    let targets = vec![vec![FixedPoint(PRECISION)], vec![FixedPoint(PRECISION / 2)]];

    let batch = TrainingBatch::from_arrays(&inputs, &targets)?;
    let training_data = vec![batch];

    // Training with zero learning rate should succeed but not learn
    trainer.train(&mut network, &training_data, None)?;

    // Learning rate should remain zero
    assert_eq!(trainer.get_current_learning_rate(), 0.0);

    Ok(())
}

#[tokio::test]
async fn test_liquid_training_early_stopping() -> Result<(), Box<dyn std::error::Error>> {
    let network_config = LiquidNetworkConfig {
        network_type: NetworkType::LTC,
        input_size: 2,
        output_size: 1,
        layer_configs: vec![],
        output_layer: OutputLayerConfig {
            use_linear_output: true,
            output_activation: Some(ActivationType::Linear),
            dropout_rate: None,
        },
        default_dt: FixedPoint::from_f64(0.01),
        market_regime_adaptation: false,
    };
    let mut network = LiquidNetwork::new(network_config)?;

    let training_config = LiquidTrainingConfig {
        batch_size: 2,
        max_epochs: 100,
        early_stopping_patience: 3, // Stop if no improvement for 3 epochs
        ..Default::default()
    };
    let mut trainer = LiquidTrainer::new(training_config);

    // Training data
    let train_inputs = vec![
        vec![FixedPoint(PRECISION / 2), FixedPoint(PRECISION / 4)],
        vec![FixedPoint(PRECISION / 3), FixedPoint(PRECISION / 5)],
    ];
    let train_targets = vec![vec![FixedPoint(PRECISION)], vec![FixedPoint(PRECISION / 2)]];
    let train_batch = TrainingBatch::from_arrays(&train_inputs, &train_targets)?;

    // Validation data
    let val_inputs = vec![vec![FixedPoint(PRECISION / 6), FixedPoint(PRECISION / 7)]];
    let val_targets = vec![vec![FixedPoint(PRECISION / 3)]];
    let val_batch = TrainingBatch::from_arrays(&val_inputs, &val_targets)?;

    let training_data = vec![train_batch];
    let validation_data = vec![val_batch];

    trainer.train(&mut network, &training_data, Some(&validation_data))?;

    // Training should have stopped early (less than max epochs)
    let history = trainer.get_training_history();
    assert!(
        history.len() < 100,
        "Early stopping should trigger before max epochs"
    );

    Ok(())
}

#[tokio::test]
async fn test_liquid_training_batch_size_one() -> Result<(), Box<dyn std::error::Error>> {
    let network_config = LiquidNetworkConfig {
        network_type: NetworkType::LTC,
        input_size: 2,
        output_size: 1,
        layer_configs: vec![],
        output_layer: OutputLayerConfig {
            use_linear_output: true,
            output_activation: Some(ActivationType::Linear),
            dropout_rate: None,
        },
        default_dt: FixedPoint::from_f64(0.01),
        market_regime_adaptation: false,
    };
    let mut network = LiquidNetwork::new(network_config)?;

    let training_config = LiquidTrainingConfig {
        batch_size: 1, // Single sample per batch
        max_epochs: 5,
        ..Default::default()
    };
    let mut trainer = LiquidTrainer::new(training_config);

    let inputs = vec![vec![FixedPoint(PRECISION / 2), FixedPoint(PRECISION / 4)]];
    let targets = vec![vec![FixedPoint(PRECISION)]];

    let batch = TrainingBatch::from_arrays(&inputs, &targets)?;
    let training_data = vec![batch];

    // Training with batch size 1 should succeed
    trainer.train(&mut network, &training_data, None)?;

    Ok(())
}

#[tokio::test]
async fn test_liquid_training_gradient_clipping() -> Result<(), Box<dyn std::error::Error>> {
    let network_config = LiquidNetworkConfig {
        network_type: NetworkType::LTC,
        input_size: 2,
        output_size: 1,
        layer_configs: vec![],
        output_layer: OutputLayerConfig {
            use_linear_output: true,
            output_activation: Some(ActivationType::Linear),
            dropout_rate: None,
        },
        default_dt: FixedPoint::from_f64(0.01),
        market_regime_adaptation: false,
    };
    let mut network = LiquidNetwork::new(network_config)?;

    let training_config = LiquidTrainingConfig {
        gradient_clip_threshold: FixedPoint(PRECISION / 10), // Small threshold = aggressive clipping
        batch_size: 2,
        max_epochs: 3,
        ..Default::default()
    };
    let mut trainer = LiquidTrainer::new(training_config);

    let inputs = vec![
        vec![FixedPoint(PRECISION), FixedPoint(PRECISION)],
        vec![FixedPoint(-PRECISION), FixedPoint(-PRECISION)],
    ];
    let targets = vec![vec![FixedPoint(PRECISION)], vec![FixedPoint(PRECISION / 2)]];

    let batch = TrainingBatch::from_arrays(&inputs, &targets)?;
    let training_data = vec![batch];

    // Training should apply gradient clipping
    trainer.train(&mut network, &training_data, None)?;

    Ok(())
}

#[tokio::test]
async fn test_liquid_training_adaptive_learning_rate() -> Result<(), Box<dyn std::error::Error>> {
    let network_config = LiquidNetworkConfig {
        network_type: NetworkType::LTC,
        input_size: 2,
        output_size: 1,
        layer_configs: vec![],
        output_layer: OutputLayerConfig {
            use_linear_output: true,
            output_activation: Some(ActivationType::Linear),
            dropout_rate: None,
        },
        default_dt: FixedPoint::from_f64(0.01),
        market_regime_adaptation: false,
    };
    let mut network = LiquidNetwork::new(network_config)?;

    let training_config = LiquidTrainingConfig {
        learning_rate: FixedPoint(PRECISION / 100), // 0.01
        adaptive_learning_rate: true,
        batch_size: 2,
        max_epochs: 50,
        ..Default::default()
    };
    let mut trainer = LiquidTrainer::new(training_config);

    let initial_lr = trainer.get_current_learning_rate();

    let inputs = vec![
        vec![FixedPoint(PRECISION / 2), FixedPoint(PRECISION / 4)],
        vec![FixedPoint(PRECISION / 3), FixedPoint(PRECISION / 5)],
    ];
    let targets = vec![vec![FixedPoint(PRECISION)], vec![FixedPoint(PRECISION / 2)]];

    let batch = TrainingBatch::from_arrays(&inputs, &targets)?;
    let training_data = vec![batch];

    trainer.train(&mut network, &training_data, None)?;

    let final_lr = trainer.get_current_learning_rate();

    // Learning rate should have decayed
    assert!(final_lr < initial_lr, "Adaptive learning rate should decay");

    Ok(())
}

#[tokio::test]
async fn test_liquid_training_market_regime_adaptation() -> Result<(), Box<dyn std::error::Error>> {
    let network_config = LiquidNetworkConfig {
        network_type: NetworkType::LTC,
        input_size: 2,
        output_size: 1,
        layer_configs: vec![],
        output_layer: OutputLayerConfig {
            use_linear_output: true,
            output_activation: Some(ActivationType::Linear),
            dropout_rate: None,
        },
        default_dt: FixedPoint::from_f64(0.01),
        market_regime_adaptation: false,
    };
    let mut network = LiquidNetwork::new(network_config)?;

    let training_config = LiquidTrainingConfig {
        market_regime_adaptation: true,
        batch_size: 2,
        max_epochs: 5,
        ..Default::default()
    };
    let mut trainer = LiquidTrainer::new(training_config);

    // Create samples with different market regimes
    let sample1 = TrainingSample {
        input: vec![FixedPoint(PRECISION / 2), FixedPoint(PRECISION / 4)],
        target: vec![FixedPoint(PRECISION)],
        timestamp: Some(1000),
        market_regime: Some(MarketRegime::Trending),
        volatility: Some(FixedPoint(PRECISION * 2)), // High volatility
    };

    let sample2 = TrainingSample {
        input: vec![FixedPoint(PRECISION / 3), FixedPoint(PRECISION / 5)],
        target: vec![FixedPoint(PRECISION / 2)],
        timestamp: Some(2000),
        market_regime: Some(MarketRegime::Sideways),
        volatility: Some(FixedPoint(PRECISION / 10)), // Low volatility
    };

    let batch = TrainingBatch::new(vec![sample1, sample2]);
    let training_data = vec![batch];

    // Training should adapt to market regimes
    trainer.train(&mut network, &training_data, None)?;

    Ok(())
}

#[tokio::test]
async fn test_liquid_training_l2_regularization() -> Result<(), Box<dyn std::error::Error>> {
    let network_config = LiquidNetworkConfig {
        network_type: NetworkType::LTC,
        input_size: 2,
        output_size: 1,
        layer_configs: vec![],
        output_layer: OutputLayerConfig {
            use_linear_output: true,
            output_activation: Some(ActivationType::Linear),
            dropout_rate: None,
        },
        default_dt: FixedPoint::from_f64(0.01),
        market_regime_adaptation: false,
    };
    let mut network = LiquidNetwork::new(network_config)?;

    let training_config = LiquidTrainingConfig {
        l2_regularization: FixedPoint(PRECISION / 100), // 0.01 L2 penalty
        batch_size: 2,
        max_epochs: 5,
        ..Default::default()
    };
    let mut trainer = LiquidTrainer::new(training_config);

    let inputs = vec![
        vec![FixedPoint(PRECISION / 2), FixedPoint(PRECISION / 4)],
        vec![FixedPoint(PRECISION / 3), FixedPoint(PRECISION / 5)],
    ];
    let targets = vec![vec![FixedPoint(PRECISION)], vec![FixedPoint(PRECISION / 2)]];

    let batch = TrainingBatch::from_arrays(&inputs, &targets)?;
    let training_data = vec![batch];

    // Training with L2 regularization should succeed
    trainer.train(&mut network, &training_data, None)?;

    Ok(())
}

// ============================================================================
// Cross-Model Training Edge Cases
// ============================================================================

#[tokio::test]
async fn test_training_convergence_detection() -> Result<(), Box<dyn std::error::Error>> {
    // Test convergence detection for DQN
    let config = DQNConfig {
        learning_rate: 0.0001,
        ..Default::default()
    };
    let mut agent = DQNAgent::new(config)?;

    // Add identical experiences (should converge quickly)
    for _ in 0..500 {
        let experience = Experience::new(
            vec![1.0; 52],
            TradingAction::Hold.to_int(),
            0.0,
            vec![1.0; 52],
            false,
        );
        agent.store_experience(experience)?;
    }

    let mut losses = Vec::new();
    for _ in 0..10 {
        let loss = agent.train()?;
        losses.push(loss);
    }

    // Loss should decrease or stabilize (convergence)
    let first_loss = losses[0];
    let last_loss = losses[losses.len() - 1];
    assert!(
        last_loss <= first_loss * 1.5,
        "Loss should not increase significantly"
    );

    Ok(())
}

#[tokio::test]
async fn test_training_with_mixed_terminal_non_terminal() -> Result<(), Box<dyn std::error::Error>>
{
    let config = DQNConfig::default();
    let mut agent = DQNAgent::new(config)?;

    // Mix terminal and non-terminal experiences
    for i in 0..400 {
        let is_terminal = i % 10 == 0;
        let experience = Experience::new(
            vec![i as f32; 52],
            TradingAction::from_int((i % 3) as u8).unwrap().to_int(),
            if is_terminal { 0.0 } else { 1.0 },
            vec![(i + 1) as f32; 52],
            is_terminal,
        );
        agent.store_experience(experience)?;
    }

    // Training should handle mixed terminal states
    let loss = agent.train()?;
    assert!(loss.is_finite());

    Ok(())
}

#[tokio::test]
async fn test_training_metrics_accumulation() -> Result<(), Box<dyn std::error::Error>> {
    let config = DQNConfig::default();
    let mut agent = DQNAgent::new(config)?;

    // Track metrics over multiple training steps
    for i in 0..400 {
        let experience = Experience::new(
            vec![i as f32; 52],
            TradingAction::Buy.to_int(),
            1.0,
            vec![i as f32 + 0.1; 52],
            false,
        );
        agent.store_experience(experience)?;
    }

    agent.train()?;
    agent.train()?;
    agent.train()?;

    let stats = agent.get_training_stats();
    assert_eq!(stats["training_step"], 3.0);
    assert!(stats["total_steps"] >= 3.0);

    Ok(())
}
