//! Risk management service implementation

use crate::proto::risk::{
    risk_service_server::RiskService, CircuitBreakerStatus, CircuitBreakerType,
    EmergencyStopRequest, EmergencyStopResponse, GetCircuitBreakerStatusRequest,
    GetCircuitBreakerStatusResponse, GetPositionRiskRequest, GetPositionRiskResponse,
    GetRiskMetricsRequest, GetRiskMetricsResponse, GetVaRRequest, GetVaRResponse,
    PositionRisk as ProtoPositionRisk, RiskAlertEvent, RiskAlertSeverity, RiskAlertType,
    RiskLevel, RiskMetrics, RiskScore, RiskViolation, RiskViolationType,
    StreamCircuitBreakerStatusRequest, StreamRiskAlertsRequest, StreamRiskMetricsRequest,
    StreamVaRRequest, SymbolVaR, VaRChangeType, VaREvent, VaRMethod, ValidateOrderRequest,
    ValidateOrderResponse,
};
use crate::repositories::ConfigRepository;
use crate::state::TradingServiceState;
use tonic::{Request, Response, Status};
use tracing::{debug, error, info, warn, instrument};

/// Risk service implementation
#[derive(Debug, Clone)]
pub struct RiskServiceImpl {
    state: TradingServiceState,
}

/// Annualized risk-free rate assumption for Sharpe/Sortino calculations.
/// Uses 3-month T-bill proxy; override via config if needed.
const RISK_FREE_RATE_ANNUAL: f64 = 0.05;

/// Minimum number of return observations required for ratio calculations.
/// Below this threshold, Sharpe/Sortino return 0.0 to avoid noisy estimates.
const MIN_RETURN_OBSERVATIONS: usize = 5;

impl RiskServiceImpl {
    /// Create new risk service
    pub fn new(state: TradingServiceState) -> Self {
        Self { state }
    }

    /// Fetch open positions from the trading repository.
    ///
    /// Returns an empty vec (with a warning) if the repository query fails,
    /// so that callers can still produce partial metrics.
    async fn fetch_positions(&self) -> Vec<crate::repositories::TradingPosition> {
        match self
            .state
            .trading_repository
            .get_positions(None, None)
            .await
        {
            Ok(positions) => positions,
            Err(e) => {
                warn!("Failed to fetch positions for risk metrics: {}", e);
                Vec::new()
            },
        }
    }

    /// Fetch recent execution history to derive a return series.
    ///
    /// Returns an empty vec (with a warning) on failure.
    async fn fetch_executions(&self) -> Vec<crate::repositories::ExecutionEvent> {
        let request = crate::proto::trading::GetExecutionHistoryRequest {
            account_id: None,
            symbol: None,
            start_time: None,
            end_time: None,
            limit: Some(1000),
        };
        match self
            .state
            .trading_repository
            .get_execution_history(&request)
            .await
        {
            Ok(executions) => executions,
            Err(e) => {
                warn!("Failed to fetch execution history for risk metrics: {}", e);
                Vec::new()
            },
        }
    }

    /// Compute daily returns from execution prices.
    ///
    /// Groups executions by timestamp (day boundary) and uses the mean execution
    /// price per day. Returns are simple: (P_t / P_{t-1}) - 1.
    fn compute_returns_from_executions(
        executions: &[crate::repositories::ExecutionEvent],
    ) -> Vec<f64> {
        if executions.len() < 2 {
            return Vec::new();
        }

        // Collect prices sorted by timestamp (ascending)
        let mut prices: Vec<(i64, f64)> = executions
            .iter()
            .filter(|e| e.price > 0.0)
            .map(|e| (e.timestamp, e.price))
            .collect();
        prices.sort_by_key(|(ts, _)| *ts);

        if prices.len() < 2 {
            return Vec::new();
        }

        // Compute simple returns from consecutive prices
        prices
            .windows(2)
            .filter_map(|w| {
                let prev = w[0].1;
                let curr = w[1].1;
                if prev > 0.0 {
                    Some(curr / prev - 1.0)
                } else {
                    None
                }
            })
            .collect()
    }

    /// Calculate current drawdown from positions' unrealized PnL.
    ///
    /// Drawdown = total_unrealized_loss / total_portfolio_value (as a positive fraction).
    /// If there are no positions or portfolio value is zero, returns 0.0.
    fn compute_current_drawdown(positions: &[crate::repositories::TradingPosition]) -> f64 {
        if positions.is_empty() {
            return 0.0;
        }

        let total_market_value: f64 = positions.iter().map(|p| p.market_value.abs()).sum();
        let total_unrealized_pnl: f64 = positions.iter().map(|p| p.unrealized_pnl).sum();

        if total_market_value <= 0.0 {
            return 0.0;
        }

        // Drawdown is the negative PnL as a fraction of portfolio value.
        // If PnL is positive, drawdown is 0.
        if total_unrealized_pnl < 0.0 {
            (-total_unrealized_pnl) / total_market_value
        } else {
            0.0
        }
    }

    /// Determine a fallback daily volatility estimate based on asset class heuristics.
    ///
    /// Uses symbol naming conventions to classify the dominant asset class:
    /// - Crypto symbols (BTC, ETH, etc.): 4% daily vol
    /// - Forex symbols (6-char pairs like EURUSD, or .FX suffix): 0.5% daily vol
    /// - Futures symbols (.FUT suffix, ES, NQ, ZN, etc.): 2% daily vol
    /// - Equities (default): 1.5% daily vol
    ///
    /// When the portfolio has mixed symbols, uses the highest vol estimate
    /// for a conservative fallback.
    fn asset_class_fallback_volatility(symbols: &[String]) -> f64 {
        if symbols.is_empty() {
            // Unknown portfolio: default to futures-like 2%
            return 0.02;
        }

        let mut max_vol: f64 = 0.0;
        for symbol in symbols {
            let upper = symbol.to_uppercase();
            let vol = if upper.contains("BTC")
                || upper.contains("ETH")
                || upper.contains("SOL")
                || upper.contains("DOGE")
                || upper.contains("XRP")
                || upper.ends_with("USDT")
                || upper.ends_with("USD") && upper.len() <= 7
                    && (upper.starts_with("BTC")
                        || upper.starts_with("ETH")
                        || upper.starts_with("SOL"))
            {
                0.04 // Crypto: 4% daily
            } else if upper.ends_with(".FX")
                || upper.contains("EUR")
                    && upper.contains("USD")
                    && upper.len() == 6
                || upper.starts_with("6E")
                || upper.starts_with("6J")
                || upper.starts_with("6B")
                || upper.starts_with("6A")
            {
                0.005 // Forex: 0.5% daily
            } else if upper.ends_with(".FUT")
                || upper.starts_with("ES")
                || upper.starts_with("NQ")
                || upper.starts_with("ZN")
                || upper.starts_with("ZB")
                || upper.starts_with("CL")
                || upper.starts_with("GC")
                || upper == "PORTFOLIO"
            {
                0.02 // Futures: 2% daily
            } else {
                0.015 // Equities: 1.5% daily
            };

            if vol > max_vol {
                max_vol = vol;
            }
        }

        max_vol
    }

    /// Compute annualized volatility from a return series.
    ///
    /// Uses the sample standard deviation of returns, annualized by sqrt(252).
    fn compute_volatility(returns: &[f64]) -> f64 {
        if returns.len() < 2 {
            return 0.0;
        }

        let n = returns.len() as f64;
        let mean = returns.iter().sum::<f64>() / n;
        let variance = returns.iter().map(|r| (r - mean).powi(2)).sum::<f64>() / (n - 1.0);
        let daily_vol = variance.sqrt();

        // Annualize: daily_vol * sqrt(trading_days)
        daily_vol * 252_f64.sqrt()
    }

