//! Sharpe Ratio Calculation Utilities
//!
//! This module provides utilities for calculating the Sharpe ratio, a widely used
//! metric for risk-adjusted returns in trading strategies. The Sharpe ratio measures
//! excess return per unit of volatility.
//!
//! # Formula
//!
//! ```text
//! Sharpe Ratio = (Mean Return - Risk-Free Rate) / Standard Deviation of Returns
//!                × sqrt(Periods per Year)
//! ```
//!
//! # Examples
//!
//! ```rust
//! use ml::metrics::sharpe::calculate_sharpe_ratio;
//!
//! let daily_returns = vec![0.01, -0.005, 0.02, 0.015, -0.01];
//! let sharpe = calculate_sharpe_ratio(&daily_returns, 0.02, 252.0).unwrap();
//! println!("Annualized Sharpe Ratio: {:.2}", sharpe);
//! ```

use crate::MLError;

/// Calculate annualized Sharpe ratio from trading returns
///
/// The Sharpe ratio is a measure of risk-adjusted return, calculated as the ratio
/// of excess return (return above the risk-free rate) to the standard deviation
/// of returns. Higher values indicate better risk-adjusted performance.
///
/// # Arguments
///
/// * `returns` - Vector of period returns (e.g., daily PnL percentages as decimals)
///   Example: 1% return = 0.01, -2% return = -0.02
/// * `risk_free_rate` - Annual risk-free rate as a decimal (default: 0.02 = 2%)
/// * `periods_per_year` - Number of periods per year for annualization
///   - Daily returns: 252 trading days
///   - Weekly returns: 52 weeks
///   - Monthly returns: 12 months
///
/// # Returns
///
/// Returns `Ok(f64)` with the annualized Sharpe ratio, or `Err(MLError)` if calculation fails.
///
/// # Edge Cases
///
/// - Empty returns vector → returns 0.0
/// - Zero standard deviation (constant returns) → returns 0.0
/// - Negative Sharpe ratio → allowed (indicates losses exceed risk-free rate)
///
/// # Examples
///
/// ```rust
/// use ml::metrics::sharpe::calculate_sharpe_ratio;
///
/// // Daily returns with 2% annual risk-free rate
/// let returns = vec![0.01, -0.005, 0.02, 0.015, -0.01, 0.008];
/// let sharpe = calculate_sharpe_ratio(&returns, 0.02, 252.0).unwrap();
/// assert!(sharpe > 0.0); // Positive returns should yield positive Sharpe
/// ```
///
/// # Performance
///
/// This implementation uses a numerically stable two-pass algorithm:
/// 1. First pass: Calculate mean
/// 2. Second pass: Calculate variance
///
/// Time complexity: O(n) where n is the number of returns
/// Space complexity: O(1)
pub fn calculate_sharpe_ratio(
    returns: &[f64],
    risk_free_rate: f64,
    periods_per_year: f64,
) -> Result<f64, MLError> {
    // Edge case: Empty returns
    if returns.is_empty() {
        return Ok(0.0);
    }

    // Validate inputs
    if periods_per_year <= 0.0 {
        return Err(MLError::ValidationError {
            message: format!(
                "Periods per year must be positive, got: {}",
                periods_per_year
            ),
        });
    }

    if !risk_free_rate.is_finite() {
        return Err(MLError::ValidationError {
            message: format!("Risk-free rate must be finite, got: {}", risk_free_rate),
        });
    }

    // Check for NaN or infinite values in returns
    for (i, &ret) in returns.iter().enumerate() {
        if !ret.is_finite() {
            return Err(MLError::ValidationError {
                message: format!("Return at index {} is not finite: {}", i, ret),
            });
        }
    }

    // Calculate mean return
    let mean_return = returns.iter().sum::<f64>() / returns.len() as f64;

    // Calculate variance using two-pass algorithm for numerical stability
    let variance = returns
        .iter()
        .map(|&r| {
            let diff = r - mean_return;
            diff * diff
        })
        .sum::<f64>()
        / returns.len() as f64;

    // Calculate standard deviation
    let std_dev = variance.sqrt();

    // Edge case: Zero standard deviation (constant returns)
    if std_dev == 0.0 || std_dev < 1e-10 {
        // If returns are constant and above risk-free rate, return infinity
        // If returns are constant and below risk-free rate, return negative infinity
        // For practical purposes, return 0.0 to indicate no risk-adjusted excess return
        return Ok(0.0);
    }

    // Convert annual risk-free rate to period risk-free rate
    let period_risk_free_rate = risk_free_rate / periods_per_year;

    // Calculate Sharpe ratio
    let excess_return = mean_return - period_risk_free_rate;
    let sharpe_ratio = (excess_return / std_dev) * periods_per_year.sqrt();

    // Validate result
    if !sharpe_ratio.is_finite() {
        return Err(MLError::ValidationError {
            message: format!(
                "Calculated Sharpe ratio is not finite: {} (mean_return: {}, std_dev: {}, excess_return: {})",
                sharpe_ratio, mean_return, std_dev, excess_return
            ),
        });
    }

    Ok(sharpe_ratio)
}

