#![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,
)]
//! Liquid NN Training Pipeline TDD Test Suite
//!
//! Comprehensive E2E tests for Liquid Neural Network training with CPU-only
//! fixed-point arithmetic. Tests cover forward/backward passes, training loop
//! convergence, checkpoint persistence, inference determinism, and memory usage.
//!
//! Architecture:
//! - CPU-ONLY (fixed-point arithmetic for <100μs inference)
//! - No CUDA dependencies (by design, not a limitation)
//! - Fixed-point precision: 8 decimal places (PRECISION = 100_000_000)
//! - Training: CPU-based gradient descent with MSE loss
//! - Inference: Deterministic fixed-point computation
//!
//! Test Coverage:
//! 1. Forward pass: Fixed-point computation correctness
//! 2. Backward pass: Gradient calculation (CPU only)
//! 3. Training loop: Loss convergence over epochs
//! 4. Checkpoint save/load: Safetensors persistence
//! 5. Inference determinism: Same input → same output
//! 6. Memory usage: CPU memory within limits

#![allow(unused_crate_dependencies)]

use ml::liquid::{
    ActivationType, FixedPoint, LTCConfig, LayerConfig, LiquidNetwork, LiquidNetworkConfig,
    LiquidTrainer, LiquidTrainingConfig, NetworkType, OutputLayerConfig, SolverType, TrainingBatch,
    TrainingSample, TrainingUtils, PRECISION,
};
use std::time::Instant;
use tracing::info;

// ============================================================================
// Test 1: Forward Pass - Fixed-Point Computation
// ============================================================================

#[test]
fn test_liquid_nn_forward_pass() -> anyhow::Result<()> {
    info!("=== Test 1: Forward Pass - Fixed-Point Computation ===");

    // Create minimal Liquid NN (16 input → 8 hidden → 3 output)
    let ltc_config = LTCConfig {
        input_size: 16,
        hidden_size: 8,
        tau_min: FixedPoint::from_f64(0.1),
        tau_max: FixedPoint::from_f64(1.0),
        use_bias: true,
        solver_type: SolverType::Euler, // Simplest solver for testing
        activation: ActivationType::Tanh,
    };

    let network_config = LiquidNetworkConfig {
        network_type: NetworkType::LTC,
        input_size: 16,
        output_size: 3,
        layer_configs: vec![LayerConfig::LTC(ltc_config)],
        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)?;
    info!(parameter_count = network.parameter_count(), "Created network: 16 inputs -> 8 hidden (LTC) -> 3 outputs");

    // Create input with fixed-point values
    let input: Vec<FixedPoint> = (0..16)
        .map(|i| FixedPoint::from_f64(0.5 + (i as f64) * 0.01))
        .collect();

    info!(?input, "Input features (first 5 shown in debug)");

    // Forward pass
    let start = Instant::now();
    let output = network.forward(&input)?;
    let duration = start.elapsed();

    info!(?duration, output_len = output.len(), ?output, "Forward pass completed");

    // Assertions
    assert_eq!(output.len(), 3, "Output should have 3 values");
    assert!(
        duration.as_micros() < 1000,
        "Forward pass should be <1ms (target: <100μs in production)"
    );

    // Verify fixed-point arithmetic correctness
    for &val in &output {
        assert!(
            val.is_finite(),
            "Output values should be finite (no overflow)"
        );
    }

    info!("Forward pass completed successfully");
    Ok(())
}

// ============================================================================
// Test 2: Backward Pass - Gradient Computation (CPU Only)
// ============================================================================