    /// Compute annualized Sharpe ratio from a return series.
    ///
    /// Sharpe = (mean_return - risk_free_daily) / std_dev, annualized.
    /// Returns 0.0 if insufficient data or zero volatility.
    fn compute_sharpe_ratio(returns: &[f64]) -> f64 {
        if returns.len() < MIN_RETURN_OBSERVATIONS {
            return 0.0;
        }

        let n = returns.len() as f64;
        let mean_return = returns.iter().sum::<f64>() / n;
        let variance = returns.iter().map(|r| (r - mean_return).powi(2)).sum::<f64>() / (n - 1.0);
        let daily_std = variance.sqrt();

        if daily_std < 1e-12 {
            return 0.0;
        }

        let daily_rf = RISK_FREE_RATE_ANNUAL / 252.0;
        let excess_return = mean_return - daily_rf;

        // Annualized Sharpe
        (excess_return / daily_std) * 252_f64.sqrt()
    }

    /// Compute annualized Sortino ratio from a return series.
    ///
    /// Sortino = (mean_return - risk_free_daily) / downside_deviation, annualized.
    /// Downside deviation only considers returns below the risk-free rate.
    /// Returns 0.0 if insufficient data or zero downside deviation.
    fn compute_sortino_ratio(returns: &[f64]) -> f64 {
        if returns.len() < MIN_RETURN_OBSERVATIONS {
            return 0.0;
        }

        let n = returns.len() as f64;
        let mean_return = returns.iter().sum::<f64>() / n;
        let daily_rf = RISK_FREE_RATE_ANNUAL / 252.0;

        // Downside deviation: only returns below the risk-free rate
        let downside_sq_sum: f64 = returns
            .iter()
            .filter(|&&r| r < daily_rf)
            .map(|r| (r - daily_rf).powi(2))
            .sum();

        let downside_count = returns.iter().filter(|&&r| r < daily_rf).count();
        if downside_count < 2 {
            return 0.0;
        }

        let downside_dev = (downside_sq_sum / (downside_count as f64 - 1.0)).sqrt();
        if downside_dev < 1e-12 {
            return 0.0;
        }

        let excess_return = mean_return - daily_rf;

        // Annualized Sortino
        (excess_return / downside_dev) * 252_f64.sqrt()
    }

    /// Build per-position risk entries using position data and VaR from the risk engine.
    async fn build_position_risks(
        &self,
        positions: &[crate::repositories::TradingPosition],
    ) -> Vec<ProtoPositionRisk> {
        if positions.is_empty() {
            return Vec::new();
        }

        let total_market_value: f64 = positions.iter().map(|p| p.market_value.abs()).sum();
        let risk_engine = self.state.risk_engine.read().await;

        let mut result = Vec::with_capacity(positions.len());
        for pos in positions {
            // Calculate marginal VaR for this position
            let var_contribution = match risk_engine
                .calculate_marginal_var(
                    &pos.account_id,
                    &pos.symbol,
                    pos.quantity.abs(),
                    pos.average_price,
                )
                .await
            {
                Ok(var) => var,
                Err(e) => {
                    debug!(
                        "Marginal VaR for position {} failed: {}, using 0.0",
                        pos.symbol, e
                    );
                    0.0
                },
            };

            // Concentration risk: position market value as % of total portfolio
            let concentration_risk = if total_market_value > 0.0 {
                (pos.market_value.abs() / total_market_value) * 100.0
            } else {
                0.0
            };

            // Determine risk level based on concentration
            let risk_level = if concentration_risk > 50.0 {
                RiskLevel::Critical
            } else if concentration_risk > 30.0 {
                RiskLevel::High
            } else if concentration_risk > 15.0 {
                RiskLevel::Medium
            } else {
                RiskLevel::Low
            };

            result.push(ProtoPositionRisk {
                symbol: pos.symbol.clone(),
                position_size: pos.quantity,
                market_value: pos.market_value,
                var_contribution,
                concentration_risk,
                // Liquidity risk: not yet computed (requires order book depth data)
                liquidity_risk: 0.0,
                overall_score: Some(RiskScore {
                    overall_score: concentration_risk / 10.0, // scale 0-10
                    concentration_score: concentration_risk / 10.0,
                    liquidity_score: 0.0, // requires order book depth integration
                    volatility_score: 0.0, // requires per-symbol volatility
                    correlation_score: 0.0, // requires cross-asset correlation
                    risk_level: risk_level as i32,
                }),
                metrics: vec![],
            });
        }
        result
    }
}

#[tonic::async_trait]
impl RiskService for RiskServiceImpl {
    #[instrument(skip_all)]
    async fn get_va_r(
        &self,
        request: Request<GetVaRRequest>,
    ) -> Result<Response<GetVaRResponse>, Status> {
        let req = request.into_inner();

        let confidence_level = req.confidence_level;
        let lookback_days = req.lookback_days;
        let method = VaRMethod::try_from(req.method).unwrap_or(VaRMethod::VarMethodHistorical);

        // Fetch live positions FIRST so we can compute real portfolio notional for VaR.
        let positions = self.fetch_positions().await;
        let position_map: std::collections::HashMap<String, f64> = positions
            .iter()
            .map(|p| (p.symbol.clone(), p.quantity.abs()))
            .collect();

        // Portfolio notional = sum of |quantity * avg_price| across all open positions.
        // Falls back to 100_000.0 when no positions exist (conservative default for
        // empty-portfolio queries so VaR still returns a meaningful estimate).
        let portfolio_notional: f64 = positions
            .iter()
            .map(|p| (p.quantity * p.average_price).abs())
            .sum();
        let portfolio_notional = if portfolio_notional > 0.0 {
            portfolio_notional
        } else {
            100_000.0
        };

        // Delegate to the real RiskEngine for marginal VaR.
        // calculate_comprehensive_var requires full historical price data that is not available
        // at the gRPC boundary, so we use calculate_marginal_var as the portfolio-level estimate
        // with the real portfolio notional derived from open positions above.
        let risk_engine = self.state.risk_engine.read().await;
        let portfolio_var = match risk_engine
            .calculate_marginal_var(
                "portfolio",
                "PORTFOLIO",
                portfolio_notional,
                1.0,
            )
            .await
        {
            Ok(var) => {
                info!("VaR calculated via RiskEngine: notional={:.2}, var={:.4}", portfolio_notional, var);
                var
            },
            Err(e) => {
                error!(
                    "RiskEngine VaR calculation failed for get_var (error: {}), using asset-class-specific volatility fallback",
                    e
                );
                // Asset-class-specific volatility fallback based on typical daily vol:
                // equities 1.5%, futures 2%, crypto 4%, forex 0.5%
                // Use futures default (2%) since portfolio is mixed and this is a conservative middle ground.
                // Per-symbol VaR below uses individual symbol classification.
                let fallback_vol = Self::asset_class_fallback_volatility(&req.symbols);
                portfolio_notional * fallback_vol
            },
        };

        // Build per-symbol marginal VaRs using the same engine.
        // Each symbol's contribution is calculated individually; if a symbol fails we skip it.
        let num_symbols = req.symbols.len();
        let equal_contribution_pct = if num_symbols > 0 {
            100.0 / num_symbols as f64
        } else {
            0.0
        };

        let mut symbol_vars = Vec::with_capacity(num_symbols);
        for symbol in req.symbols {
            // Use the real position size from the trading repository.
            // If no open position exists for this symbol, default to 0.0 (no exposure).
            let pos_size = position_map.get(&symbol).copied().unwrap_or(0.0);

            let symbol_var = match risk_engine
                .calculate_marginal_var("portfolio", &symbol, pos_size, 1.0)
                .await
            {
                Ok(var) => var,
                Err(e) => {
                    warn!("Marginal VaR for symbol {} failed: {}", symbol, e);
                    // Fall back to equal-weight share of portfolio VaR
                    portfolio_var * (equal_contribution_pct / 100.0)
                },
            };

            // Marginal contribution: symbol's VaR as a percentage of the total portfolio VaR.
            // Falls back to equal contribution if portfolio VaR is zero (avoids division by zero).
            let contribution_pct = if portfolio_var.abs() > 1e-12 {
                (symbol_var / portfolio_var) * 100.0
            } else {
                equal_contribution_pct
            };

            symbol_vars.push(SymbolVaR {
                symbol,
                var_value: symbol_var,
                position_size: pos_size,
                contribution_pct,
            });
        }

        Ok(Response::new(GetVaRResponse {
            portfolio_var,
            symbol_vars,
            confidence_level,
            lookback_days,
            method: method as i32,
            calculated_at: chrono::Utc::now().timestamp(),
        }))
    }

