//! Financial calculation unit tests
//! 
//! Tests mathematical accuracy, edge cases, and precision requirements
//! for financial calculations in the trading system.

// CANONICAL TYPE IMPORTS - Use types::prelude::Decimal
use proptest::prelude::*;
use num_traits::FromPrimitive; // For Decimal::from_f64

#[cfg(test)]
mod tests {
    use super::*;
    
    #[tokio::test]
    async fn test_pnl_calculations_precision() {
        // Test precise PnL calculations
        let entry_price = Price::new(Decimal::from_str("123.456789").unwrap()).map_err(|e| format!("Failed to create entry price: {}", e)).unwrap();
        let current_price = Price::new(Decimal::from_str("125.987654").unwrap()).map_err(|e| format!("Failed to create current price: {}", e)).unwrap();
        let quantity = Quantity::new(Decimal::from(1000)).map_err(|e| format!("Failed to create quantity: {}", e)).unwrap();
        
        let unrealized_pnl = calculate_unrealized_pnl(
            &quantity,
            &entry_price,
            &current_price,
            Side::Buy
        ).expect("Should calculate unrealized PnL");
        
        // Expected: (125.987654 - 123.456789) * 1000 = 2530.865
        let expected = Decimal::from_str("2530.865000").unwrap();
        assert_eq!(unrealized_pnl.value(), expected);
    }
    
    #[tokio::test]
    async fn test_pnl_short_position() {
        let entry_price = Price::new(Decimal::from_str("100.00").unwrap()).map_err(|e| format!("Failed to create entry price: {}", e)).unwrap();
        let current_price = Price::new(Decimal::from_str("95.00").unwrap()).map_err(|e| format!("Failed to create current price: {}", e)).unwrap();
        let quantity = Quantity::new(Decimal::from(100)).map_err(|e| format!("Failed to create quantity: {}", e)).unwrap();
        
        // Short position: profit when price goes down
        let unrealized_pnl = calculate_unrealized_pnl(
            &quantity,
            &entry_price,
            &current_price,
            Side::Sell
        ).expect("Should calculate short PnL");
        
        // Expected: (100.00 - 95.00) * 100 = 500.00 profit
        let expected = Decimal::from(500);
        assert_eq!(unrealized_pnl.value(), expected);
    }
    
    #[tokio::test]
    async fn test_commission_calculations() {
        let trade_value = Money::new(Decimal::from(10000), Currency::USD);
        let commission_rate = Decimal::from_str("0.001").unwrap(); // 0.1%
        
        let commission = calculate_commission(&trade_value, commission_rate)
            .expect("Should calculate commission");
        
        assert_eq!(commission.amount, Decimal::from(10)); // $10 commission
        assert_eq!(commission.currency, Currency::USD);
    }
    
    #[tokio::test]
    async fn test_slippage_calculations() {
        let expected_price = Price::new(Decimal::from(100)).map_err(|e| format!("Failed to create expected price: {}", e)).unwrap();
        let actual_price = Price::new(Decimal::from_str("100.05").unwrap()).map_err(|e| format!("Failed to create actual price: {}", e)).unwrap();
        let quantity = Quantity::new(Decimal::from(1000)).map_err(|e| format!("Failed to create quantity: {}", e)).unwrap();
        
        let slippage = calculate_slippage(
            &expected_price,
            &actual_price,
            &quantity,
            Side::Buy
        ).expect("Should calculate slippage");
        
        // Expected slippage: (100.05 - 100.00) * 1000 = 50.00
        assert_eq!(slippage.amount, Decimal::from_str("50.00").unwrap());
    }
    