#[test]
fn test_liquid_nn_backward_pass() -> anyhow::Result<()> {
    info!("=== Test 2: Backward Pass - Gradient Computation ===");

    // Create network
    let ltc_config = LTCConfig {
        input_size: 4,
        hidden_size: 4,
        tau_min: FixedPoint::from_f64(0.1),
        tau_max: FixedPoint::from_f64(1.0),
        use_bias: true,
        solver_type: SolverType::Euler,
        activation: ActivationType::Tanh,
    };

    let network_config = LiquidNetworkConfig {
        network_type: NetworkType::LTC,
        input_size: 4,
        output_size: 2,
        layer_configs: vec![LayerConfig::LTC(ltc_config)],
        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.clone())?;
    let trainer_config = LiquidTrainingConfig::default();
    let mut trainer = LiquidTrainer::new(trainer_config);

    info!("Created network: 4 -> 4 (LTC) -> 2");

    // Create training sample
    let input = vec![
        FixedPoint::from_f64(0.5),
        FixedPoint::from_f64(0.3),
        FixedPoint::from_f64(0.7),
        FixedPoint::from_f64(0.2),
    ];
    let target = vec![FixedPoint::one(), FixedPoint::zero()];

    let sample = TrainingSample {
        input: input.clone(),
        target: target.clone(),
        timestamp: None,
        market_regime: None,
        volatility: None,
    };

    info!(?input, ?target, "Training sample");

    // Forward pass to get predictions
    let predictions = network.forward(&input)?;
    info!(?predictions, "Predictions before training");

    // Calculate loss manually (MSE)
    let loss_before: f64 = predictions
        .iter()
        .zip(target.iter())
        .map(|(pred, tgt)| {
            let diff = pred.to_f64() - tgt.to_f64();
            diff * diff
        })
        .sum::<f64>()
        / predictions.len() as f64;
    info!(loss_before, "Loss before training");

    // Train to verify gradient computation (use public train method)
    let batches = vec![TrainingBatch::new(vec![sample])];
    trainer.train(&mut network, &batches, None)?;

    let history = trainer.get_training_history();
    let batch_loss = history.last().map(|m| m.training_loss).unwrap_or(0.0);
    let gradient_history_len = trainer.gradient_history.len();
    info!(batch_loss, gradient_history_len, "Training results");

    // Verify gradient was computed
    assert!(
        !trainer.gradient_history.is_empty(),
        "Gradient history should contain gradients after training"
    );

    // Verify gradient is finite
    let last_gradient = trainer.gradient_history.last().unwrap();
    assert!(
        last_gradient.is_finite(),
        "Gradient should be finite (no overflow)"
    );

    info!(last_gradient = last_gradient.to_f64(), "Backward pass completed successfully");

    Ok(())
}

// ============================================================================
// Test 3: Training Loop Convergence - Loss Decreases Over Epochs
// ============================================================================

#[test]
fn test_training_loop_convergence() -> anyhow::Result<()> {
    info!("=== Test 3: Training Loop Convergence ===");

    // Create small network for fast convergence test
    let ltc_config = LTCConfig {
        input_size: 3,
        hidden_size: 4,
        tau_min: FixedPoint::from_f64(0.1),
        tau_max: FixedPoint::from_f64(1.0),
        use_bias: true,
        solver_type: SolverType::Euler,
        activation: ActivationType::Tanh,
    };

    let network_config = LiquidNetworkConfig {
        network_type: NetworkType::LTC,
        input_size: 3,
        output_size: 2,
        layer_configs: vec![LayerConfig::LTC(ltc_config)],
        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)?;
    info!("Created network: 3 -> 4 (LTC) -> 2");

    // Create synthetic training data (simple XOR-like problem)
    let mut training_samples = Vec::new();
    for i in 0..20 {
        let x = (i % 4) as f64;
        let input = vec![
            FixedPoint::from_f64(x / 4.0),
            FixedPoint::from_f64((x * 2.0) / 4.0),
            FixedPoint::from_f64((x * 3.0) / 4.0),
        ];
        let target = if i % 2 == 0 {
            vec![FixedPoint::one(), FixedPoint::zero()]
        } else {
            vec![FixedPoint::zero(), FixedPoint::one()]
        };

        training_samples.push(TrainingSample {
            input,
            target,
            timestamp: None,
            market_regime: None,
            volatility: None,
        });
    }

    info!(sample_count = training_samples.len(), "Created training samples");

    // Create batches
    let batches = TrainingUtils::create_batches(training_samples, 4);
    info!(batch_count = batches.len(), "Created batches (batch size: 4)");

    // Configure training (10 epochs for convergence test)
    let training_config = LiquidTrainingConfig {
        learning_rate: FixedPoint(PRECISION / 100), // 0.01
        batch_size: 4,
        max_epochs: 10,
        early_stopping_patience: 5,
        gradient_clip_threshold: FixedPoint::one(),
        l2_regularization: FixedPoint::zero(),
        adaptive_learning_rate: false,
        market_regime_adaptation: false,
        validation_split: 0.0,
    };

    let mut trainer = LiquidTrainer::new(training_config);
    info!("Training configuration: learning_rate=0.01, max_epochs=10, batch_size=4");

    // Train network
    info!("Starting training");
    let start = Instant::now();
    trainer.train(&mut network, &batches, None)?;
    let training_time = start.elapsed();

    info!(?training_time, "Training completed");

    // Verify loss convergence
    let history = trainer.get_training_history();
    assert!(
        history.len() >= 2,
        "Training history should have at least 2 epochs"
    );

    let first_loss = history[0].training_loss;
    let last_loss = history.last().unwrap().training_loss;

    info!(first_loss, "Epoch 0 loss");
    for (i, metrics) in history.iter().enumerate().skip(1) {
        info!(epoch = i, loss = metrics.training_loss, "Epoch loss");
    }
    info!(last_loss, "Final loss");

    // Assert loss decreased (convergence)
    assert!(
        last_loss < first_loss,
        "Loss should decrease during training (first={:.6}, last={:.6})",
        first_loss,
        last_loss
    );

    let loss_reduction = ((first_loss - last_loss) / first_loss) * 100.0;
    info!(loss_reduction, "Training converged successfully");

    Ok(())
}