    #[instrument(skip_all)]
    async fn get_risk_metrics(
        &self,
        _request: Request<GetRiskMetricsRequest>,
    ) -> Result<Response<GetRiskMetricsResponse>, Status> {
        // Fetch live position data FIRST so we can compute real portfolio notional for VaR.
        let positions = self.fetch_positions().await;

        // Portfolio notional = sum of |quantity * avg_price| across all open positions.
        // Falls back to 100_000.0 when no positions exist (conservative default so VaR
        // still returns a meaningful estimate for empty portfolios).
        let portfolio_notional: f64 = positions
            .iter()
            .map(|p| (p.quantity * p.average_price).abs())
            .sum();
        let portfolio_notional = if portfolio_notional > 0.0 {
            portfolio_notional
        } else {
            100_000.0
        };

        // Use the real RiskEngine's configured confidence level and max VaR limit
        // to produce the 1d VaR estimate. Longer horizons scale by sqrt(T).
        let risk_engine = self.state.risk_engine.read().await;

        // Compute a representative 1-day portfolio VaR via marginal VaR using the
        // real portfolio notional derived from open positions above.
        // TODO: Replace with calculate_comprehensive_var once historical price data
        //       is available at the gRPC boundary for full parametric/Monte Carlo VaR.
        let portfolio_var_1d = match risk_engine
            .calculate_marginal_var("portfolio", "PORTFOLIO", portfolio_notional, 1.0)
            .await
        {
            Ok(var) => var,
            Err(e) => {
                error!(
                    "RiskEngine marginal VaR failed for get_risk_metrics (error: {}), using asset-class-specific volatility fallback",
                    e
                );
                // Asset-class-specific volatility fallback. Use the position symbols to
                // determine the dominant asset class and pick the appropriate daily vol.
                let symbols: Vec<String> = positions.iter().map(|p| p.symbol.clone()).collect();
                let fallback_vol = Self::asset_class_fallback_volatility(&symbols);
                portfolio_notional * fallback_vol
            },
        };
        // Drop the read lock before fetching from repositories
        drop(risk_engine);

        // Scale 1d VaR to 5d and 30d using square-root-of-time rule
        let portfolio_var_5d = portfolio_var_1d * 5_f64.sqrt();
        let portfolio_var_30d = portfolio_var_1d * 30_f64.sqrt();

        // max_drawdown_limit comes from config
        let max_drawdown = self
            .state
            .config_repository
            .get_config_f64("Risk", "max_drawdown")
            .await
            .map_err(|e| Status::internal(format!("Failed to get max drawdown config: {}", e)))?
            .unwrap_or(0.10);
        let executions = self.fetch_executions().await;

        // Current drawdown from open positions' unrealized PnL
        let current_drawdown = Self::compute_current_drawdown(&positions);

        // Derive return series from execution history for ratio calculations
        let returns = Self::compute_returns_from_executions(&executions);
        let num_returns = returns.len();

        let volatility = Self::compute_volatility(&returns);
        let sharpe_ratio = Self::compute_sharpe_ratio(&returns);
        let sortino_ratio = Self::compute_sortino_ratio(&returns);

        debug!(
            "Risk metrics computed: drawdown={:.4}, vol={:.4}, sharpe={:.4}, sortino={:.4}, returns_n={}",
            current_drawdown, volatility, sharpe_ratio, sortino_ratio, num_returns
        );

        // Build per-position risk entries with VaR contributions
        let position_risks = self.build_position_risks(&positions).await;

        let metrics = RiskMetrics {
            portfolio_var_1d,
            portfolio_var_5d,
            portfolio_var_30d,
            max_drawdown,
            current_drawdown,
            sharpe_ratio,
            sortino_ratio,
            // Not yet computed: requires benchmark correlation from market_data_repository
            beta: 0.0,
            // Not yet computed: requires benchmark-relative returns from market_data_repository
            alpha: 0.0,
            volatility,
            position_risks,
        };

        Ok(Response::new(GetRiskMetricsResponse {
            metrics: Some(metrics),
            calculated_at: chrono::Utc::now().timestamp(),
        }))
    }

    #[instrument(skip_all)]
    async fn get_position_risk(
        &self,
        request: Request<GetPositionRiskRequest>,
    ) -> Result<Response<GetPositionRiskResponse>, Status> {
        let req = request.into_inner();

        // Fetch positions, optionally filtered by symbol/account
        let all_positions = self.fetch_positions().await;

        // Apply filters from request
        let filtered: Vec<_> = all_positions
            .into_iter()
            .filter(|p| {
                req.symbol
                    .as_ref()
                    .is_none_or(|s| s.is_empty() || p.symbol == *s)
            })
            .filter(|p| {
                req.account_id
                    .as_ref()
                    .is_none_or(|a| a.is_empty() || p.account_id == *a)
            })
            .collect();

        let position_risks = self.build_position_risks(&filtered).await;

        // Portfolio risk score: weighted average of concentration risks, capped at 100
        let portfolio_risk_score = if position_risks.is_empty() {
            0.0
        } else {
            let total_concentration: f64 =
                position_risks.iter().map(|pr| pr.concentration_risk).sum();
            // Use max concentration as the portfolio score (highest single-name risk)
            let max_concentration = position_risks
                .iter()
                .map(|pr| pr.concentration_risk)
                .fold(0.0_f64, f64::max);
            // Blend: 60% max single-name concentration + 40% HHI-like score
            let hhi_like = total_concentration / position_risks.len() as f64;
            let score = 0.6 * max_concentration + 0.4 * hhi_like;
            score.min(100.0)
        };

        debug!(
            "Position risk: {} positions, portfolio_risk_score={:.2}",
            position_risks.len(),
            portfolio_risk_score
        );

        Ok(Response::new(GetPositionRiskResponse {
            position_risks,
            portfolio_risk_score,
        }))
    }