/// Calculate Sharpe ratio with default parameters
///
/// Uses standard defaults:
/// - Risk-free rate: 2% annual (0.02)
/// - Periods per year: 252 (daily trading periods)
///
/// # Arguments
///
/// * `returns` - Vector of daily returns as decimals
///
/// # Examples
///
/// ```rust
/// use ml::metrics::sharpe::calculate_sharpe_ratio_default;
///
/// let daily_returns = vec![0.01, -0.005, 0.02, 0.015, -0.01];
/// let sharpe = calculate_sharpe_ratio_default(&daily_returns).unwrap();
/// ```
pub fn calculate_sharpe_ratio_default(returns: &[f64]) -> Result<f64, MLError> {
    calculate_sharpe_ratio(returns, 0.02, 252.0)
}

#[cfg(test)]
#[allow(clippy::assertions_on_result_states)]
mod tests {
    use super::*;
    use approx::assert_relative_eq;

    #[test]
    fn test_sharpe_ratio_positive_returns() {
        // Manual calculation test case
        // Returns: [2%, 3%, 1%, 4%, 2.5%] = [0.02, 0.03, 0.01, 0.04, 0.025]
        let returns = vec![0.02, 0.03, 0.01, 0.04, 0.025];

        // Mean return = (0.02 + 0.03 + 0.01 + 0.04 + 0.025) / 5 = 0.125 / 5 = 0.025 (2.5%)
        let mean_return = 0.025;

        // Variance calculation:
        // (0.02 - 0.025)^2 = 0.000025
        // (0.03 - 0.025)^2 = 0.000025
        // (0.01 - 0.025)^2 = 0.000225
        // (0.04 - 0.025)^2 = 0.000225
        // (0.025 - 0.025)^2 = 0.0
        // Sum = 0.0005, Variance = 0.0005 / 5 = 0.0001
        let std_dev = 0.0001_f64.sqrt(); // = 0.01

        // Risk-free rate (annual): 2% = 0.02
        // Period risk-free rate (daily): 0.02 / 252 ≈ 0.0000793651
        let period_rf = 0.02 / 252.0;

        // Excess return = 0.025 - 0.0000793651 ≈ 0.0249206349
        let excess_return = mean_return - period_rf;

        // Sharpe = (0.0249206349 / 0.01) * sqrt(252)
        //         = 2.49206349 * 15.8745...
        //         ≈ 39.56
        let expected_sharpe = (excess_return / std_dev) * 252.0_f64.sqrt();

        let sharpe = calculate_sharpe_ratio(&returns, 0.02, 252.0).unwrap();

        assert_relative_eq!(sharpe, expected_sharpe, epsilon = 0.01);
        assert!(
            sharpe > 0.0,
            "Positive returns should yield positive Sharpe"
        );
    }

    #[test]
    fn test_sharpe_ratio_negative_returns() {
        // Negative returns should yield negative Sharpe ratio
        let returns = vec![-0.01, -0.02, -0.015, -0.03, -0.01];
        let sharpe = calculate_sharpe_ratio(&returns, 0.02, 252.0).unwrap();

        assert!(
            sharpe < 0.0,
            "Negative returns should yield negative Sharpe"
        );
    }

    #[test]
    fn test_sharpe_ratio_empty_returns() {
        // Empty returns should return 0.0
        let returns: Vec<f64> = vec![];
        let sharpe = calculate_sharpe_ratio(&returns, 0.02, 252.0).unwrap();

        assert_eq!(sharpe, 0.0);
    }

    #[test]
    fn test_sharpe_ratio_constant_returns() {
        // Zero standard deviation (all returns identical)
        let returns = vec![0.01, 0.01, 0.01, 0.01, 0.01];
        let sharpe = calculate_sharpe_ratio(&returns, 0.02, 252.0).unwrap();

        // Constant returns yield 0.0 Sharpe ratio
        assert_eq!(sharpe, 0.0);
    }

    #[test]
    fn test_sharpe_ratio_single_return() {
        // Single return has zero variance
        let returns = vec![0.05];
        let sharpe = calculate_sharpe_ratio(&returns, 0.02, 252.0).unwrap();

        assert_eq!(sharpe, 0.0);
    }

    #[test]
    fn test_sharpe_ratio_mixed_returns() {
        // Mixed positive and negative returns
        let returns = vec![0.02, -0.01, 0.03, -0.005, 0.015];
        let sharpe = calculate_sharpe_ratio(&returns, 0.02, 252.0).unwrap();

        // Should be finite
        assert!(sharpe.is_finite());
    }

    #[test]
    fn test_sharpe_ratio_zero_risk_free_rate() {
        let returns = vec![0.01, 0.02, -0.005, 0.015];
        let sharpe = calculate_sharpe_ratio(&returns, 0.0, 252.0).unwrap();

        assert!(sharpe.is_finite());
        assert!(sharpe > 0.0); // Positive mean should yield positive Sharpe
    }