// ============================================================================
// Test 4: Checkpoint Save/Load - Safetensors Persistence
// ============================================================================

#[test]
fn test_checkpoint_save_load() -> anyhow::Result<()> {
    info!("=== Test 4: Checkpoint Save/Load ===");

    // Create network
    let ltc_config = LTCConfig {
        input_size: 5,
        hidden_size: 6,
        tau_min: FixedPoint::from_f64(0.1),
        tau_max: FixedPoint::from_f64(1.0),
        use_bias: true,
        solver_type: SolverType::Euler,
        activation: ActivationType::Tanh,
    };

    let network_config = LiquidNetworkConfig {
        network_type: NetworkType::LTC,
        input_size: 5,
        output_size: 3,
        layer_configs: vec![LayerConfig::LTC(ltc_config)],
        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 network = LiquidNetwork::new(network_config.clone())?;
    info!("Created original network: 5 -> 6 (LTC) -> 3");

    // Create test input
    let input: Vec<FixedPoint> = (0..5)
        .map(|i| FixedPoint::from_f64(0.1 + (i as f64) * 0.1))
        .collect();

    // Save checkpoint BEFORE running forward pass to preserve initial state
    info!("Saving checkpoint");
    let original_network = network.clone();
    let checkpoint_json = serde_json::to_string(&original_network)?;
    info!(checkpoint_size_bytes = checkpoint_json.len(), "Checkpoint saved");

    // Run forward pass on original network
    let mut original_network_mut = original_network.clone();
    let original_output = original_network_mut.forward(&input)?;
    info!(?original_output, "Original predictions");

    // Load checkpoint
    info!("Loading checkpoint");
    let mut loaded_network: LiquidNetwork = serde_json::from_str(&checkpoint_json)?;
    info!("Checkpoint loaded successfully");

    // Verify predictions match
    let loaded_output = loaded_network.forward(&input)?;
    info!(?loaded_output, "Loaded predictions");

    // Compare outputs
    for (i, (&orig, &loaded)) in original_output.iter().zip(loaded_output.iter()).enumerate() {
        let diff = (orig.0 - loaded.0).abs();
        info!(
            output_idx = i,
            orig = orig.to_f64(),
            loaded = loaded.to_f64(),
            diff,
            "Output comparison"
        );
        assert_eq!(
            orig, loaded,
            "Output {} should match exactly after checkpoint reload",
            i
        );
    }

    info!("Checkpoint save/load verified (deterministic)");
    Ok(())
}

// ============================================================================
// Test 5: Inference Determinism - Same Input → Same Output
// ============================================================================

#[test]
fn test_inference_determinism() -> anyhow::Result<()> {
    info!("=== Test 5: Inference Determinism ===");

    // Create network
    let ltc_config = LTCConfig {
        input_size: 8,
        hidden_size: 8,
        tau_min: FixedPoint::from_f64(0.1),
        tau_max: FixedPoint::from_f64(1.0),
        use_bias: true,
        solver_type: SolverType::RK4, // Higher-order solver
        activation: ActivationType::Tanh,
    };

    let network_config = LiquidNetworkConfig {
        network_type: NetworkType::LTC,
        input_size: 8,
        output_size: 4,
        layer_configs: vec![LayerConfig::LTC(ltc_config)],
        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)?;
    info!("Created network: 8 -> 8 (LTC, RK4) -> 4");

    // Create test input
    let input: Vec<FixedPoint> = vec![
        FixedPoint::from_f64(0.123),
        FixedPoint::from_f64(0.456),
        FixedPoint::from_f64(0.789),
        FixedPoint::from_f64(0.234),
        FixedPoint::from_f64(0.567),
        FixedPoint::from_f64(0.890),
        FixedPoint::from_f64(0.345),
        FixedPoint::from_f64(0.678),
    ];

    info!("Running 10 inference passes with identical input");

    // Run inference 10 times with same input
    let mut outputs = Vec::new();
    for i in 0..10 {
        // Reset network state before each inference
        network.reset_states();

        let output = network.forward(&input)?;
        outputs.push(output);

        if i == 0 {
            info!(output = ?outputs[0], run = i, "First run output");
        }
    }

    // Verify all outputs are identical
    let first_output = &outputs[0];
    for (run_idx, output) in outputs.iter().enumerate().skip(1) {
        for (i, (&expected, &actual)) in first_output.iter().zip(output.iter()).enumerate() {
            assert_eq!(
                expected, actual,
                "Run {} output[{}] should match run 0 (deterministic)",
                run_idx, i
            );
        }
    }

    info!(?first_output, "Inference is deterministic (10/10 runs identical)");
    Ok(())
}

// ============================================================================
// Test 6: Memory Usage - CPU Memory Within Limits
// ============================================================================

#[test]
fn test_memory_usage() -> anyhow::Result<()> {
    info!("=== Test 6: Memory Usage ===");

    // Create realistic-sized network (16 → 128 → 3)
    let ltc_config = LTCConfig {
        input_size: 16,
        hidden_size: 128,
        tau_min: FixedPoint::from_f64(0.1),
        tau_max: FixedPoint::from_f64(1.0),
        use_bias: true,
        solver_type: SolverType::RK4,
        activation: ActivationType::Tanh,
    };

    let network_config = LiquidNetworkConfig {
        network_type: NetworkType::LTC,
        input_size: 16,
        output_size: 3,
        layer_configs: vec![LayerConfig::LTC(ltc_config)],
        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 network = LiquidNetwork::new(network_config)?;
    info!("Created network: 16 -> 128 (LTC) -> 3");

    // Calculate memory footprint
    let param_count = network.parameter_count();
    let bytes_per_param = size_of::<FixedPoint>(); // 8 bytes (i64)
    let total_bytes = param_count * bytes_per_param;
    let kb = total_bytes as f64 / 1024.0;
    let mb = kb / 1024.0;

    info!(
        param_count,
        bytes_per_param,
        total_bytes,
        kb,
        mb,
        "Memory analysis"
    );

    // Parameter breakdown
    let input_weights = 16 * 128; // input_size × hidden_size
    let recurrent_weights = 128 * 128; // hidden_size × hidden_size
    let bias = 128; // hidden_size
    let output_weights = 128 * 3; // hidden_size × output_size
    let output_bias = 3; // output_size
    let total_calculated = input_weights + recurrent_weights + bias + output_weights + output_bias;

    info!(
        input_weights,
        recurrent_weights,
        hidden_bias = bias,
        output_weights,
        output_bias,
        total_calculated,
        "Parameter breakdown"
    );

    // Verify memory is reasonable (<10 MB for this size)
    assert!(
        mb < 10.0,
        "Network memory should be <10 MB (actual: {:.3} MB)",
        mb
    );

    // Create training dataset and measure memory
    info!("Testing with 1000 training samples");
    let mut samples = Vec::new();
    for i in 0..1000 {
        let input: Vec<FixedPoint> = (0..16)
            .map(|j| FixedPoint::from_f64((i * j) as f64 / 1000.0))
            .collect();
        let target = vec![
            FixedPoint::from_f64((i % 3 == 0) as u8 as f64),
            FixedPoint::from_f64((i % 3 == 1) as u8 as f64),
            FixedPoint::from_f64((i % 3 == 2) as u8 as f64),
        ];

        samples.push(TrainingSample {
            input,
            target,
            timestamp: None,
            market_regime: None,
            volatility: None,
        });
    }

    let sample_memory = samples.len() * (16 + 3) * size_of::<FixedPoint>();
    let sample_mb = sample_memory as f64 / 1024.0 / 1024.0;
    info!(sample_mb, "Sample dataset memory");

    // Total memory (network + samples)
    let total_mb = mb + sample_mb;
    info!(total_mb, "Total memory usage");

    // Verify total memory is reasonable (<50 MB)
    assert!(
        total_mb < 50.0,
        "Total memory (network + samples) should be <50 MB (actual: {:.3} MB)",
        total_mb
    );

    info!(network_mb = mb, sample_mb, total_mb, "Memory usage within limits (<50 MB limit)");

    Ok(())
}

// ============================================================================
// Helper Functions
// ============================================================================
