//! Kelly Criterion Position Sizing Service
//!
//! This service integrates the ML-enhanced Kelly Criterion optimizer with the risk management
//! position tracker to provide real-time optimal position sizing recommendations.
//!
//! # Features
//!
//! - Real-time Kelly position sizing using market data
//! - Integration with position tracker for portfolio state
//! - Volatility adjustments and ML enhancements
//! - API for trading components to request position sizes
//! - Risk-aware position recommendations
//! - Portfolio concentration management
//!
//! # Usage
//!
//! ```no_run
//! // Kelly position sizing service usage example
//! // Note: This is a conceptual example - actual implementation details may vary
//! use ml::risk::kelly_position_sizing_service::{KellyPositionSizingService, KellyServiceConfig};
//!
//! #[tokio::main]
//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
//!     // Initialize the Kelly position sizing service
//!     let config = KellyServiceConfig::default();
//!
//!     // Service would be initialized with config and dependencies
//!     // let kelly_service = KellyPositionSizingService::new(config).await?;
//!
//!     // Position sizing requests would be made through the service API
//!     // let recommendation = kelly_service.get_position_sizing(&request).await?;
//!
//!     Ok(())
//! }
//! ```

use std::collections::HashMap;
use std::fmt::Write as _;
use std::sync::Arc;
use std::time::Duration;

use chrono::{DateTime, Utc};
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use tokio::sync::{broadcast, RwLock};
use tracing::{debug, info, warn};

use crate::{KellyCriterionOptimizer, KellyOptimizerConfig, KellyPositionRecommendation};
use crate::{MLError, MLResult as Result, MarketDataSnapshot};
use common::types::Price;
use risk::position_tracker::{EnhancedRiskPosition, PositionUpdateEvent};
use risk::risk_types::{InstrumentId, PortfolioId, StrategyId};

// CIRCULAR DEPENDENCY FIX: Using trait-based interfaces
// Production types until we implement proper abstractions

/// Production PositionTracker with required methods
#[derive(Debug, Clone)]
pub struct PositionTracker {
    name: String,
}

impl PositionTracker {
    pub fn new() -> Self {
        Self {
            name: "production_tracker".to_owned(),
        }
    }

    pub fn subscribe_to_updates(&self) -> broadcast::Receiver<PositionUpdateEvent> {
        let (_tx, rx) = broadcast::channel(100);
        rx
    }

    pub fn get_enhanced_position(
        &self,
        _portfolio_id: &str,
        _asset_id: &str,
    ) -> Option<EnhancedRiskPosition> {
        // Known limitation: Position retrieval not implemented
        // Production should query from RiskEngine or TradingEngine state
        None
    }

    pub fn get_portfolio_summary(&self, _portfolio_id: &str) -> Option<PortfolioSummary> {
        Some(PortfolioSummary {
            total_value: Price::ZERO,
            positions_count: 0,
        })
    }
}

// Production portfolio summary struct
#[derive(Debug, Clone)]
pub struct PortfolioSummary {
    pub total_value: Price,
    pub positions_count: usize,
}

// Direct type usage - no compatibility wrappers

/// Risk tolerance levels for position sizing
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum RiskTolerance {
    /// Conservative: 25% of full Kelly
    Conservative,
    /// Moderate: 50% of full Kelly
    Moderate,
    /// Aggressive: 75% of full Kelly
    Aggressive,
    /// Full Kelly: 100% of calculated Kelly
    FullKelly,
    /// Custom fractional Kelly
    Custom(f64),
}

impl RiskTolerance {
    /// Get the Kelly fraction for this risk tolerance
    pub fn kelly_fraction(&self) -> f64 {
        match self {
            RiskTolerance::Conservative => 0.25,
            RiskTolerance::Moderate => 0.50,
            RiskTolerance::Aggressive => 0.75,
            RiskTolerance::FullKelly => 1.0,
            RiskTolerance::Custom(fraction) => *fraction,
        }
    }
}

/// Position sizing request from trading components
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PositionSizingRequest {
    /// Asset to size position for
    pub asset_id: String, // Using String temporarily
    /// Portfolio identifier
    pub portfolio_id: PortfolioId,
    /// Strategy identifier
    pub strategy_id: StrategyId,
    /// Optional target allocation (0.0 to 1.0)
    pub target_allocation: Option<f64>,
    /// Risk tolerance level
    pub risk_tolerance: RiskTolerance,
    /// Optional current market price
    pub current_price: Option<Price>,
    /// Optional historical returns data
    pub historical_returns: Option<Vec<f64>>,
    /// Maximum position size override
    pub max_position_size: Option<Price>,
    /// Timestamp of request
    pub requested_at: DateTime<Utc>,
}