    #[instrument(skip_all)]
    async fn validate_order(
        &self,
        request: Request<ValidateOrderRequest>,
    ) -> Result<Response<ValidateOrderResponse>, Status> {
        let req = request.into_inner();

        info!(
            "validate_order: symbol={}, qty={:.2}, price={:.2}, side={}, account={}",
            req.symbol, req.quantity, req.price, req.side, req.account_id
        );

        let mut violations = vec![];
        let mut is_valid = true;

        // 1. Kill switch / circuit breaker check — CRITICAL SAFETY
        //    Reject ALL orders when kill switch is engaged.
        if let Some(kill_switch) = self.state.kill_switch_system.as_ref() {
            if let Err(e) = kill_switch
                .validate_order_with_kill_switch(&req.symbol, &req.account_id, None)
                .await
            {
                warn!("validate_order: kill switch rejected order: {}", e);
                violations.push(RiskViolation {
                    violation_type: RiskViolationType::VarLimit as i32,
                    description: format!("Kill switch / circuit breaker active: {}", e),
                    current_value: 0.0,
                    limit_value: 0.0,
                    severity: RiskAlertSeverity::Emergency as i32,
                });
                is_valid = false;
            }
        }

        // 2. Load risk limits for this account (max order size, position limit, daily loss, etc.)
        let risk_limits = self
            .state
            .risk_repository
            .get_risk_limits(&req.account_id)
            .await
            .ok();

        // 2a. Maximum order size check
        let max_order_quantity = if let Some(ref limits) = risk_limits {
            limits.max_order_size
        } else {
            // Fallback to config repository, then to hard default
            self.state
                .config_repository
                .get_config_f64("Risk", "max_order_quantity")
                .await
                .map_err(|e| {
                    warn!("Failed to load max_order_quantity from config: {}", e);
                    Status::internal(format!("Failed to load risk config: {}", e))
                })?
                .unwrap_or(1_000_000.0)
        };

        if req.quantity > max_order_quantity {
            violations.push(RiskViolation {
                violation_type: RiskViolationType::PositionLimit as i32,
                description: format!(
                    "Order size {:.2} exceeds maximum limit {:.2}",
                    req.quantity, max_order_quantity
                ),
                current_value: req.quantity,
                limit_value: max_order_quantity,
                severity: RiskAlertSeverity::Critical as i32,
            });
            is_valid = false;
        }

        // 2b. Position limit check — prevent over-concentration in a single symbol
        if let Some(ref limits) = risk_limits {
            if limits.max_position_limit > 0.0 && req.quantity > limits.max_position_limit {
                violations.push(RiskViolation {
                    violation_type: RiskViolationType::PositionLimit as i32,
                    description: format!(
                        "Order quantity {:.2} exceeds position limit {:.2} for account {}",
                        req.quantity, limits.max_position_limit, req.account_id
                    ),
                    current_value: req.quantity,
                    limit_value: limits.max_position_limit,
                    severity: RiskAlertSeverity::Critical as i32,
                });
                is_valid = false;
            }
        }

        // 3. Daily loss limit check — fetch current risk metrics for drawdown vs daily loss
        if let Some(ref limits) = risk_limits {
            if let Some(daily_loss_limit) = limits.daily_loss_limit {
                if daily_loss_limit > 0.0 {
                    if let Ok(metrics) = self
                        .state
                        .risk_repository
                        .get_risk_metrics(&req.account_id)
                        .await
                    {
                        if metrics.current_drawdown >= daily_loss_limit {
                            violations.push(RiskViolation {
                                violation_type: RiskViolationType::Drawdown as i32,
                                description: format!(
                                    "Daily loss limit reached: current drawdown {:.4} >= limit {:.4}",
                                    metrics.current_drawdown, daily_loss_limit
                                ),
                                current_value: metrics.current_drawdown,
                                limit_value: daily_loss_limit,
                                severity: RiskAlertSeverity::Critical as i32,
                            });
                            is_valid = false;
                        }
                    }
                }
            }
        }

        // 4. Leverage limit check — reject if account leverage already at maximum
        {
            let max_leverage = self
                .state
                .config_repository
                .get_config_f64("Risk", "max_leverage")
                .await
                .ok()
                .flatten()
                .unwrap_or(10.0);

            if max_leverage > 0.0 {
                if let Ok(metrics) = self
                    .state
                    .risk_repository
                    .get_risk_metrics(&req.account_id)
                    .await
                {
                    if metrics.leverage_ratio >= max_leverage {
                        violations.push(RiskViolation {
                            violation_type: RiskViolationType::Concentration as i32,
                            description: format!(
                                "Leverage limit exceeded: current {:.2}x >= max {:.2}x",
                                metrics.leverage_ratio, max_leverage
                            ),
                            current_value: metrics.leverage_ratio,
                            limit_value: max_leverage,
                            severity: RiskAlertSeverity::Critical as i32,
                        });
                        is_valid = false;
                    }
                }
            }
        }

        // 5. VaR limit check via the real risk engine (existing check)
        if req.price > 0.0 && !req.symbol.is_empty() {
            let risk_engine = self.state.risk_engine.read().await;
            if let Err(var_err) = risk_engine
                .check_var_limit(
                    &req.account_id,
                    &req.symbol,
                    req.quantity,
                    req.price,
                )
                .await
            {
                violations.push(RiskViolation {
                    violation_type: RiskViolationType::VarLimit as i32,
                    description: var_err.clone(),
                    current_value: req.quantity * req.price,
                    limit_value: risk_engine.max_var_limit(),
                    severity: RiskAlertSeverity::Critical as i32,
                });
                is_valid = false;
            }
        }

        if !is_valid {
            warn!(
                "validate_order REJECTED: {} violation(s) for symbol={} account={}",
                violations.len(),
                req.symbol,
                req.account_id
            );
        }

        let risk_score = RiskScore {
            overall_score: if is_valid { 3.0 } else { 8.0 },
            concentration_score: 2.0,
            liquidity_score: 3.0,
            volatility_score: 4.0,
            correlation_score: 2.0,
            risk_level: if is_valid {
                RiskLevel::Medium
            } else {
                RiskLevel::High
            } as i32,
        };

        Ok(Response::new(ValidateOrderResponse {
            is_valid,
            violations,
            risk_score: Some(risk_score),
            message: if is_valid {
                "Order validation passed all 5 risk checks".to_string()
            } else {
                "Order validation failed".to_string()
            },
        }))
    }

    #[instrument(skip_all)]
    async fn emergency_stop(
        &self,
        request: Request<EmergencyStopRequest>,
    ) -> Result<Response<EmergencyStopResponse>, Status> {
        let req = request.into_inner();

        // Delegate to the real TradingServiceKillSwitch when configured.
        // If the kill switch system is not initialized, reject the call so callers
        // know that the emergency stop is not operational rather than silently
        // pretending success.
        let kill_switch = self
            .state
            .kill_switch_system
            .as_ref()
            .ok_or_else(|| {
                error!("Emergency stop called but kill_switch_system is not configured");
                Status::unavailable(
                    "Kill switch system is not configured; emergency stop is unavailable",
                )
            })?;

        error!("gRPC emergency_stop triggered: {}", req.reason);

        kill_switch
            .emergency_shutdown(req.reason.clone())
            .await
            .map_err(|e| {
                error!("emergency_shutdown failed: {}", e);
                Status::internal(format!("Emergency shutdown failed: {}", e))
            })?;

        info!("Emergency stop complete, global kill switch engaged");

        // Query open orders that were active at the time of emergency shutdown
        let affected_orders = match self.state.order_manager.read().await.get_open_orders().await {
            orders if orders.is_empty() => {
                info!("No open orders at time of emergency stop");
                vec![]
            },
            orders => {
                let order_ids: Vec<String> = orders
                    .iter()
                    .map(|o| o.id.to_string())
                    .collect();
                warn!(
                    "Emergency stop affecting {} open orders: {:?}",
                    order_ids.len(),
                    order_ids
                );
                order_ids
            },
        };

        Ok(Response::new(EmergencyStopResponse {
            success: true,
            message: format!("Emergency stop activated: {}", req.reason),
            timestamp: chrono::Utc::now().timestamp(),
            affected_orders,
        }))
    }

    #[instrument(skip_all)]
    async fn get_circuit_breaker_status(
        &self,
        _request: Request<GetCircuitBreakerStatusRequest>,
    ) -> Result<Response<GetCircuitBreakerStatusResponse>, Status> {
        let mut circuit_breakers = Vec::new();

        // Report kill switch status as the global emergency circuit breaker
        if let Some(kill_switch) = self.state.kill_switch_system.as_ref() {
            match kill_switch.get_status().await {
                Ok(status) => {
                    circuit_breakers.push(CircuitBreakerStatus {
                        name: "global_kill_switch".to_string(),
                        is_triggered: status.is_active || status.is_emergency_active,
                        trigger_reason: if status.is_emergency_active {
                            Some("Emergency shutdown activated".to_string())
                        } else if status.is_active {
                            Some("Kill switch active".to_string())
                        } else {
                            None
                        },
                        triggered_at: None, // Kill switch does not expose activation timestamp
                        reset_at: None,
                        breaker_type: CircuitBreakerType::PortfolioLoss as i32,
                    });
                },
                Err(e) => {
                    warn!("Failed to query kill switch status: {}", e);
                    // Report the breaker as unknown/untriggered rather than hiding it
                    circuit_breakers.push(CircuitBreakerStatus {
                        name: "global_kill_switch".to_string(),
                        is_triggered: false,
                        trigger_reason: Some(format!("Status unavailable: {}", e)),
                        triggered_at: None,
                        reset_at: None,
                        breaker_type: CircuitBreakerType::PortfolioLoss as i32,
                    });
                },
            }
        }
        // If kill_switch_system is None, circuit_breakers remains empty —
        // the kill switch is not configured, so there is nothing to report.

        Ok(Response::new(GetCircuitBreakerStatusResponse {
            circuit_breakers,
        }))
    }