    #[test]
    fn test_sharpe_ratio_high_volatility() {
        // High volatility should reduce Sharpe ratio
        let returns = vec![0.1, -0.08, 0.12, -0.09, 0.11];
        let sharpe_high_vol = calculate_sharpe_ratio(&returns, 0.02, 252.0).unwrap();

        // Lower volatility returns with same mean
        let returns_low_vol = vec![0.032, 0.03, 0.034, 0.03, 0.034];
        let sharpe_low_vol = calculate_sharpe_ratio(&returns_low_vol, 0.02, 252.0).unwrap();

        // Lower volatility should yield higher Sharpe ratio
        assert!(sharpe_low_vol > sharpe_high_vol);
    }

    #[test]
    fn test_sharpe_ratio_different_periods() {
        let returns = vec![0.01, 0.02, -0.005, 0.015, 0.01];

        // Daily (252 periods)
        let sharpe_daily = calculate_sharpe_ratio(&returns, 0.02, 252.0).unwrap();

        // Weekly (52 periods)
        let sharpe_weekly = calculate_sharpe_ratio(&returns, 0.02, 52.0).unwrap();

        // Monthly (12 periods)
        let sharpe_monthly = calculate_sharpe_ratio(&returns, 0.02, 12.0).unwrap();

        // Daily should be highest due to sqrt(periods) scaling
        assert!(sharpe_daily > sharpe_weekly);
        assert!(sharpe_weekly > sharpe_monthly);
    }

    #[test]
    fn test_sharpe_ratio_default() {
        let returns = vec![0.01, 0.02, -0.005, 0.015];

        let sharpe_default = calculate_sharpe_ratio_default(&returns).unwrap();
        let sharpe_explicit = calculate_sharpe_ratio(&returns, 0.02, 252.0).unwrap();

        assert_relative_eq!(sharpe_default, sharpe_explicit, epsilon = 1e-10);
    }

    #[test]
    fn test_sharpe_ratio_validation_invalid_periods() {
        let returns = vec![0.01, 0.02, 0.015];

        // Zero periods per year
        let result = calculate_sharpe_ratio(&returns, 0.02, 0.0);
        assert!(result.is_err());

        // Negative periods per year
        let result = calculate_sharpe_ratio(&returns, 0.02, -252.0);
        assert!(result.is_err());
    }

    #[test]
    fn test_sharpe_ratio_validation_nan_returns() {
        let returns = vec![0.01, f64::NAN, 0.015];

        let result = calculate_sharpe_ratio(&returns, 0.02, 252.0);
        assert!(result.is_err());
    }

    #[test]
    fn test_sharpe_ratio_validation_infinite_returns() {
        let returns = vec![0.01, f64::INFINITY, 0.015];

        let result = calculate_sharpe_ratio(&returns, 0.02, 252.0);
        assert!(result.is_err());
    }

    #[test]
    fn test_sharpe_ratio_validation_nan_risk_free_rate() {
        let returns = vec![0.01, 0.02, 0.015];

        let result = calculate_sharpe_ratio(&returns, f64::NAN, 252.0);
        assert!(result.is_err());
    }

    #[test]
    fn test_sharpe_ratio_large_dataset() {
        // Test with realistic trading dataset (1 year of daily returns)
        let mut returns = Vec::with_capacity(252);
        for i in 0..252 {
            // Simulate realistic returns with some volatility
            let base_return = 0.001; // 0.1% daily average
            let volatility = 0.02 * ((i as f64 * 0.1).sin()); // ±2% volatility
            returns.push(base_return + volatility);
        }

        let sharpe = calculate_sharpe_ratio(&returns, 0.02, 252.0).unwrap();

        assert!(sharpe.is_finite());
        assert!(sharpe != 0.0); // Should have some risk-adjusted return
    }

    #[test]
    fn test_sharpe_ratio_zero_mean_returns() {
        // Returns that average to zero
        let returns = vec![0.01, -0.01, 0.02, -0.02, 0.005, -0.005];
        let sharpe = calculate_sharpe_ratio(&returns, 0.02, 252.0).unwrap();

        // Should be negative (below risk-free rate)
        assert!(sharpe < 0.0);
    }

    #[test]
    fn test_sharpe_ratio_comparison() {
        // Strategy A: Higher returns, higher volatility
        let returns_a = vec![0.05, -0.03, 0.06, -0.02, 0.04];
        let sharpe_a = calculate_sharpe_ratio(&returns_a, 0.02, 252.0).unwrap();

        // Strategy B: Lower returns, lower volatility
        let returns_b = vec![0.015, 0.012, 0.018, 0.014, 0.016];
        let sharpe_b = calculate_sharpe_ratio(&returns_b, 0.02, 252.0).unwrap();

        // Both should be valid comparisons
        assert!(sharpe_a.is_finite());
        assert!(sharpe_b.is_finite());

        // Strategy B has better risk-adjusted returns (lower volatility)
        assert!(sharpe_b > sharpe_a);
    }
}