impl PositionSizingRequest {
    /// Create a new position sizing request
    pub fn new(
        asset_id: String,
        portfolio_id: PortfolioId,
        strategy_id: StrategyId,
        risk_tolerance: RiskTolerance,
    ) -> Self {
        Self {
            asset_id,
            portfolio_id,
            strategy_id,
            target_allocation: None,
            risk_tolerance,
            current_price: None,
            historical_returns: None,
            max_position_size: None,
            requested_at: Utc::now(),
        }
    }

    /// Set target allocation
    pub fn with_target_allocation(mut self, allocation: f64) -> Self {
        self.target_allocation = Some(allocation);
        self
    }

    /// Set current market price
    pub fn with_current_price(mut self, price: Price) -> Self {
        self.current_price = Some(price);
        self
    }

    /// Set historical returns
    pub fn with_historical_returns(mut self, returns: Vec<f64>) -> Self {
        self.historical_returns = Some(returns);
        self
    }

    /// Set maximum position size
    pub fn with_max_position_size(mut self, max_size: Price) -> Self {
        self.max_position_size = Some(max_size);
        self
    }
}

/// Enhanced position sizing recommendation with ML insights
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EnhancedPositionSizingRecommendation {
    /// Base Kelly recommendation
    pub kelly_recommendation: KellyPositionRecommendation,
    /// Adjusted position size based on risk tolerance
    pub adjusted_position_fraction: f64,
    /// Recommended position size in base currency
    pub recommended_position_size: Price,
    /// Current portfolio allocation to this asset
    pub current_allocation: f64,
    /// Portfolio concentration metrics
    pub portfolio_concentration: f64,
    /// Volatility-adjusted recommendation
    pub volatility_adjusted_fraction: f64,
    /// Risk-adjusted confidence score
    pub risk_adjusted_confidence: f64,
    /// Portfolio-level risk metrics
    pub portfolio_beta: f64,
    /// Asset correlation with portfolio
    pub portfolio_correlation: f64,
    /// Concentration risk warning
    pub concentration_warning: Option<String>,
    /// Recommendation rationale
    pub rationale: String,
    /// Service metadata
    pub service_version: String,
    /// Calculation timestamp
    pub calculated_at: DateTime<Utc>,
}

/// Configuration for Kelly Position Sizing Service
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KellyServiceConfig {
    /// Kelly optimizer configuration
    pub kelly_config: KellyOptimizerConfig,
    /// Maximum portfolio allocation for single asset
    pub max_single_asset_allocation: f64,
    /// Minimum confidence threshold for recommendations
    pub min_confidence_threshold: f64,
    /// Historical data lookback period (days)
    pub lookback_period_days: usize,
    /// Enable volatility adjustments
    pub enable_volatility_adjustments: bool,
    /// Enable concentration risk monitoring
    pub enable_concentration_monitoring: bool,
    /// Cache TTL for position data
    pub cache_ttl: Duration,
    /// Maximum recommendation age before refresh
    pub max_recommendation_age: Duration,
}

impl Default for KellyServiceConfig {
    fn default() -> Self {
        Self {
            kelly_config: KellyOptimizerConfig::default(),
            max_single_asset_allocation: 0.15, // 15% max allocation
            min_confidence_threshold: 0.6,
            lookback_period_days: 252, // 1 year
            enable_volatility_adjustments: true,
            enable_concentration_monitoring: true,
            cache_ttl: Duration::from_secs(300), // 5 minutes
            max_recommendation_age: Duration::from_secs(60), // 1 minute
        }
    }
}

/// Kelly Position Sizing Service
#[derive(Debug)]
pub struct KellyPositionSizingService {
    /// Kelly criterion optimizer
    kelly_optimizer: KellyCriterionOptimizer,
    /// Position tracker for portfolio state
    position_tracker: Arc<PositionTracker>,
    /// Service configuration
    config: KellyServiceConfig,
    /// Cached recommendations
    recommendation_cache:
        Arc<RwLock<HashMap<String, (EnhancedPositionSizingRecommendation, DateTime<Utc>)>>>,
    /// Market data cache (latest snapshot per instrument)
    market_data_cache: Arc<RwLock<HashMap<InstrumentId, MarketDataSnapshot>>>,
    /// Previous prices per asset, used to compute returns from successive updates
    previous_prices: Arc<RwLock<HashMap<String, f64>>>,
    /// Rolling return history per asset (most recent last)
    return_history: Arc<RwLock<HashMap<String, Vec<f64>>>>,
    /// Position update receiver
    position_update_receiver: Option<broadcast::Receiver<PositionUpdateEvent>>,
}