    type StreamVaRUpdatesStream = std::pin::Pin<
        Box<dyn tokio_stream::Stream<Item = Result<crate::proto::risk::VaREvent, Status>> + Send>,
    >;

    #[instrument(skip_all)]
    async fn stream_va_r_updates(
        &self,
        request: Request<StreamVaRRequest>,
    ) -> Result<Response<Self::StreamVaRUpdatesStream>, Status> {
        let req = request.into_inner();
        let confidence_level = if req.confidence_level > 0.0 {
            req.confidence_level
        } else {
            0.95
        };
        let update_freq = if req.update_frequency_seconds > 0 {
            req.update_frequency_seconds as u64
        } else {
            10
        };

        info!(
            "StreamVaRUpdates started: confidence={}, freq={}s",
            confidence_level, update_freq
        );

        let (tx, rx) = tokio::sync::mpsc::channel(128);
        let state = self.state.clone();
        let mut previous_var: Option<f64> = None;

        tokio::spawn(async move {
            let mut interval =
                tokio::time::interval(std::time::Duration::from_secs(update_freq));

            loop {
                interval.tick().await;

                // Calculate current portfolio VaR via marginal VaR estimate
                let risk_engine = state.risk_engine.read().await;
                let portfolio_var = match risk_engine
                    .calculate_marginal_var(
                        "portfolio",
                        "PORTFOLIO",
                        confidence_level * 1_000_000.0,
                        1.0,
                    )
                    .await
                {
                    Ok(var) => var,
                    Err(e) => {
                        warn!("StreamVaRUpdates: VaR calculation failed: {}", e);
                        // Parametric fallback
                        confidence_level * 0.02
                    },
                };
                drop(risk_engine);

                // Determine change type relative to previous value
                let change_type = match previous_var {
                    Some(prev) if portfolio_var > prev * 1.01 => {
                        VaRChangeType::VarChangeTypeIncreased
                    },
                    Some(prev) if portfolio_var < prev * 0.99 => {
                        VaRChangeType::VarChangeTypeDecreased
                    },
                    _ => VaRChangeType::VarChangeTypeUnspecified,
                };
                previous_var = Some(portfolio_var);

                let event = VaREvent {
                    portfolio_var,
                    symbol_vars: vec![],
                    change_type: change_type as i32,
                    timestamp: chrono::Utc::now().timestamp(),
                };

                if tx.send(Ok(event)).await.is_err() {
                    info!("StreamVaRUpdates: client disconnected");
                    break;
                }
            }
        });

        Ok(Response::new(Box::pin(
            tokio_stream::wrappers::ReceiverStream::new(rx),
        )))
    }

    type StreamRiskAlertsStream = std::pin::Pin<
        Box<
            dyn tokio_stream::Stream<Item = Result<crate::proto::risk::RiskAlertEvent, Status>>
                + Send,
        >,
    >;

    #[instrument(skip_all)]
    async fn stream_risk_alerts(
        &self,
        request: Request<StreamRiskAlertsRequest>,
    ) -> Result<Response<Self::StreamRiskAlertsStream>, Status> {
        let req = request.into_inner();
        let min_severity = req.min_severity;

        info!(
            "StreamRiskAlerts started: min_severity={}",
            min_severity
        );

        let (tx, rx) = tokio::sync::mpsc::channel(128);
        let state = self.state.clone();
        let mut alert_counter: u64 = 0;

        tokio::spawn(async move {
            let mut interval =
                tokio::time::interval(std::time::Duration::from_secs(10));

            loop {
                interval.tick().await;

                let mut alerts: Vec<RiskAlertEvent> = Vec::new();

                // Check kill switch status for emergency alerts
                if let Some(kill_switch) = state.kill_switch_system.as_ref() {
                    match kill_switch.get_status().await {
                        Ok(ks_status) => {
                            if ks_status.is_emergency_active {
                                alert_counter += 1;
                                alerts.push(RiskAlertEvent {
                                    alert_id: format!("risk-alert-{}", alert_counter),
                                    alert_type: RiskAlertType::VarBreach as i32,
                                    severity: RiskAlertSeverity::Emergency as i32,
                                    message: "Emergency shutdown is ACTIVE — all trading halted"
                                        .to_string(),
                                    symbol: None,
                                    account_id: None,
                                    metadata: std::collections::HashMap::new(),
                                    timestamp: chrono::Utc::now().timestamp(),
                                });
                            } else if ks_status.is_active {
                                alert_counter += 1;
                                alerts.push(RiskAlertEvent {
                                    alert_id: format!("risk-alert-{}", alert_counter),
                                    alert_type: RiskAlertType::PositionLimit as i32,
                                    severity: RiskAlertSeverity::Critical as i32,
                                    message: "Kill switch is ACTIVE — trading halted".to_string(),
                                    symbol: None,
                                    account_id: None,
                                    metadata: std::collections::HashMap::new(),
                                    timestamp: chrono::Utc::now().timestamp(),
                                });
                            }
                            if !ks_status.is_healthy {
                                alert_counter += 1;
                                alerts.push(RiskAlertEvent {
                                    alert_id: format!("risk-alert-{}", alert_counter),
                                    alert_type: RiskAlertType::Unspecified as i32,
                                    severity: RiskAlertSeverity::Warning as i32,
                                    message: "Kill switch system is UNHEALTHY".to_string(),
                                    symbol: None,
                                    account_id: None,
                                    metadata: std::collections::HashMap::new(),
                                    timestamp: chrono::Utc::now().timestamp(),
                                });
                            }
                        },
                        Err(e) => {
                            alert_counter += 1;
                            alerts.push(RiskAlertEvent {
                                alert_id: format!("risk-alert-{}", alert_counter),
                                alert_type: RiskAlertType::Unspecified as i32,
                                severity: RiskAlertSeverity::Warning as i32,
                                message: format!("Kill switch status unavailable: {}", e),
                                symbol: None,
                                account_id: None,
                                metadata: std::collections::HashMap::new(),
                                timestamp: chrono::Utc::now().timestamp(),
                            });
                        },
                    }
                }

                // Check VaR breach by comparing current VaR to the configured limit
                let risk_engine = state.risk_engine.read().await;
                let max_var_limit = risk_engine.max_var_limit();
                let confidence = risk_engine.var_confidence();
                let current_var = (risk_engine
                    .calculate_marginal_var(
                        "portfolio",
                        "PORTFOLIO",
                        confidence * 1_000_000.0,
                        1.0,
                    )
                    .await).ok();
                drop(risk_engine);

                if let Some(var_value) = current_var {
                    if var_value > max_var_limit {
                        alert_counter += 1;
                        let mut metadata = std::collections::HashMap::new();
                        metadata.insert("var_value".to_string(), format!("{:.4}", var_value));
                        metadata.insert("var_limit".to_string(), format!("{:.4}", max_var_limit));
                        alerts.push(RiskAlertEvent {
                            alert_id: format!("risk-alert-{}", alert_counter),
                            alert_type: RiskAlertType::VarBreach as i32,
                            severity: RiskAlertSeverity::Critical as i32,
                            message: format!(
                                "VaR breach: current VaR {:.2} exceeds limit {:.2}",
                                var_value, max_var_limit
                            ),
                            symbol: None,
                            account_id: None,
                            metadata,
                            timestamp: chrono::Utc::now().timestamp(),
                        });
                    }
                }

                // Filter alerts by minimum severity and send each one
                for alert in alerts {
                    if alert.severity >= min_severity
                        && tx.send(Ok(alert)).await.is_err() {
                            info!("StreamRiskAlerts: client disconnected");
                            return;
                        }
                }
            }
        });

        Ok(Response::new(Box::pin(
            tokio_stream::wrappers::ReceiverStream::new(rx),
        )))
    }