    #[tokio::test]
    async fn test_portfolio_value_calculation() {
        let mut portfolio = Portfolio::new();
        
        // Add AAPL position
        let aapl_position = Position {
            symbol: Symbol::new("AAPL".to_string()).map_err(|e| format!("Failed to create AAPL symbol: {}", e)).unwrap(),
            quantity: Quantity::new(Decimal::from(100)).map_err(|e| format!("Failed to create quantity: {}", e)).unwrap(),
            average_price: Price::new(Decimal::from(150)).map_err(|e| format!("Failed to create average price: {}", e)).unwrap(),
            realized_pnl: PnL::new(Decimal::ZERO).map_err(|e| format!("Failed to create realized PnL: {}", e)).unwrap(),
            unrealized_pnl: PnL::new(Decimal::ZERO).map_err(|e| format!("Failed to create unrealized PnL: {}", e)).unwrap(),
            last_updated: chrono::Utc::now(),
        };
        portfolio.add_position(aapl_position);
        
        // Add MSFT position
        let msft_position = Position {
            symbol: Symbol::new("MSFT".to_string()).map_err(|e| format!("Failed to create MSFT symbol: {}", e)).unwrap(),
            quantity: Quantity::new(Decimal::from(50)).map_err(|e| format!("Failed to create quantity: {}", e)).unwrap(),
            average_price: Price::new(Decimal::from(300)).map_err(|e| format!("Failed to create average price: {}", e)).unwrap(),
            realized_pnl: PnL::new(Decimal::ZERO).map_err(|e| format!("Failed to create realized PnL: {}", e)).unwrap(),
            unrealized_pnl: PnL::new(Decimal::ZERO).map_err(|e| format!("Failed to create unrealized PnL: {}", e)).unwrap(),
            last_updated: chrono::Utc::now(),
        };
        portfolio.add_position(msft_position);
        
        // Mock current prices
        let mut current_prices = std::collections::HashMap::new();
        current_prices.insert(
            Symbol::new("AAPL".to_string()).map_err(|e| format!("Failed to create AAPL symbol: {}", e)).unwrap(),
            Price::new(Decimal::from(155)).map_err(|e| format!("Failed to create AAPL price: {}", e)).unwrap()
        );
        current_prices.insert(
            Symbol::new("MSFT".to_string()).map_err(|e| format!("Failed to create MSFT symbol: {}", e)).unwrap(),
            Price::new(Decimal::from(310)).map_err(|e| format!("Failed to create MSFT price: {}", e)).unwrap()
        );
        
        let total_value = portfolio.calculate_total_value(&current_prices)
            .expect("Should calculate portfolio value");
        
        // AAPL: 100 * 155 = 15,500
        // MSFT: 50 * 310 = 15,500
        // Total: 31,000
        assert_eq!(total_value.amount, Decimal::from(31000));
    }
    
    #[tokio::test]
    async fn test_risk_metrics() {
        let positions = vec![
            Position {
                symbol: Symbol::new("AAPL".to_string()).map_err(|e| format!("Failed to create AAPL symbol: {}", e)).unwrap(),
                quantity: Quantity::new(Decimal::from(1000)).map_err(|e| format!("Failed to create quantity: {}", e)).unwrap(),
                average_price: Price::new(Decimal::from(150)).map_err(|e| format!("Failed to create average price: {}", e)).unwrap(),
                realized_pnl: PnL::new(Decimal::from(500)).map_err(|e| format!("Failed to create realized PnL: {}", e)).unwrap(),
                unrealized_pnl: PnL::new(Decimal::from(-200)).map_err(|e| format!("Failed to create unrealized PnL: {}", e)).unwrap(),
                last_updated: chrono::Utc::now(),
            },
            Position {
                symbol: Symbol::new("MSFT".to_string()).map_err(|e| format!("Failed to create MSFT symbol: {}", e)).unwrap(),
                quantity: Quantity::new(Decimal::from(-500)).map_err(|e| format!("Failed to create quantity: {}", e)).unwrap(), // Short position
                average_price: Price::new(Decimal::from(300)).map_err(|e| format!("Failed to create average price: {}", e)).unwrap(),
                realized_pnl: PnL::new(Decimal::from(1000)).map_err(|e| format!("Failed to create realized PnL: {}", e)).unwrap(),
                unrealized_pnl: PnL::new(Decimal::from(300)).map_err(|e| format!("Failed to create unrealized PnL: {}", e)).unwrap(),
                last_updated: chrono::Utc::now(),
            },
        ];
        
        let risk_metrics = calculate_portfolio_risk(&positions)
            .expect("Should calculate risk metrics");
        
        // Test that risk metrics are computed
        assert!(risk_metrics.total_exposure.amount > Decimal::ZERO);
        assert!(risk_metrics.net_exposure.amount != Decimal::ZERO);
        assert!(risk_metrics.var_1_day.is_some());
    }
    