impl KellyPositionSizingService {
    /// Create a new Kelly Position Sizing Service
    pub async fn new(
        config: KellyServiceConfig,
        position_tracker: PositionTracker,
    ) -> Result<Self> {
        let kelly_optimizer = KellyCriterionOptimizer::new(config.kelly_config.clone())?;
        let position_tracker = Arc::new(position_tracker);

        // Subscribe to position updates
        let position_update_receiver = Some(position_tracker.subscribe_to_updates());

        Ok(Self {
            kelly_optimizer,
            position_tracker,
            config,
            recommendation_cache: Arc::new(RwLock::new(HashMap::new())),
            market_data_cache: Arc::new(RwLock::new(HashMap::new())),
            previous_prices: Arc::new(RwLock::new(HashMap::new())),
            return_history: Arc::new(RwLock::new(HashMap::new())),
            position_update_receiver,
        })
    }

    /// Get position sizing recommendation
    pub async fn get_position_sizing(
        &self,
        request: &PositionSizingRequest,
    ) -> Result<EnhancedPositionSizingRecommendation> {
        debug!(
            "Processing position sizing request for {} in portfolio {}",
            request.asset_id, request.portfolio_id
        );

        // Check cache first
        if let Some(cached) = self.get_cached_recommendation(request).await? {
            debug!("Returning cached recommendation for {}", request.asset_id);
            return Ok(cached);
        }

        // Get current position data
        let _current_position = self
            .position_tracker
            .get_enhanced_position(&request.portfolio_id, &request.asset_id.clone());

        // Get portfolio summary for concentration analysis
        let portfolio_summary = self
            .position_tracker
            .get_portfolio_summary(&request.portfolio_id);

        // Get historical returns data
        let historical_returns = if let Some(returns) = &request.historical_returns {
            returns.clone()
        } else {
            self.get_historical_returns(&request.asset_id).await?
        };

        // Calculate basic Kelly recommendation
        let kelly_recommendation = self
            .kelly_optimizer
            .recommend_position(request.asset_id.clone(), &historical_returns)?;

        // Apply risk tolerance adjustment
        let risk_fraction = request.risk_tolerance.kelly_fraction();
        let adjusted_fraction = kelly_recommendation.recommended_fraction * risk_fraction;

        // Get current market price
        let _current_price = if let Some(price) = request.current_price {
            price
        } else {
            self.get_current_market_price(&request.asset_id).await?
        };

        // Calculate portfolio metrics
        let (current_allocation, portfolio_concentration, portfolio_beta, portfolio_correlation) =
            self.calculate_portfolio_metrics(
                &request.portfolio_id,
                &request.asset_id,
                &portfolio_summary,
            )
            .await?;

        // Apply concentration limits
        let concentration_adjusted_fraction = self.apply_concentration_limits(
            adjusted_fraction,
            current_allocation,
            portfolio_concentration,
        )?;

        // Apply volatility adjustments if enabled
        let volatility_adjusted_fraction = if self.config.enable_volatility_adjustments {
            self.apply_volatility_adjustments(
                concentration_adjusted_fraction,
                kelly_recommendation.volatility,
                &historical_returns,
            )?
        } else {
            concentration_adjusted_fraction
        };

        // Calculate recommended position size
        let portfolio_value = portfolio_summary
            .map(|s| s.total_value)
            .unwrap_or(Price::ZERO);

        let recommended_position_size = if portfolio_value > Price::ZERO {
            let fraction_decimal =
                Decimal::try_from(volatility_adjusted_fraction).map_err(|_| {
                    MLError::InvalidInput("Failed to convert fraction to decimal".to_owned())
                })?;
            let position_value = portfolio_value.to_decimal().map_err(|e| {
                MLError::InvalidInput(format!(
                    "Failed to convert portfolio value to decimal: {}",
                    e
                ))
            })? * fraction_decimal;
            Price::from_decimal(position_value)
        } else {
            Price::ZERO
        };

        // Apply maximum position size limit if specified
        let final_position_size = if let Some(max_size) = request.max_position_size {
            std::cmp::min(recommended_position_size, max_size)
        } else {
            recommended_position_size
        };

        // Calculate risk-adjusted confidence
        let risk_adjusted_confidence = kelly_recommendation.confidence
            * (1.0 - (portfolio_concentration - self.config.max_single_asset_allocation).max(0.0));

        // Generate concentration warning if applicable
        let concentration_warning =
            (portfolio_concentration > self.config.max_single_asset_allocation).then(|| {
                format!(
                    "Portfolio concentration ({:.1}%) exceeds maximum allowed ({:.1}%)",
                    portfolio_concentration * 100.0,
                    self.config.max_single_asset_allocation * 100.0
                )
            });

        // Generate recommendation rationale
        let rationale = self.generate_rationale(
            &kelly_recommendation,
            risk_fraction,
            portfolio_concentration,
            risk_adjusted_confidence,
        );

        let recommendation = EnhancedPositionSizingRecommendation {
            kelly_recommendation,
            adjusted_position_fraction: volatility_adjusted_fraction,
            recommended_position_size: final_position_size,
            current_allocation,
            portfolio_concentration,
            volatility_adjusted_fraction,
            risk_adjusted_confidence,
            portfolio_beta,
            portfolio_correlation,
            concentration_warning,
            rationale,
            service_version: std::env::var("CARGO_PKG_VERSION")
                .unwrap_or_else(|_| "1.0.0".to_owned()),
            calculated_at: Utc::now(),
        };

        // Cache the recommendation
        self.cache_recommendation(request, recommendation.clone())
            .await?;

        info!("✅ Generated Kelly position sizing recommendation for {} - Size: ${:.2}, Fraction: {:.2}%",
              request.asset_id, final_position_size, volatility_adjusted_fraction * 100.0);

        Ok(recommendation)
    }

