//! Bridge adapter: wraps `ml::ensemble::ModelInferenceAdapter` into `ml::MLModel`
//!
//! The `EnsembleCoordinator` in `trading_service` expects `Arc<dyn MLModel>`
//! instances in its `ModelRegistry`, while the real candle-backed inference
//! adapters (`DqnInferenceAdapter`, `PpoInferenceAdapter`, etc.) implement
//! the `ModelInferenceAdapter` trait from `ml::ensemble::inference_adapter`.
//!
//! This module provides `InferenceAdapterBridge` which wraps any
//! `ModelInferenceAdapter` and presents it as an `MLModel` so that the
//! ensemble coordinator can call real model inference.

use ml::ensemble::inference_adapter::{FeatureVector, ModelInferenceAdapter};
use ml::{Features, MLModel, MLResult, ModelMetadata, ModelPrediction, ModelType};

/// Wraps a `ModelInferenceAdapter` to implement the `MLModel` trait.
///
/// This allows the real candle-backed inference adapters (DQN, PPO, TFT,
/// Mamba2) to be registered in the trading_service `EnsembleCoordinator`
/// via `register_loaded_model`.
pub struct InferenceAdapterBridge {
    adapter: Box<dyn ModelInferenceAdapter>,
    model_type: ModelType,
    model_id: String,
}

impl std::fmt::Debug for InferenceAdapterBridge {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("InferenceAdapterBridge")
            .field("model_id", &self.model_id)
            .field("model_type", &self.model_type)
            .field("is_ready", &self.adapter.is_ready())
            .finish()
    }
}

impl InferenceAdapterBridge {
    /// Create a new bridge wrapping the given adapter.
    ///
    /// * `adapter`    - Real inference adapter (DQN, PPO, TFT, or Mamba2)
    /// * `model_id`   - Identifier used in the ensemble coordinator (e.g. "DQN")
    /// * `model_type` - Corresponding `ModelType` enum variant
    pub fn new(
        adapter: Box<dyn ModelInferenceAdapter>,
        model_id: String,
        model_type: ModelType,
    ) -> Self {
        Self {
            adapter,
            model_type,
            model_id,
        }
    }
}

#[async_trait::async_trait]
impl MLModel for InferenceAdapterBridge {
    fn name(&self) -> &str {
        &self.model_id
    }

    fn model_type(&self) -> ModelType {
        self.model_type
    }

    async fn predict(&self, features: &Features) -> MLResult<ModelPrediction> {
        let fv = FeatureVector {
            values: features.values.clone(),
            timestamp: features.timestamp as i64,
        };

        let ensemble_pred = self.adapter.predict(&fv)?;

        Ok(ModelPrediction::new(
            self.model_id.clone(),
            ensemble_pred.direction,
            ensemble_pred.confidence,
        ))
    }

    fn get_confidence(&self) -> f64 {
        // Return a reasonable default; actual confidence comes per-prediction
        0.8
    }

    fn is_ready(&self) -> bool {
        self.adapter.is_ready()
    }

    fn get_metadata(&self) -> ModelMetadata {
        ModelMetadata::new(
            self.model_type,
            "1.0.0".to_string(),
            51, // production 51-dim feature vector
            0.0,
        )
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use ml::ensemble::inference_adapter::{EnsemblePrediction, PredictionMeta};

    struct StubAdapter {
        name: &'static str,
    }

    impl ModelInferenceAdapter for StubAdapter {
        fn model_name(&self) -> &str {
            self.name
        }

        fn predict(&self, _features: &FeatureVector) -> MLResult<EnsemblePrediction> {
            Ok(EnsemblePrediction {
                model_name: self.name.to_string(),
                direction: 0.6,
                confidence: 0.85,
                metadata: PredictionMeta::default(),
            })
        }

        fn is_ready(&self) -> bool {
            true
        }
    }

    // SAFETY: StubAdapter has no mutable interior state.
    unsafe impl Send for StubAdapter {}
    unsafe impl Sync for StubAdapter {}

    #[tokio::test]
    async fn test_bridge_predict() {
        let adapter = Box::new(StubAdapter { name: "DQN" });
        let bridge = InferenceAdapterBridge::new(
            adapter,
            "DQN".to_string(),
            ModelType::DQN,
        );

        let features = Features::new(
            vec![0.5; 51],
            (0..51).map(|i| format!("f{}", i)).collect(),
        );

        let prediction = bridge.predict(&features).await.unwrap();
        assert_eq!(prediction.model_id, "DQN");
        assert!((prediction.value - 0.6).abs() < 1e-6);
        assert!((prediction.confidence - 0.85).abs() < 1e-6);
    }

    #[test]
    fn test_bridge_metadata() {
        let adapter = Box::new(StubAdapter { name: "PPO" });
        let bridge = InferenceAdapterBridge::new(
            adapter,
            "PPO".to_string(),
            ModelType::PPO,
        );

        assert_eq!(bridge.name(), "PPO");
        assert!(bridge.is_ready());
        assert_eq!(bridge.model_type(), ModelType::PPO);
    }
}