    // Server-streaming: polls GetCircuitBreakerStatus at configurable interval
    type StreamCircuitBreakerStatusStream = std::pin::Pin<
        Box<
            dyn tokio_stream::Stream<Item = Result<GetCircuitBreakerStatusResponse, Status>>
                + Send,
        >,
    >;

    #[instrument(skip_all)]
    async fn stream_circuit_breaker_status(
        &self,
        request: Request<StreamCircuitBreakerStatusRequest>,
    ) -> Result<Response<Self::StreamCircuitBreakerStatusStream>, Status> {
        let req = request.into_inner();
        let interval_secs = if req.interval_seconds > 0 {
            u64::from(req.interval_seconds.clamp(1, 60))
        } else {
            2
        };

        info!(
            interval_secs = interval_secs,
            symbol = ?req.symbol,
            "StreamCircuitBreakerStatus started"
        );

        let (tx, rx) = tokio::sync::mpsc::channel(16);
        let svc = self.clone();
        let symbol = req.symbol;

        tokio::spawn(async move {
            let mut interval =
                tokio::time::interval(std::time::Duration::from_secs(interval_secs));

            loop {
                interval.tick().await;

                let cb_req = GetCircuitBreakerStatusRequest {
                    symbol: symbol.clone(),
                };

                match svc
                    .get_circuit_breaker_status(tonic::Request::new(cb_req))
                    .await
                {
                    Ok(resp) => {
                        if tx.send(Ok(resp.into_inner())).await.is_err() {
                            info!("StreamCircuitBreakerStatus: client disconnected");
                            break;
                        }
                    },
                    Err(e) => {
                        warn!("StreamCircuitBreakerStatus: poll failed: {}", e);
                        if tx.send(Err(e)).await.is_err() {
                            break;
                        }
                        break;
                    },
                }
            }
        });

        Ok(Response::new(Box::pin(
            tokio_stream::wrappers::ReceiverStream::new(rx),
        )))
    }

    // Server-streaming: polls GetRiskMetrics at configurable interval
    type StreamRiskMetricsStream = std::pin::Pin<
        Box<
            dyn tokio_stream::Stream<Item = Result<GetRiskMetricsResponse, Status>>
                + Send,
        >,
    >;

    #[instrument(skip_all)]
    async fn stream_risk_metrics(
        &self,
        request: Request<StreamRiskMetricsRequest>,
    ) -> Result<Response<Self::StreamRiskMetricsStream>, Status> {
        let req = request.into_inner();
        let interval_secs = if req.interval_seconds > 0 {
            u64::from(req.interval_seconds.clamp(1, 60))
        } else {
            3
        };

        info!(
            interval_secs = interval_secs,
            portfolio_id = ?req.portfolio_id,
            "StreamRiskMetrics started"
        );

        let (tx, rx) = tokio::sync::mpsc::channel(16);
        let svc = self.clone();
        let portfolio_id = req.portfolio_id;

        tokio::spawn(async move {
            let mut interval =
                tokio::time::interval(std::time::Duration::from_secs(interval_secs));

            loop {
                interval.tick().await;

                let metrics_req = GetRiskMetricsRequest {
                    portfolio_id: portfolio_id.clone(),
                };

                match svc
                    .get_risk_metrics(tonic::Request::new(metrics_req))
                    .await
                {
                    Ok(resp) => {
                        if tx.send(Ok(resp.into_inner())).await.is_err() {
                            info!("StreamRiskMetrics: client disconnected");
                            break;
                        }
                    },
                    Err(e) => {
                        warn!("StreamRiskMetrics: poll failed: {}", e);
                        if tx.send(Err(e)).await.is_err() {
                            break;
                        }
                        break;
                    },
                }
            }
        });

        Ok(Response::new(Box::pin(
            tokio_stream::wrappers::ReceiverStream::new(rx),
        )))
    }
}

#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::indexing_slicing)]
mod tests {
    use super::*;
    use crate::repositories::{ExecutionEvent, TradingPosition};
    use common::OrderSide;

    // -----------------------------------------------------------------------
    // Helper: create a TradingPosition
    // -----------------------------------------------------------------------
    fn position(symbol: &str, qty: f64, avg_price: f64, market_value: f64, pnl: f64) -> TradingPosition {
        TradingPosition {
            account_id: "test-account".to_string(),
            symbol: symbol.to_string(),
            quantity: qty,
            average_price: avg_price,
            market_value,
            unrealized_pnl: pnl,
            timestamp: 1_700_000_000,
        }
    }

    fn execution(ts: i64, price: f64) -> ExecutionEvent {
        ExecutionEvent {
            id: format!("exec-{}", ts),
            order_id: "order-1".to_string(),
            account_id: "test-account".to_string(),
            symbol: "EURUSD".to_string(),
            side: OrderSide::Buy,
            quantity: 100.0,
            price,
            timestamp: ts,
        }
    }

    // -----------------------------------------------------------------------
    // 1. Parametric VaR fallback formula
    // -----------------------------------------------------------------------
    #[test]
    fn test_parametric_var_fallback_formula() {
        // When RiskEngine fails, VaR = portfolio_notional * 0.02
        let portfolio_notional = 500_000.0_f64;
        let fallback_var = portfolio_notional * 0.02;
        assert!((fallback_var - 10_000.0).abs() < 1e-6,
            "VaR fallback for 500k notional should be 10,000; got {}", fallback_var);

        // Zero notional
        let zero_var = 0.0_f64 * 0.02;
        assert!((zero_var - 0.0).abs() < 1e-10);

        // Very large notional
        let large = 1_000_000_000.0_f64 * 0.02;
        assert!((large - 20_000_000.0).abs() < 1e-6);
    }

    // -----------------------------------------------------------------------
    // 2. equal_contribution_pct calculation
    // -----------------------------------------------------------------------
    #[test]
    fn test_equal_contribution_pct_for_n_symbols() {
        // 4 symbols: each contributes 25%
        let num_symbols = 4_usize;
        let pct = if num_symbols > 0 { 100.0 / num_symbols as f64 } else { 0.0 };
        assert!((pct - 25.0).abs() < 1e-10);

        // 1 symbol: 100%
        let pct1 = 100.0 / 1.0_f64;
        assert!((pct1 - 100.0).abs() < 1e-10);

        // 10 symbols: 10%
        let pct10 = 100.0 / 10.0_f64;
        assert!((pct10 - 10.0).abs() < 1e-10);
    }

    #[test]
    fn test_equal_contribution_pct_empty_symbols() {
        let num_symbols = 0_usize;
        let pct = if num_symbols > 0 { 100.0 / num_symbols as f64 } else { 0.0 };
        assert!((pct - 0.0).abs() < 1e-10);
    }

    // -----------------------------------------------------------------------
    // 3. compute_current_drawdown
    // -----------------------------------------------------------------------
    #[test]
    fn test_drawdown_empty_positions() {
        let dd = RiskServiceImpl::compute_current_drawdown(&[]);
        assert!((dd - 0.0).abs() < 1e-10);
    }

    #[test]
    fn test_drawdown_positive_pnl_is_zero() {
        let positions = vec![
            position("EURUSD", 1000.0, 1.10, 11000.0, 500.0),
        ];
        let dd = RiskServiceImpl::compute_current_drawdown(&positions);
        assert!((dd - 0.0).abs() < 1e-10, "Positive PnL should yield zero drawdown");
    }

    #[test]
    fn test_drawdown_negative_pnl() {
        // market_value = 10000, unrealized_pnl = -2000
        // drawdown = 2000 / 10000 = 0.20
        let positions = vec![
            position("EURUSD", 1000.0, 1.10, 10000.0, -2000.0),
        ];
        let dd = RiskServiceImpl::compute_current_drawdown(&positions);
        assert!((dd - 0.20).abs() < 1e-10, "Drawdown should be 0.20; got {}", dd);
    }