    /// Update market data for an asset.
    ///
    /// Computes the log return from the previous price and appends it to the
    /// rolling return history for this asset. The history is capped at
    /// `lookback_period_days` entries.
    pub async fn update_market_data(&self, market_data: MarketDataSnapshot) -> Result<()> {
        let asset_id = market_data.symbol.clone();
        let current_price: f64 = market_data
            .price
            .try_into()
            .unwrap_or(0.0);

        debug!("Updating market data for {}: price={}", asset_id, current_price);

        // Compute return from previous price
        if current_price > 0.0 {
            let mut prev = self.previous_prices.write().await;
            if let Some(&prev_price) = prev.get(&asset_id) {
                if prev_price > 0.0 {
                    let log_return = (current_price / prev_price).ln();
                    let mut history = self.return_history.write().await;
                    let returns = history.entry(asset_id.clone()).or_default();
                    returns.push(log_return);
                    // Cap to lookback window
                    let max_len = self.config.lookback_period_days;
                    if returns.len() > max_len {
                        let excess = returns.len() - max_len;
                        returns.drain(..excess);
                    }
                }
            }
            prev.insert(asset_id.clone(), current_price);
        }

        // Update market data cache
        let mut cache = self.market_data_cache.write().await;
        cache.insert(asset_id.clone(), market_data);

        // Invalidate cached recommendations for this asset
        self.invalidate_cache_for_asset(&asset_id).await?;

        Ok(())
    }

    /// Get historical returns for an asset from accumulated market data updates.
    ///
    /// Returns are computed in [`update_market_data`] as log returns from
    /// successive price snapshots. If the caller provides `historical_returns`
    /// in the request, those take precedence.
    async fn get_historical_returns(&self, asset_id: &String) -> Result<Vec<f64>> {
        let history = self.return_history.read().await;
        if let Some(returns) = history.get(asset_id) {
            if returns.len() >= 10 {
                return Ok(returns.clone());
            }
            warn!(
                "Insufficient return history for {} ({} entries, need >= 10). \
                 Feed market data via update_market_data() to build history.",
                asset_id,
                returns.len()
            );
        } else {
            warn!(
                "No return history for {}. Feed market data via update_market_data() first.",
                asset_id
            );
        }
        Err(MLError::InvalidInput(format!(
            "Insufficient historical returns for {}. \
             Call update_market_data() with at least 10 successive price snapshots.",
            asset_id
        )))
    }

    /// Get current market price for an asset from the market data cache.
    async fn get_current_market_price(&self, asset_id: &String) -> Result<Price> {
        let cache = self.market_data_cache.read().await;
        if let Some(market_data) = cache.get(asset_id) {
            let price_f64: f64 = market_data
                .price
                .try_into()
                .unwrap_or(0.0);
            Price::from_f64(price_f64)
                .map_err(|e| MLError::InvalidInput(format!("Failed to create price: {:?}", e)))
        } else {
            Err(MLError::InvalidInput(format!(
                "No market data available for {}. Call update_market_data() first.",
                asset_id
            )))
        }
    }