    #[tokio::test]
    async fn test_compound_interest() {
        let principal = Decimal::from(10000);
        let rate = Decimal::from_str("0.05").unwrap(); // 5% annual
        let periods = 252; // Trading days in a year
        let time_years = Decimal::from(1);
        
        let final_amount = calculate_compound_interest(principal, rate, periods, time_years)
            .expect("Should calculate compound interest");
        
        // Compound daily: 10000 * (1 + 0.05/252)^252 ≈ 10512.71
        let expected_min = Decimal::from_str("10500").unwrap();
        let expected_max = Decimal::from_str("10520").unwrap();
        
        assert!(final_amount >= expected_min);
        assert!(final_amount <= expected_max);
    }
    
    #[tokio::test]
    async fn test_sharpe_ratio() {
        let returns = vec![
            Decimal::from_str("0.02").unwrap(),   // 2%
            Decimal::from_str("-0.01").unwrap(),  // -1%
            Decimal::from_str("0.03").unwrap(),   // 3%
            Decimal::from_str("0.01").unwrap(),   // 1%
            Decimal::from_str("-0.02").unwrap(),  // -2%
        ];
        
        let risk_free_rate = Decimal::from_str("0.001").unwrap(); // 0.1%
        
        let sharpe = calculate_sharpe_ratio(&returns, risk_free_rate)
            .expect("Should calculate Sharpe ratio");
        
        // Expected: (mean_return - risk_free_rate) / std_dev
        assert!(sharpe > Decimal::ZERO);
        assert!(sharpe < Decimal::from(10)); // Reasonable upper bound
    }
    
    #[tokio::test]
    async fn test_max_drawdown() {
        let portfolio_values = vec![
            Decimal::from(100000),
            Decimal::from(105000),
            Decimal::from(102000),
            Decimal::from(98000),   // Drawdown starts
            Decimal::from(95000),   // Maximum drawdown
            Decimal::from(97000),
            Decimal::from(103000),  // Recovery
        ];
        
        let max_drawdown = calculate_max_drawdown(&portfolio_values)
            .expect("Should calculate max drawdown");
        
        // Max drawdown: (105000 - 95000) / 105000 ≈ 9.52%
        let expected = Decimal::from_str("0.095238095238095238").unwrap();
        let tolerance = Decimal::from_str("0.001").unwrap();
        
        assert!((max_drawdown - expected).abs() < tolerance);
    }
}

// Helper functions for financial calculations
fn calculate_unrealized_pnl(
    quantity: &Quantity,
    entry_price: &Price,
    current_price: &Price,
    side: Side,
) -> Result<PnL, Box<dyn std::error::Error>> {
    let price_diff = match side {
        Side::Buy => current_price.value() - entry_price.value(),
        Side::Sell => entry_price.value() - current_price.value(),
    };
    
    let pnl_value = price_diff * quantity.value();
    Ok(PnL::new(pnl_value)?)
}

fn calculate_commission(
    trade_value: &Money,
    rate: Decimal,
) -> Result<Money, Box<dyn std::error::Error>> {
    let commission_amount = trade_value.amount * rate;
    Ok(Money::new(commission_amount, trade_value.currency))
}

fn calculate_slippage(
    expected_price: &Price,
    actual_price: &Price,
    quantity: &Quantity,
    _side: Side,
) -> Result<Money, Box<dyn std::error::Error>> {
    let price_diff = (actual_price.value() - expected_price.value()).abs();
    let slippage_cost = price_diff * quantity.value();
    Ok(Money::new(slippage_cost, Currency::USD))
}

fn calculate_portfolio_risk(
    positions: &[Position],
) -> Result<PositionRiskMetrics, Box<dyn std::error::Error>> {
    let mut total_long_exposure = Decimal::ZERO;
    let mut total_short_exposure = Decimal::ZERO;
    
    for position in positions {
        let position_value = position.quantity.value().abs() * position.average_price.value();
        
        if position.quantity.value() > Decimal::ZERO {
            total_long_exposure += position_value;
        } else {
            total_short_exposure += position_value;
        }
    }
    
    Ok(PositionRiskMetrics {
        total_exposure: Money::new(total_long_exposure + total_short_exposure, Currency::USD),
        net_exposure: Money::new(total_long_exposure - total_short_exposure, Currency::USD),
        long_exposure: Money::new(total_long_exposure, Currency::USD),
        short_exposure: Money::new(total_short_exposure, Currency::USD),
        var_1_day: Some(Money::new(total_long_exposure * Decimal::from_str("0.02").unwrap(), Currency::USD)),
        var_5_day: Some(Money::new(total_long_exposure * Decimal::from_str("0.045").unwrap(), Currency::USD)),
        beta: Some(Decimal::from_str("1.0").unwrap()),
        correlation_to_market: Some(Decimal::from_str("0.8").unwrap()),
    })
}