    #[test]
    fn test_drawdown_multiple_positions_mixed_pnl() {
        // pos1: market_value=10000, pnl=-3000
        // pos2: market_value=5000,  pnl=+1000
        // total_market_value = 15000, total_pnl = -2000
        // drawdown = 2000 / 15000 = 0.1333...
        let positions = vec![
            position("EURUSD", 1000.0, 10.0, 10000.0, -3000.0),
            position("GBPUSD", 500.0, 10.0, 5000.0, 1000.0),
        ];
        let dd = RiskServiceImpl::compute_current_drawdown(&positions);
        let expected = 2000.0 / 15000.0;
        assert!((dd - expected).abs() < 1e-10, "Expected drawdown {:.6}; got {:.6}", expected, dd);
    }

    // -----------------------------------------------------------------------
    // 4. compute_returns_from_executions
    // -----------------------------------------------------------------------
    #[test]
    fn test_returns_empty_executions() {
        let returns = RiskServiceImpl::compute_returns_from_executions(&[]);
        assert!(returns.is_empty());
    }

    #[test]
    fn test_returns_single_execution() {
        let execs = vec![execution(1, 100.0)];
        let returns = RiskServiceImpl::compute_returns_from_executions(&execs);
        assert!(returns.is_empty(), "Single execution should yield no returns");
    }

    #[test]
    fn test_returns_two_executions() {
        // price goes from 100 to 110 => return = 110/100 - 1 = 0.10
        let execs = vec![execution(1, 100.0), execution(2, 110.0)];
        let returns = RiskServiceImpl::compute_returns_from_executions(&execs);
        assert_eq!(returns.len(), 1);
        assert!((returns[0] - 0.10).abs() < 1e-10);
    }

    #[test]
    fn test_returns_negative_price_filtered() {
        // Negative and zero prices should be filtered out
        let execs = vec![
            execution(1, 100.0),
            execution(2, 0.0),   // zero price filtered
            execution(3, 120.0),
        ];
        let returns = RiskServiceImpl::compute_returns_from_executions(&execs);
        // After filtering zero price: prices = [(1, 100), (3, 120)]
        // return = 120/100 - 1 = 0.20
        assert_eq!(returns.len(), 1);
        assert!((returns[0] - 0.20).abs() < 1e-10);
    }

    #[test]
    fn test_returns_sorted_by_timestamp() {
        // Executions provided out of order should be sorted
        let execs = vec![
            execution(3, 120.0),
            execution(1, 100.0),
            execution(2, 110.0),
        ];
        let returns = RiskServiceImpl::compute_returns_from_executions(&execs);
        assert_eq!(returns.len(), 2);
        // 100 -> 110: +10%
        assert!((returns[0] - 0.10).abs() < 1e-10);
        // 110 -> 120: +9.09%
        assert!((returns[1] - (120.0 / 110.0 - 1.0)).abs() < 1e-10);
    }

    // -----------------------------------------------------------------------
    // 5. compute_volatility
    // -----------------------------------------------------------------------
    #[test]
    fn test_volatility_empty() {
        assert!((RiskServiceImpl::compute_volatility(&[]) - 0.0).abs() < 1e-10);
    }

    #[test]
    fn test_volatility_single_return() {
        assert!((RiskServiceImpl::compute_volatility(&[0.01]) - 0.0).abs() < 1e-10);
    }

    #[test]
    fn test_volatility_constant_returns_is_zero() {
        // All returns the same => std dev = 0 => vol = 0
        let returns = vec![0.01, 0.01, 0.01, 0.01, 0.01];
        let vol = RiskServiceImpl::compute_volatility(&returns);
        assert!((vol - 0.0).abs() < 1e-10, "Constant returns should have zero vol");
    }

    #[test]
    fn test_volatility_known_series() {
        // Returns: [0.01, -0.01, 0.02, -0.02]
        // mean = 0.0
        // variance = (0.0001 + 0.0001 + 0.0004 + 0.0004) / 3 = 0.001 / 3
        // daily_vol = sqrt(0.001/3)
        // annualized = daily_vol * sqrt(252)
        let returns = vec![0.01, -0.01, 0.02, -0.02];
        let vol = RiskServiceImpl::compute_volatility(&returns);
        let expected_variance: f64 = 0.001 / 3.0;
        let expected = expected_variance.sqrt() * 252.0_f64.sqrt();
        assert!((vol - expected).abs() < 1e-10, "Expected vol {:.6}; got {:.6}", expected, vol);
    }

    // -----------------------------------------------------------------------
    // 6. compute_sharpe_ratio
    // -----------------------------------------------------------------------
    #[test]
    fn test_sharpe_insufficient_data() {
        // Fewer than MIN_RETURN_OBSERVATIONS (5) returns
        let returns = vec![0.01, 0.02, 0.01, -0.01];
        let sharpe = RiskServiceImpl::compute_sharpe_ratio(&returns);
        assert!((sharpe - 0.0).abs() < 1e-10, "Should return 0.0 for insufficient data");
    }

    #[test]
    fn test_sharpe_zero_volatility() {
        let returns = vec![0.01; 10];
        let sharpe = RiskServiceImpl::compute_sharpe_ratio(&returns);
        assert!((sharpe - 0.0).abs() < 1e-10, "Zero volatility should return 0.0 Sharpe");
    }

    #[test]
    fn test_sharpe_positive_returns() {
        // Use enough data points with positive mean return
        let returns = vec![0.01, 0.02, 0.015, 0.005, 0.012, 0.008];
        let sharpe = RiskServiceImpl::compute_sharpe_ratio(&returns);
        // Mean return is clearly positive and above risk-free => Sharpe should be positive
        assert!(sharpe > 0.0, "Sharpe should be positive for consistently positive returns; got {}", sharpe);
    }

    // -----------------------------------------------------------------------
    // 7. compute_sortino_ratio
    // -----------------------------------------------------------------------
    #[test]
    fn test_sortino_insufficient_data() {
        let returns = vec![0.01, -0.01, 0.02];
        let sortino = RiskServiceImpl::compute_sortino_ratio(&returns);
        assert!((sortino - 0.0).abs() < 1e-10);
    }

    #[test]
    fn test_sortino_no_downside() {
        // All returns well above risk-free => no downside deviation => 0
        let daily_rf = RISK_FREE_RATE_ANNUAL / 252.0;
        let high_return = daily_rf + 0.01; // well above risk-free
        let returns = vec![high_return; 10];
        let sortino = RiskServiceImpl::compute_sortino_ratio(&returns);
        assert!((sortino - 0.0).abs() < 1e-10, "No downside should yield zero Sortino");
    }

    #[test]
    fn test_sortino_with_downside() {
        // Mix of positive and negative returns
        let returns = vec![-0.02, 0.03, -0.01, 0.02, -0.015, 0.01, 0.005];
        let sortino = RiskServiceImpl::compute_sortino_ratio(&returns);
        // Just verify it produces a finite number (not NaN/Inf)
        assert!(sortino.is_finite(), "Sortino should be finite; got {}", sortino);
    }

    // -----------------------------------------------------------------------
    // 8. VaR scaling: square-root-of-time rule
    // -----------------------------------------------------------------------
    #[test]
    fn test_var_scaling_sqrt_time() {
        // The service uses: var_5d = var_1d * sqrt(5), var_30d = var_1d * sqrt(30)
        let var_1d = 10_000.0_f64;
        let var_5d = var_1d * 5_f64.sqrt();
        let var_30d = var_1d * 30_f64.sqrt();

        assert!((var_5d - 22_360.679).abs() < 1.0,
            "5d VaR should be ~22360.68; got {:.3}", var_5d);
        assert!((var_30d - 54_772.256).abs() < 1.0,
            "30d VaR should be ~54772.26; got {:.3}", var_30d);
        // Verify ordering: 1d < 5d < 30d
        assert!(var_1d < var_5d);
        assert!(var_5d < var_30d);
    }