    /// Calculate portfolio metrics from the position tracker.
    ///
    /// Returns (current_allocation, portfolio_concentration, portfolio_beta, portfolio_correlation).
    /// Uses real position data when available; defaults to conservative values otherwise.
    async fn calculate_portfolio_metrics(
        &self,
        portfolio_id: &PortfolioId,
        asset_id: &String,
        portfolio_summary: &Option<PortfolioSummary>,
    ) -> Result<(f64, f64, f64, f64)> {
        // Attempt to get real position from tracker
        let position = self
            .position_tracker
            .get_enhanced_position(portfolio_id, asset_id);

        let (current_allocation, portfolio_concentration) = match (position, portfolio_summary) {
            (Some(pos), Some(summary)) if summary.total_value > Price::ZERO => {
                let total_val: f64 = summary
                    .total_value
                    .to_decimal()
                    .ok()
                    .and_then(|d| d.try_into().ok())
                    .unwrap_or(1.0);
                let pos_val: f64 = pos
                    .base_position
                    .market_value
                    .to_decimal()
                    .ok()
                    .and_then(|d| d.try_into().ok())
                    .unwrap_or(0.0);
                let alloc = if total_val > 0.0 {
                    pos_val / total_val
                } else {
                    0.0
                };
                let conc = if summary.positions_count > 0 {
                    1.0 / summary.positions_count as f64
                } else {
                    1.0
                };
                (alloc, conc)
            }
            _ => {
                debug!(
                    "No live position/portfolio data for {}, using conservative defaults",
                    asset_id
                );
                (0.0, 0.0)
            }
        };

        // Beta and correlation require multi-asset return series which we don't
        // track yet. Use market-neutral defaults (beta=1, correlation=0.5).
        let portfolio_beta = 1.0;
        let portfolio_correlation = 0.5;

        Ok((
            current_allocation,
            portfolio_concentration,
            portfolio_beta,
            portfolio_correlation,
        ))
    }

    /// Apply concentration limits to the position fraction
    fn apply_concentration_limits(
        &self,
        fraction: f64,
        current_allocation: f64,
        portfolio_concentration: f64,
    ) -> Result<f64> {
        // If adding this position would exceed concentration limits, reduce it
        let max_additional_allocation =
            self.config.max_single_asset_allocation - current_allocation;
        let concentration_adjusted = fraction.min(max_additional_allocation.max(0.0));

        // Further reduce if overall portfolio concentration is high
        let concentration_penalty = if portfolio_concentration > 0.5 {
            0.8 // Reduce by 20% for high concentration
        } else {
            1.0
        };

        Ok(concentration_adjusted * concentration_penalty)
    }

    /// Apply volatility adjustments to position sizing
    fn apply_volatility_adjustments(
        &self,
        fraction: f64,
        asset_volatility: f64,
        historical_returns: &[f64],
    ) -> Result<f64> {
        // Calculate rolling volatility from historical returns
        let mean_return = historical_returns.iter().sum::<f64>() / historical_returns.len() as f64;
        let variance = historical_returns
            .iter()
            .map(|r| (r - mean_return).powi(2))
            .sum::<f64>()
            / historical_returns.len() as f64;
        let calculated_volatility = variance.sqrt();

        // Use the higher of asset volatility or calculated volatility
        let effective_volatility = asset_volatility.max(calculated_volatility);

        // Reduce position size for high volatility assets
        let volatility_adjustment = if effective_volatility > 0.03 {
            // High volatility (>3% daily) - reduce position
            (0.03 / effective_volatility).min(1.0)
        } else {
            1.0
        };

        Ok(fraction * volatility_adjustment)
    }

    /// Generate recommendation rationale
    fn generate_rationale(
        &self,
        kelly_rec: &KellyPositionRecommendation,
        risk_fraction: f64,
        portfolio_concentration: f64,
        risk_adjusted_confidence: f64,
    ) -> String {
        let mut rationale = format!(
            "Kelly Criterion recommends {:.1}% allocation with {:.0}% confidence. ",
            kelly_rec.recommended_fraction * 100.0,
            kelly_rec.confidence * 100.0
        );

        if risk_fraction < 1.0 {
            _ = write!(
                rationale,
                "Applied {:.0}% risk tolerance adjustment. ",
                risk_fraction * 100.0
            );
        }

        if portfolio_concentration > 0.3 {
            rationale.push_str("Portfolio concentration is elevated. ");
        }

        if risk_adjusted_confidence < 0.7 {
            rationale.push_str("Reduced confidence due to risk factors.");
        } else {
            rationale.push_str("High confidence recommendation.");
        }

        rationale
    }

