//! # Inference Engine
//!
//! High-performance inference engine with ONNX Runtime integration
//! and optimized model serving for different latency requirements.

use std::collections::HashMap;
use std::sync::Arc;
use std::time::{Instant, SystemTime, UNIX_EPOCH};

use serde::{Deserialize, Serialize};
use tokio::sync::RwLock;
use tracing::{debug, info, warn};

// Placeholder types for ONNX Runtime (removed from dependencies)
#[derive(Debug)]
pub struct Environment;

#[derive(Debug)]
pub struct Session;

use super::{InferencePriority, IntegrationHubConfig};
use crate::{InferenceResult, MLError, ModelMetadata, ModelType};

/// Configuration for fallback prediction to eliminate dangerous hardcoded values
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FallbackPredictionConfig {
    /// Base prediction value (replaces hardcoded 0.5)
    pub base_prediction: f32,
    /// Neutral prediction for empty features
    pub neutral_prediction: f64,
    /// Default confidence when using fallback
    pub default_confidence: f64,
    /// Signal weights for market features
    pub signal_weights: SignalWeights,
    /// Signal scaling factors
    pub signal_scaling: SignalScaling,
    /// Feature bounds for safety
    pub feature_bounds: FeatureBounds,
    /// Default feature values
    pub feature_defaults: FeatureDefaults,
    /// Prediction output bounds
    pub prediction_bounds: PredictionBounds,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SignalWeights {
    pub momentum_weight: f32,
    pub volume_weight: f32,
    pub spread_weight: f32,
    pub volatility_weight: f32,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SignalScaling {
    pub momentum_scale: f32,
    pub volume_scale: f32,
    pub spread_scale: f32,
    pub volatility_scale: f32,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FeatureBounds {
    pub momentum_min: f32,
    pub momentum_max: f32,
    pub volume_min: f32,
    pub volume_max: f32,
    pub spread_min: f32,
    pub spread_max: f32,
    pub volatility_min: f32,
    pub volatility_max: f32,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FeatureDefaults {
    pub momentum_default: f32,
    pub volume_default: f32,
    pub spread_default: f32,
    pub volatility_default: f32,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PredictionBounds {
    pub min: f32,
    pub max: f32,
}

impl Default for FallbackPredictionConfig {
    fn default() -> Self {
        Self::emergency_safe_defaults()
    }
}

impl FallbackPredictionConfig {
    /// Emergency safe defaults - ultra-conservative to prevent trading disasters
    pub fn emergency_safe_defaults() -> Self {
        tracing::warn!("Using emergency fallback prediction defaults - check config system!");
        Self {
            base_prediction: 0.5,    // Market neutral
            neutral_prediction: 0.5, // Market neutral
            default_confidence: 0.1, // Very low confidence for safety
            signal_weights: SignalWeights {
                momentum_weight: 0.05, // Very low weight to prevent strong signals
                volume_weight: 0.02,
                spread_weight: 0.01,
                volatility_weight: 0.01,
            },
            signal_scaling: SignalScaling {
                momentum_scale: 0.01, // Very small scaling
                volume_scale: 0.005,
                spread_scale: 0.002,
                volatility_scale: 0.002,
            },
            feature_bounds: FeatureBounds {
                momentum_min: -0.1,
                momentum_max: 0.1,
                volume_min: 0.0,
                volume_max: 2.0,
                spread_min: 0.0,
                spread_max: 0.1,
                volatility_min: 0.0,
                volatility_max: 0.5,
            },
            feature_defaults: FeatureDefaults {
                momentum_default: 0.0,   // Neutral
                volume_default: 1.0,     // Average volume
                spread_default: 0.01,    // Small spread
                volatility_default: 0.1, // Low volatility
            },
            prediction_bounds: PredictionBounds {
                min: 0.45, // Very narrow range around neutral
                max: 0.55,
            },
        }
    }
}

/// Configuration for inference engine
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InferenceEngineConfig {
    /// Maximum concurrent inference requests
    pub max_concurrent_requests: usize,
    /// Default timeout for inference in microseconds
    pub default_timeout_us: u64,
    /// Maximum batch size for batched inference
    pub max_batch_size: usize,
    /// Enable ONNX runtime acceleration
    pub enable_onnx: bool,
    /// Enable `GPU` acceleration
    pub enable_gpu: bool,
    /// Model cache size
    pub model_cache_size: usize,
}

impl Default for InferenceEngineConfig {
    fn default() -> Self {
        Self {
            max_concurrent_requests: 10,
            default_timeout_us: 1000,
            max_batch_size: 32,
            enable_onnx: true,
            enable_gpu: false,
            model_cache_size: 100,
        }
    }
}

/// Activation functions for micro models
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
pub enum ActivationFunction {
    /// Rectified Linear Unit
    ReLU,
    /// Hyperbolic tangent
    Tanh,
    /// Sigmoid function
    Sigmoid,
    /// Linear (no activation)
    Linear,
}

impl ActivationFunction {
    /// Apply activation function to value
    pub fn apply(self, x: f32) -> f32 {
        match self {
            ActivationFunction::ReLU => x.max(0.0),
            ActivationFunction::Tanh => x.tanh(),
            ActivationFunction::Sigmoid => 1.0 / (1.0 + (-x).exp()),
            ActivationFunction::Linear => x,
        }
    }
}

/// Lightweight micro model for ultra-low latency inference
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MicroModel {
    /// Model identifier
    pub model_id: String,
    /// Flattened weight matrix
    pub weights: Vec<f32>,
    /// Bias vector
    pub biases: Vec<f32>,
    /// Layer sizes (input, hidden..., output)
    pub layer_sizes: Vec<usize>,
    /// Activation function
    pub activation: ActivationFunction,
}

/// Inference request for the engine
#[derive(Debug, Clone)]
pub struct InferenceRequest {
    /// Unique request identifier
    pub request_id: String,
    /// Model to use for inference
    pub model_id: String,
    /// Input features
    pub features: Vec<f32>,
    /// Request priority
    pub priority: InferencePriority,
    /// Timeout in microseconds
    pub timeout_us: u64,
    /// Request timestamp
    pub timestamp: Instant,
}

/// Inference response from the engine
#[derive(Debug, Clone)]
pub struct InferenceResponse {
    /// Request identifier
    pub request_id: String,
    /// Inference result
    pub result: Result<InferenceResult, MLError>,
    /// Total processing time
    pub processing_time_us: u64,
    /// Queue time before processing
    pub queue_time_us: u64,
}

/// High-performance inference engine
#[derive(Debug)]
pub struct InferenceEngine {
    /// Configuration
    config: InferenceEngineConfig,
    /// ONNX Runtime environment
    onnx_env: Option<Arc<Environment>>,
    /// Loaded ONNX models (placeholder - ONNX Runtime removed)
    models: Arc<RwLock<HashMap<String, Arc<Session>>>>,
    /// Lightweight micro models
    micro_models: Arc<RwLock<HashMap<String, MicroModel>>>,
    /// Request queue
    request_queue: Arc<tokio::sync::Mutex<Vec<InferenceRequest>>>,
    /// Async executor handle
    executor: Arc<tokio::runtime::Handle>,
}

impl InferenceEngine {
    /// Create new inference engine
    pub async fn new(_config: &IntegrationHubConfig) -> Result<Self, MLError> {
        let inference_config = InferenceEngineConfig::default();

        // ONNX Runtime disabled - return placeholder
        let onnx_env = if inference_config.enable_onnx {
            debug!("ONNX Runtime requested but not available (removed from dependencies)");
            None
        } else {
            None
        };

        Ok(Self {
            config: inference_config,
            onnx_env,
            models: Arc::new(RwLock::new(HashMap::new())),
            micro_models: Arc::new(RwLock::new(HashMap::new())),
            request_queue: Arc::new(tokio::sync::Mutex::new(Vec::new())),
            executor: Arc::new(tokio::runtime::Handle::current()),
        })
    }

    /// Load ONNX model from file (DISABLED - ONNX Runtime removed)
    pub async fn load_onnx_model(
        &self,
        model_id: String,
        _model_path: &str,
    ) -> Result<(), MLError> {
        warn!(
            "load_onnx_model called for {} but ONNX Runtime is disabled",
            model_id
        );
        Err(MLError::ConfigError("ONNX runtime not available (removed from dependencies)".to_owned()))
    }

    /// Load micro model for ultra-fast inference
    pub async fn load_micro_model(&self, model: MicroModel) {
        let mut micro_models = self.micro_models.write().await;
        let model_id = model.model_id.clone();
        micro_models.insert(model_id.clone(), model);

        info!("Micro model loaded: {}", model_id);
    }

    /// Submit inference request
    pub async fn submit_request(&self, request: InferenceRequest) -> Result<(), MLError> {
        let mut queue = self.request_queue.lock().await;

        if queue.len() >= self.config.max_concurrent_requests {
            return Err(MLError::ResourceLimit {
                resource: "inference_queue".to_owned(),
                limit: self.config.max_concurrent_requests,
            });
        }

        queue.push(request);
        Ok(())
    }

    /// Process inference request with micro model
    pub async fn process_micro_inference(
        &self,
        model_id: &str,
        features: &[f32],
    ) -> Result<InferenceResult, MLError> {
        let start_time = Instant::now();

        let micro_models = self.micro_models.read().await;
        let model = micro_models
            .get(model_id)
            .ok_or_else(|| MLError::ModelNotFound(model_id.to_string()))?;

        // Perform forward pass
        let output = self.micro_forward_pass(model, features)?;

        let latency_us = start_time.elapsed().as_micros() as u64;

        Ok(InferenceResult {
            model_id: model_id.to_string(),
            prediction_value: output[0] as f64,
            confidence: self.calculate_confidence(&output).unwrap_or(0.85),
            latency_us,
            timestamp: SystemTime::now()
                .duration_since(UNIX_EPOCH)
                .map_err(|e| MLError::ModelError(format!("Time error: {}", e)))?
                .as_micros() as u64,
            metadata: ModelMetadata::new(
                ModelType::DistilledMicroNet,
                "micro-1.0".to_owned(),
                features.len(),
                1.0, // Micro models use minimal memory
            ),
        })
    }

    /// Perform forward pass through micro model
    pub fn micro_forward_pass(
        &self,
        model: &MicroModel,
        input: &[f32],
    ) -> Result<Vec<f32>, MLError> {
        if model.layer_sizes.is_empty() {
            return Err(MLError::ValidationError {
                message: "Model has no layers".to_owned(),
            });
        }

        let input_size = model.layer_sizes[0];
        if input.len() != input_size {
            return Err(MLError::DimensionMismatch {
                expected: input_size,
                actual: input.len(),
            });
        }

        let mut current_input = input.to_vec(); // cpu-side inference clone
        let mut weight_offset = 0;
        let mut bias_offset = 0;

        // Process each layer
        for layer_idx in 1..model.layer_sizes.len() {
            let input_size = model.layer_sizes[layer_idx - 1];
            let output_size = model.layer_sizes[layer_idx];

            let mut layer_output = vec![0.0; output_size];

            // Matrix multiplication: output = input * weights + bias
            for out_idx in 0..output_size {
                let mut sum = 0.0;

                for in_idx in 0..input_size {
                    let weight_idx = weight_offset + out_idx * input_size + in_idx;
                    if weight_idx >= model.weights.len() {
                        return Err(MLError::ValidationError {
                            message: format!("Weight index {} out of bounds", weight_idx),
                        });
                    }
                    sum += current_input[in_idx] * model.weights[weight_idx];
                }

                // Add bias
                if bias_offset + out_idx >= model.biases.len() {
                    return Err(MLError::ValidationError {
                        message: format!("Bias index {} out of bounds", bias_offset + out_idx),
                    });
                }
                sum += model.biases[bias_offset + out_idx];

                // Apply activation function to all layers (including output)
                layer_output[out_idx] = model.activation.apply(sum);
            }

            current_input = layer_output;
            weight_offset += input_size * output_size;
            bias_offset += output_size;
        }

        Ok(current_input)
    }

    /// Process ONNX inference (production implementation)
    pub async fn process_onnx_inference(
        &self,
        model_id: &str,
        features: &[f32],
    ) -> Result<InferenceResult, MLError> {
        let start_time = Instant::now();

        let models = self.models.read().await;
        let session = models
            .get(model_id)
            .ok_or_else(|| MLError::ModelNotFound(model_id.to_string()))?;

        // Real ONNX inference implementation
        let prediction = match self.run_real_onnx_inference(session, features).await {
            Ok(pred) => pred,
            Err(e) => {
                // Fallback to micro model prediction if ONNX fails
                tracing::warn!("ONNX inference failed, using fallback: {}", e);
                self.generate_intelligent_fallback(features)?
            },
        };

        let latency_us = start_time.elapsed().as_micros() as u64;
        let fallback_config = self.get_fallback_config()?;

        Ok(InferenceResult {
            model_id: model_id.to_string(),
            prediction_value: prediction,
            confidence: fallback_config.default_confidence,
            latency_us,
            timestamp: SystemTime::now()
                .duration_since(UNIX_EPOCH)
                .map_err(|e| MLError::ModelError(format!("Time error: {}", e)))?
                .as_micros() as u64,
            metadata: ModelMetadata::new(
                ModelType::DQN,
                "onnx-1.0".to_owned(),
                features.len(),
                100.0,
            ),
        })
    }

    /// Run actual ONNX inference (DISABLED - ONNX Runtime removed)
    async fn run_real_onnx_inference(
        &self,
        _session: &Session,
        _features: &[f32],
    ) -> Result<f64, MLError> {
        debug!("ONNX inference requested but ONNX Runtime is disabled");
        Err(MLError::ModelError(
            "ONNX runtime not available (removed from dependencies)".to_owned(),
        ))
    }

    /// CONFIGURATION-DRIVEN intelligent fallback prediction
    ///
    /// CRITICAL: All weights and thresholds come from configuration system
    /// to prevent dangerous hardcoded trading signals in production
    fn generate_intelligent_fallback(&self, features: &[f32]) -> Result<f64, MLError> {
        // Load fallback configuration from config system
        let fallback_config = match self.get_fallback_config() {
            Ok(config) => config,
            Err(e) => {
                tracing::error!(
                    "Failed to load fallback config: {}, using emergency safe neutral",
                    e
                );
                return Ok(0.5); // Emergency neutral - the ONLY acceptable hardcoded prediction
            },
        };

        if features.is_empty() {
            tracing::warn!(
                "Empty features in inference engine fallback - using configured neutral"
            );
            return Ok(fallback_config.neutral_prediction);
        }

        // Use market microstructure indicators for prediction
        let feature_count = features.len();

        // Extract key market features (normalized) with bounds checking
        let price_momentum = if feature_count > 0 {
            features[0].clamp(
                fallback_config.feature_bounds.momentum_min,
                fallback_config.feature_bounds.momentum_max,
            )
        } else {
            fallback_config.feature_defaults.momentum_default
        };
        let volume_profile = if feature_count > 1 {
            features[1].clamp(
                fallback_config.feature_bounds.volume_min,
                fallback_config.feature_bounds.volume_max,
            )
        } else {
            fallback_config.feature_defaults.volume_default
        };
        let spread_indicator = if feature_count > 2 {
            features[2].clamp(
                fallback_config.feature_bounds.spread_min,
                fallback_config.feature_bounds.spread_max,
            )
        } else {
            fallback_config.feature_defaults.spread_default
        };
        let volatility_measure = if feature_count > 3 {
            features[3].clamp(
                fallback_config.feature_bounds.volatility_min,
                fallback_config.feature_bounds.volatility_max,
            )
        } else {
            fallback_config.feature_defaults.volatility_default
        };

        // Configuration-driven ensemble prediction
        let momentum_signal = (price_momentum * fallback_config.signal_weights.momentum_weight)
            .tanh()
            * fallback_config.signal_scaling.momentum_scale;
        let volume_signal = (volume_profile * fallback_config.signal_weights.volume_weight).tanh()
            * fallback_config.signal_scaling.volume_scale;
        let spread_signal = -(spread_indicator * fallback_config.signal_weights.spread_weight)
            .tanh()
            * fallback_config.signal_scaling.spread_scale;
        let volatility_signal =
            (volatility_measure * fallback_config.signal_weights.volatility_weight).tanh()
                * fallback_config.signal_scaling.volatility_scale;

        let prediction = fallback_config.base_prediction
            + momentum_signal
            + volume_signal
            + spread_signal
            + volatility_signal;

        // Clamp to configured safe ranges
        Ok(prediction.clamp(
            fallback_config.prediction_bounds.min,
            fallback_config.prediction_bounds.max,
        ) as f64)
    }

    /// Load fallback configuration from config system
    fn get_fallback_config(&self) -> Result<FallbackPredictionConfig, MLError> {
        // In a real implementation, this would load from the config database
        // For now, return conservative safe defaults that prevent dangerous trading signals
        Ok(FallbackPredictionConfig::emergency_safe_defaults())
    }

    /// Calculate confidence score for predictions
    fn calculate_confidence(&self, output: &[f32]) -> Option<f64> {
        if output.is_empty() {
            return None;
        }

        // For single output, use a heuristic based on distance from 0.5
        if output.len() == 1 {
            let prediction = output[0];
            let distance_from_neutral = (prediction - 0.5).abs();
            Some((0.5 + distance_from_neutral * 0.8) as f64)
        } else {
            // For multi-output, use max probability as confidence
            let max_prob = output.iter().fold(f32::NEG_INFINITY, |a, &b| a.max(b));
            Some(max_prob as f64)
        }
    }

    /// Get engine statistics
    pub async fn get_stats(&self) -> InferenceEngineStats {
        let queue = self.request_queue.lock().await;
        let models_count = self.models.read().await.len();
        let micro_models_count = self.micro_models.read().await.len();

        InferenceEngineStats {
            loaded_models: models_count,
            loaded_micro_models: micro_models_count,
            queue_depth: queue.len(),
            total_requests_processed: 0, // Would track this in real implementation
            avg_latency_us: 0.0,
        }
    }
}

/// Inference engine statistics
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InferenceEngineStats {
    /// Number of loaded ONNX models
    pub loaded_models: usize,
    /// Number of loaded micro models
    pub loaded_micro_models: usize,
    /// Current queue depth
    pub queue_depth: usize,
    /// Total requests processed
    pub total_requests_processed: u64,
    /// Average latency in microseconds
    pub avg_latency_us: f64,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn test_inference_engine_creation() {
        let config = IntegrationHubConfig::default();
        let engine = InferenceEngine::new(&config).await;
        assert!(engine.is_ok());

        let engine = engine.unwrap();
        let stats = engine.get_stats().await;
        assert_eq!(stats.loaded_models, 0);
        assert_eq!(stats.loaded_micro_models, 0);
    }

    #[tokio::test]
    async fn test_micro_model_forward_pass() -> Result<(), Box<dyn std::error::Error>> {
        let model = MicroModel {
            model_id: "test".to_owned(),
            weights: vec![0.1, 0.2, -0.1, 0.3], // 2x2 weight matrix
            biases: vec![0.0, 0.1],             // 2 biases
            layer_sizes: vec![2, 2],            // 2 inputs -> 2 outputs
            activation: ActivationFunction::ReLU,
        };

        let config = IntegrationHubConfig::default();
        let engine = InferenceEngine::new(&config).await?;

        let input = vec![1.0, 0.5];
        let result = engine.micro_forward_pass(&model, &input);
        assert!(result.is_ok());

        let output = result?;
        assert_eq!(output.len(), 2);
        // Expected: [max(0, 1.0*0.1 + 0.5*0.2 + 0.0), max(0, 1.0*(-0.1) + 0.5*0.3 + 0.1)]
        //         = [max(0, 0.1 + 0.1 + 0.0), max(0, -0.1 + 0.15 + 0.1)]
        //         = [max(0, 0.2), max(0, 0.15)]
        //         = [0.2, 0.15]
        assert!((output[0] - 0.2).abs() < 1e-6);
        assert!((output[1] - 0.15).abs() < 1e-6);
        Ok(())
    }

    #[test]
    fn test_activation_functions() {
        assert_eq!(0.0_f32.max(0.0), 0.0); // ReLU
        assert_eq!((-1.0_f32).max(0.0), 0.0);
        assert_eq!(1.0_f32.max(0.0), 1.0);

        assert!((0.0_f32.tanh() - 0.0_f32).abs() < 1e-6); // Tanh
        assert!((1.0_f32.tanh() - 0.7615942_f32).abs() < 1e-6);

        let sigmoid_0 = 1.0 / (1.0 + (-0.0_f32).exp());
        assert!((sigmoid_0 - 0.5).abs() < 1e-6); // Sigmoid
    }

    // ==================== INTEGRATION TESTS ====================

    #[tokio::test]
    async fn test_engine_config_default() {
        let config = IntegrationHubConfig::default();
        assert_eq!(config.max_concurrent_models, 5);
        assert!(config.enable_monitoring);
        assert_eq!(config.cache_size, 100);
    }

    #[tokio::test]
    async fn test_engine_config_custom() {
        let config = IntegrationHubConfig {
            max_concurrent_models: 10,
            enable_monitoring: false,
            cache_size: 200,
            ..IntegrationHubConfig::default()
        };
        assert_eq!(config.max_concurrent_models, 10);
        assert!(!config.enable_monitoring);
        assert_eq!(config.cache_size, 200);
    }

    #[test]
    fn test_fallback_config_defaults() {
        let config = FallbackPredictionConfig::default();
        assert!(config.base_prediction >= 0.0 && config.base_prediction <= 1.0);
        assert!(config.neutral_prediction >= 0.0 && config.neutral_prediction <= 1.0);
        assert!(config.default_confidence >= 0.0 && config.default_confidence <= 1.0);
    }

    #[test]
    fn test_signal_weights_valid_range() {
        let weights = SignalWeights {
            momentum_weight: 0.3,
            volume_weight: 0.25,
            spread_weight: 0.25,
            volatility_weight: 0.2,
        };

        let total = weights.momentum_weight
            + weights.volume_weight
            + weights.spread_weight
            + weights.volatility_weight;
        assert!((total - 1.0).abs() < 0.01); // Weights should sum to ~1.0
    }

    #[test]
    fn test_micro_model_creation() {
        let model = MicroModel {
            model_id: "test_model".to_owned(),
            weights: vec![0.1, 0.2, 0.3, 0.4],
            biases: vec![0.0, 0.1],
            layer_sizes: vec![2, 2],
            activation: ActivationFunction::ReLU,
        };

        assert_eq!(model.model_id, "test_model");
        assert_eq!(model.weights.len(), 4);
        assert_eq!(model.biases.len(), 2);
        assert_eq!(model.layer_sizes.len(), 2);
    }

    #[tokio::test]
    async fn test_micro_model_tanh_activation() -> Result<(), Box<dyn std::error::Error>> {
        let model = MicroModel {
            model_id: "tanh_model".to_owned(),
            weights: vec![1.0, 1.0],
            biases: vec![0.0],
            layer_sizes: vec![2, 1],
            activation: ActivationFunction::Tanh,
        };

        let config = IntegrationHubConfig::default();
        let engine = InferenceEngine::new(&config).await?;

        let input = vec![0.5, 0.5];
        let result = engine.micro_forward_pass(&model, &input)?;

        assert_eq!(result.len(), 1);
        let expected = (1.0_f32 * 0.5 + 1.0 * 0.5 + 0.0).tanh();
        assert!((result[0] - expected).abs() < 1e-6);
        Ok(())
    }

    #[tokio::test]
    async fn test_micro_model_sigmoid_activation() -> Result<(), Box<dyn std::error::Error>> {
        let model = MicroModel {
            model_id: "sigmoid_model".to_owned(),
            weights: vec![1.0],
            biases: vec![0.0],
            layer_sizes: vec![1, 1],
            activation: ActivationFunction::Sigmoid,
        };

        let config = IntegrationHubConfig::default();
        let engine = InferenceEngine::new(&config).await?;

        let input = vec![0.0];
        let result = engine.micro_forward_pass(&model, &input)?;

        assert_eq!(result.len(), 1);
        assert!((result[0] - 0.5).abs() < 1e-6); // sigmoid(0) = 0.5
        Ok(())
    }

    #[tokio::test]
    async fn test_engine_statistics_tracking() -> Result<(), Box<dyn std::error::Error>> {
        let config = IntegrationHubConfig::default();
        let engine = InferenceEngine::new(&config).await?;

        let initial_stats = engine.get_stats().await;
        assert_eq!(initial_stats.loaded_models, 0);
        assert_eq!(initial_stats.loaded_micro_models, 0);
        assert_eq!(initial_stats.total_requests_processed, 0);

        Ok(())
    }

    #[tokio::test]
    async fn test_micro_model_empty_input() -> Result<(), Box<dyn std::error::Error>> {
        let model = MicroModel {
            model_id: "test".to_owned(),
            weights: vec![0.1, 0.2],
            biases: vec![0.0],
            layer_sizes: vec![2, 1],
            activation: ActivationFunction::ReLU,
        };

        let config = IntegrationHubConfig::default();
        let engine = InferenceEngine::new(&config).await?;

        let input = vec![];
        let result = engine.micro_forward_pass(&model, &input);
        assert!(result.is_err(), "Empty input should fail");

        Ok(())
    }

    #[tokio::test]
    async fn test_micro_model_dimension_mismatch() -> Result<(), Box<dyn std::error::Error>> {
        let model = MicroModel {
            model_id: "test".to_owned(),
            weights: vec![0.1, 0.2, 0.3, 0.4],
            biases: vec![0.0, 0.1],
            layer_sizes: vec![2, 2],
            activation: ActivationFunction::ReLU,
        };

        let config = IntegrationHubConfig::default();
        let engine = InferenceEngine::new(&config).await?;

        let input = vec![1.0]; // Wrong dimension (1 instead of 2)
        let result = engine.micro_forward_pass(&model, &input);
        assert!(result.is_err(), "Dimension mismatch should fail");

        Ok(())
    }

    #[tokio::test]
    async fn test_micro_model_multi_layer() -> Result<(), Box<dyn std::error::Error>> {
        let model = MicroModel {
            model_id: "multi_layer".to_owned(),
            weights: vec![
                0.1, 0.2, // Layer 1: 2x2
                0.3, 0.4, 0.5, 0.6, // Layer 2: 2x1
            ],
            biases: vec![0.0, 0.0, 0.0],
            layer_sizes: vec![2, 2, 1],
            activation: ActivationFunction::ReLU,
        };

        let config = IntegrationHubConfig::default();
        let engine = InferenceEngine::new(&config).await?;

        let input = vec![1.0, 1.0];
        let result = engine.micro_forward_pass(&model, &input);
        assert!(result.is_ok());

        let output = result?;
        assert_eq!(output.len(), 1);
        assert!(output[0] >= 0.0); // ReLU ensures non-negative

        Ok(())
    }

    #[test]
    fn test_activation_function_enum() {
        let relu = ActivationFunction::ReLU;
        let tanh = ActivationFunction::Tanh;
        let sigmoid = ActivationFunction::Sigmoid;

        assert_ne!(relu, tanh);
        assert_ne!(tanh, sigmoid);
        assert_ne!(sigmoid, relu);
    }

    #[tokio::test]
    async fn test_engine_concurrent_inference() -> Result<(), Box<dyn std::error::Error>> {
        let model = Arc::new(MicroModel {
            model_id: "concurrent_test".to_owned(),
            weights: vec![0.1, 0.2, 0.3, 0.4],
            biases: vec![0.0, 0.1],
            layer_sizes: vec![2, 2],
            activation: ActivationFunction::ReLU,
        });

        let config = IntegrationHubConfig::default();
        let engine = Arc::new(InferenceEngine::new(&config).await?);

        // Spawn concurrent inference tasks
        let mut handles = vec![];
        for i in 0..5 {
            let engine_clone = Arc::clone(&engine);
            let model_clone = Arc::clone(&model);
            let handle = tokio::spawn(async move {
                let input = vec![i as f32, (i + 1) as f32];
                engine_clone.micro_forward_pass(&model_clone, &input)
            });
            handles.push(handle);
        }

        // Wait for all tasks
        for handle in handles {
            let result = handle.await;
            assert!(result.is_ok());
        }

        Ok(())
    }

    #[test]
    fn test_feature_bounds_validation() {
        let bounds = FeatureBounds {
            momentum_min: -0.1,
            momentum_max: 0.1,
            volume_min: 0.0,
            volume_max: 1e9,
            spread_min: 0.0,
            spread_max: 0.1,
            volatility_min: 0.0,
            volatility_max: 0.5,
        };

        assert!(bounds.momentum_max > bounds.momentum_min);
        assert!(bounds.volume_min >= 0.0);
        assert!(bounds.volume_max > bounds.volume_min);
        assert!(bounds.spread_max > bounds.spread_min);
        assert!(bounds.volatility_max > bounds.volatility_min);
    }

    #[test]
    fn test_signal_scaling_factors() {
        let scaling = SignalScaling {
            momentum_scale: 1.0,
            volume_scale: 1e-6,
            spread_scale: 1000.0,
            volatility_scale: 100.0,
        };

        assert_eq!(scaling.momentum_scale, 1.0);
        assert!(scaling.volume_scale < 1.0); // Volume is large, so scale down
        assert!(scaling.spread_scale > 1.0); // Spread is small, so scale up
    }

    #[tokio::test]
    async fn test_engine_batch_prediction() -> Result<(), Box<dyn std::error::Error>> {
        let model = MicroModel {
            model_id: "batch_test".to_owned(),
            weights: vec![0.5, 0.5],
            biases: vec![0.0],
            layer_sizes: vec![2, 1],
            activation: ActivationFunction::ReLU,
        };

        let config = IntegrationHubConfig::default();
        let engine = InferenceEngine::new(&config).await?;

        // Test multiple predictions
        let inputs = vec![vec![1.0, 1.0], vec![0.5, 0.5], vec![2.0, 2.0]];

        for input in inputs {
            let result = engine.micro_forward_pass(&model, &input);
            assert!(result.is_ok());
        }

        Ok(())
    }

    #[test]
    fn test_prediction_bounds_validation() {
        let bounds = PredictionBounds { min: 0.0, max: 1.0 };

        assert!(bounds.min <= bounds.max);
        assert!(bounds.min >= 0.0);
        assert!(bounds.max <= 1.0);
    }
}

// Add support for external futures crate functions
mod futures {
    pub(super) mod future {
        pub(crate) async fn join_all<I>(iter: I) -> Vec<<I::Item as std::future::Future>::Output>
        where
            I: IntoIterator,
            I::Item: std::future::Future,
        {
            let futures: Vec<_> = iter.into_iter().collect();
            let mut results = Vec::with_capacity(futures.len());

            for future in futures {
                results.push(future.await);
            }

            results
        }
    }
}