    // -----------------------------------------------------------------------
    // 9. Concentration risk level classification
    // -----------------------------------------------------------------------
    #[test]
    fn test_concentration_risk_level_thresholds() {
        // The service classifies: >50% Critical, >30% High, >15% Medium, else Low
        let classify = |concentration: f64| -> RiskLevel {
            if concentration > 50.0 {
                RiskLevel::Critical
            } else if concentration > 30.0 {
                RiskLevel::High
            } else if concentration > 15.0 {
                RiskLevel::Medium
            } else {
                RiskLevel::Low
            }
        };

        assert_eq!(classify(60.0) as i32, RiskLevel::Critical as i32);
        assert_eq!(classify(50.1) as i32, RiskLevel::Critical as i32);
        assert_eq!(classify(50.0) as i32, RiskLevel::High as i32);
        assert_eq!(classify(35.0) as i32, RiskLevel::High as i32);
        assert_eq!(classify(30.0) as i32, RiskLevel::Medium as i32);
        assert_eq!(classify(20.0) as i32, RiskLevel::Medium as i32);
        assert_eq!(classify(15.0) as i32, RiskLevel::Low as i32);
        assert_eq!(classify(5.0) as i32, RiskLevel::Low as i32);
        assert_eq!(classify(0.0) as i32, RiskLevel::Low as i32);
    }

    // -----------------------------------------------------------------------
    // 10. Constants are sane
    // -----------------------------------------------------------------------
    #[test]
    fn test_risk_constants() {
        assert!((RISK_FREE_RATE_ANNUAL - 0.05).abs() < 1e-10,
            "Risk-free rate should be 5%");
        assert_eq!(MIN_RETURN_OBSERVATIONS, 5,
            "Min return observations should be 5");
    }

    // -----------------------------------------------------------------------
    // 11. validate_order covers all 5 risk check categories
    // -----------------------------------------------------------------------
    #[test]
    fn test_validate_order_violation_types_cover_all_risk_checks() {
        // The validate_order handler must produce violations from these categories:
        // 1. Kill switch / circuit breaker  -> VarLimit (emergency severity)
        // 2. Max order size                 -> PositionLimit
        // 3. Position limit                 -> PositionLimit
        // 4. Daily loss / drawdown          -> Drawdown
        // 5. Leverage                       -> Concentration
        // 6. VaR limit                      -> VarLimit
        //
        // Verify the proto violation type enum values are distinct and correct.
        let kill_switch_vtype = RiskViolationType::VarLimit as i32;
        let order_size_vtype = RiskViolationType::PositionLimit as i32;
        let drawdown_vtype = RiskViolationType::Drawdown as i32;
        let leverage_vtype = RiskViolationType::Concentration as i32;
        let var_vtype = RiskViolationType::VarLimit as i32;

        // All five categories map to valid proto enum values (non-zero)
        assert_ne!(order_size_vtype, 0, "PositionLimit should be a valid violation type");
        assert_ne!(drawdown_vtype, 0, "Drawdown should be a valid violation type");
        assert_ne!(leverage_vtype, 0, "Concentration should be a valid violation type");
        assert_ne!(var_vtype, 0, "VarLimit should be a valid violation type");
        assert_ne!(kill_switch_vtype, 0, "Kill switch violation type should be valid");

        // Position limit and drawdown are distinct
        assert_ne!(order_size_vtype, drawdown_vtype,
            "Position limit and drawdown should be distinct violation types");
        // Drawdown and leverage are distinct
        assert_ne!(drawdown_vtype, leverage_vtype,
            "Drawdown and leverage (concentration) should be distinct violation types");
        // Leverage and VaR are distinct
        assert_ne!(leverage_vtype, var_vtype,
            "Leverage and VaR should be distinct violation types");
    }

    #[test]
    fn test_validate_order_response_message_indicates_all_checks() {
        // When all checks pass, the message should indicate comprehensive checking
        let response = ValidateOrderResponse {
            is_valid: true,
            violations: vec![],
            risk_score: None,
            message: "Order validation passed all 5 risk checks".to_string(),
        };
        assert!(
            response.message.contains("5 risk checks"),
            "Success message should mention all 5 risk checks; got: {}",
            response.message
        );
    }

    #[test]
    fn test_validate_order_builds_correct_violation_for_order_size() {
        let max_order_qty = 500.0;
        let req_qty = 1000.0;

        // Simulate the order size check logic from validate_order
        let mut violations = vec![];
        if req_qty > max_order_qty {
            violations.push(RiskViolation {
                violation_type: RiskViolationType::PositionLimit as i32,
                description: format!(
                    "Order size {:.2} exceeds maximum limit {:.2}",
                    req_qty, max_order_qty
                ),
                current_value: req_qty,
                limit_value: max_order_qty,
                severity: RiskAlertSeverity::Critical as i32,
            });
        }

        assert_eq!(violations.len(), 1);
        assert_eq!(violations[0].violation_type, RiskViolationType::PositionLimit as i32);
        assert!((violations[0].current_value - 1000.0).abs() < 1e-10);
        assert!((violations[0].limit_value - 500.0).abs() < 1e-10);
        assert_eq!(violations[0].severity, RiskAlertSeverity::Critical as i32);
    }

    #[test]
    fn test_validate_order_builds_correct_violation_for_drawdown() {
        let current_drawdown = 0.12;
        let daily_loss_limit = 0.10;

        let mut violations = vec![];
        if current_drawdown >= daily_loss_limit {
            violations.push(RiskViolation {
                violation_type: RiskViolationType::Drawdown as i32,
                description: format!(
                    "Daily loss limit reached: current drawdown {:.4} >= limit {:.4}",
                    current_drawdown, daily_loss_limit
                ),
                current_value: current_drawdown,
                limit_value: daily_loss_limit,
                severity: RiskAlertSeverity::Critical as i32,
            });
        }

        assert_eq!(violations.len(), 1);
        assert_eq!(violations[0].violation_type, RiskViolationType::Drawdown as i32);
        assert!((violations[0].current_value - 0.12).abs() < 1e-10);
        assert!((violations[0].limit_value - 0.10).abs() < 1e-10);
    }

    #[test]
    fn test_validate_order_builds_correct_violation_for_leverage() {
        let leverage_ratio = 12.0;
        let max_leverage = 10.0;

        let mut violations = vec![];
        if leverage_ratio >= max_leverage {
            violations.push(RiskViolation {
                violation_type: RiskViolationType::Concentration as i32,
                description: format!(
                    "Leverage limit exceeded: current {:.2}x >= max {:.2}x",
                    leverage_ratio, max_leverage
                ),
                current_value: leverage_ratio,
                limit_value: max_leverage,
                severity: RiskAlertSeverity::Critical as i32,
            });
        }

        assert_eq!(violations.len(), 1);
        assert_eq!(violations[0].violation_type, RiskViolationType::Concentration as i32);
        assert!((violations[0].current_value - 12.0).abs() < 1e-10);
        assert!((violations[0].limit_value - 10.0).abs() < 1e-10);
    }

    #[test]
    fn test_validate_order_multiple_violations_accumulate() {
        // Simulates a scenario where both order size and leverage checks fail
        let violations = [
            // Order size violation
            RiskViolation {
                violation_type: RiskViolationType::PositionLimit as i32,
                description: "Order size exceeded".to_string(),
                current_value: 2000.0,
                limit_value: 1000.0,
                severity: RiskAlertSeverity::Critical as i32,
            },
            // Leverage violation
            RiskViolation {
                violation_type: RiskViolationType::Concentration as i32,
                description: "Leverage exceeded".to_string(),
                current_value: 15.0,
                limit_value: 10.0,
                severity: RiskAlertSeverity::Critical as i32,
            },
            // Drawdown violation
            RiskViolation {
                violation_type: RiskViolationType::Drawdown as i32,
                description: "Daily loss limit".to_string(),
                current_value: 0.15,
                limit_value: 0.10,
                severity: RiskAlertSeverity::Critical as i32,
            },
        ];

        assert_eq!(violations.len(), 3,
            "All violations should accumulate, not short-circuit");

        // Verify each violation type is present
        let types: Vec<i32> = violations.iter().map(|v| v.violation_type).collect();
        assert!(types.contains(&(RiskViolationType::PositionLimit as i32)));
        assert!(types.contains(&(RiskViolationType::Concentration as i32)));
        assert!(types.contains(&(RiskViolationType::Drawdown as i32)));
    }
}