    /// Get cached recommendation if valid
    async fn get_cached_recommendation(
        &self,
        request: &PositionSizingRequest,
    ) -> Result<Option<EnhancedPositionSizingRecommendation>> {
        let cache_key = format!(
            "{}:{}:{}",
            request.portfolio_id, request.asset_id, request.strategy_id
        );
        let cache = self.recommendation_cache.read().await;

        if let Some((recommendation, cached_at)) = cache.get(&cache_key) {
            let age = Utc::now().signed_duration_since(*cached_at);
            if age.to_std().unwrap_or(Duration::MAX) < self.config.max_recommendation_age {
                return Ok(Some(recommendation.clone()));
            }
        }

        Ok(None)
    }

    /// Cache a recommendation
    async fn cache_recommendation(
        &self,
        request: &PositionSizingRequest,
        recommendation: EnhancedPositionSizingRecommendation,
    ) -> Result<()> {
        let cache_key = format!(
            "{}:{}:{}",
            request.portfolio_id, request.asset_id, request.strategy_id
        );
        let mut cache = self.recommendation_cache.write().await;
        cache.insert(cache_key, (recommendation, Utc::now()));
        Ok(())
    }

    /// Invalidate cache for a specific asset
    async fn invalidate_cache_for_asset(&self, asset_id: &InstrumentId) -> Result<()> {
        let mut cache = self.recommendation_cache.write().await;
        cache.retain(|key, _| !key.contains(asset_id));
        Ok(())
    }

    /// Get service metrics
    pub async fn get_metrics(&self) -> KellyServiceMetrics {
        let cache = self.recommendation_cache.read().await;
        KellyServiceMetrics {
            cached_recommendations: cache.len(),
            service_version: std::env::var("CARGO_PKG_VERSION")
                .unwrap_or_else(|_| "1.0.0".to_owned()),
            uptime: std::time::SystemTime::now()
                .duration_since(std::time::UNIX_EPOCH)
                .unwrap_or_default(),
        }
    }
}

/// Service metrics
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KellyServiceMetrics {
    /// Number of cached recommendations
    pub cached_recommendations: usize,
    /// Service version
    pub service_version: String,
    /// Service uptime
    pub uptime: Duration,
}

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

    #[tokio::test]
    async fn test_kelly_service_creation() -> Result<()> {
        let config = KellyServiceConfig::default();
        let position_tracker = PositionTracker::new();

        let service = KellyPositionSizingService::new(config, position_tracker).await?;
        let metrics = service.get_metrics().await;

        assert_eq!(metrics.cached_recommendations, 0);
        assert!(!metrics.service_version.is_empty());

        Ok(())
    }

    #[tokio::test]
    async fn test_position_sizing_request() -> std::result::Result<(), Box<dyn std::error::Error>> {
        let asset_id = "AAPL".to_owned();
        let portfolio_id = "test_portfolio".to_owned();
        let strategy_id = "test_strategy".to_owned();

        let request = PositionSizingRequest::new(
            asset_id.clone(),
            portfolio_id.clone(),
            strategy_id.clone(),
            RiskTolerance::Moderate,
        )
        .with_target_allocation(0.05)
        .with_current_price(Price::from_f64(150.0)?);

        assert_eq!(request.asset_id, asset_id);
        assert_eq!(request.portfolio_id, portfolio_id);
        assert_eq!(request.strategy_id, strategy_id);
        assert_eq!(request.target_allocation, Some(0.05));
        assert_eq!(request.current_price, Some(Price::from_f64(150.0)?));

        Ok(())
    }

    #[tokio::test]
    async fn test_risk_tolerance_fractions() {
        assert_eq!(RiskTolerance::Conservative.kelly_fraction(), 0.25);
        assert_eq!(RiskTolerance::Moderate.kelly_fraction(), 0.50);
        assert_eq!(RiskTolerance::Aggressive.kelly_fraction(), 0.75);
        assert_eq!(RiskTolerance::FullKelly.kelly_fraction(), 1.0);
        assert_eq!(RiskTolerance::Custom(0.33).kelly_fraction(), 0.33);
    }
}