fn calculate_compound_interest(
    principal: Decimal,
    annual_rate: Decimal,
    compounding_periods: i32,
    years: Decimal,
) -> Result<Decimal, Box<dyn std::error::Error>> {
    let periods_decimal = Decimal::from(compounding_periods);
    let rate_per_period = annual_rate / periods_decimal;
    let total_periods = periods_decimal * years;
    
    // A = P(1 + r/n)^(nt)
    let compound_factor = (Decimal::ONE + rate_per_period)
        .powd(total_periods)
        .ok_or("Compound calculation overflow")?;
    
    Ok(principal * compound_factor)
}

fn calculate_sharpe_ratio(
    returns: &[Decimal],
    risk_free_rate: Decimal,
) -> Result<Decimal, Box<dyn std::error::Error>> {
    if returns.is_empty() {
        return Err("No returns provided".into());
    }
    
    // Calculate mean return
    let sum: Decimal = returns.iter().sum();
    let mean = sum / Decimal::from(returns.len());
    
    // Calculate excess return
    let excess_return = mean - risk_free_rate;
    
    // Calculate standard deviation
    let variance: Decimal = returns.iter()
        .map(|r| (*r - mean).powi(2))
        .sum::<Decimal>() / Decimal::from(returns.len());
    
    let std_dev = variance.sqrt().ok_or("Cannot calculate square root")?;
    
    if std_dev == Decimal::ZERO {
        return Err("Zero standard deviation".into());
    }
    
    Ok(excess_return / std_dev)
}

fn calculate_max_drawdown(
    portfolio_values: &[Decimal],
) -> Result<Decimal, Box<dyn std::error::Error>> {
    if portfolio_values.is_empty() {
        return Err("No portfolio values provided".into());
    }
    
    let mut peak = portfolio_values[0];
    let mut max_drawdown = Decimal::ZERO;
    
    for &value in &portfolio_values {
        if value > peak {
            peak = value;
        }
        
        let drawdown = (peak - value) / peak;
        if drawdown > max_drawdown {
            max_drawdown = drawdown;
        }
    }
    
    Ok(max_drawdown)
}

#[cfg(test)]
mod property_tests {
    use super::*;
    use proptest::prelude::*;
    
    proptest! {
        #[test]
        fn pnl_calculation_symmetry(
            entry_price in 1.0f64..1000.0,
            current_price in 1.0f64..1000.0,
            quantity in 1u32..10000
        ) {
            let entry = Price::new(Decimal::from_f64_retain(entry_price).unwrap()).map_err(|e| format!("Failed to create entry price: {}", e)).unwrap();
            let current = Price::new(Decimal::from_f64_retain(current_price).unwrap()).map_err(|e| format!("Failed to create current price: {}", e)).unwrap();
            let qty = Quantity::new(Decimal::from(quantity)).map_err(|e| format!("Failed to create quantity: {}", e)).unwrap();
            
            let long_pnl = calculate_unrealized_pnl(&qty, &entry, &current, Side::Buy).unwrap();
            let short_pnl = calculate_unrealized_pnl(&qty, &entry, &current, Side::Sell).unwrap();
            
            // Long and short PnL should be opposite
            prop_assert_eq!(long_pnl.value(), -short_pnl.value());
        }
        
        #[test]
        fn commission_proportional(
            trade_value in 100.0f64..1_000_000.0,
            rate in 0.0001f64..0.01 // 0.01% to 1%
        ) {
            let value = Money::new(
                Decimal::from_f64_retain(trade_value).unwrap(),
                Currency::USD
            );
            let commission_rate = Decimal::from_f64_retain(rate).unwrap();
            
            let commission = calculate_commission(&value, commission_rate).unwrap();
            
            // Commission should be proportional to trade value
            let expected = value.amount * commission_rate;
            prop_assert_eq!(commission.amount, expected);
            
            // Commission should never exceed trade value
            prop_assert!(commission.amount <= value.amount);
        }
    }
}