//! Comprehensive Kelly Sizing Tests
//!
//! Tests for Kelly Criterion position sizing module - CRITICAL for capital protection.
//! Kelly sizing determines optimal position sizes based on win rate and profit/loss ratios.

use super::*;
use crate::kelly_sizing::{KellySizer, TradeOutcome};
use chrono::Utc;
use common::types::{Price, Symbol};
use config::structures::KellyConfig;
use rust_decimal::prelude::FromPrimitive;
use rust_decimal::Decimal;

// ============================================================================
// Test Helpers
// ============================================================================

fn create_test_kelly_config() -> KellyConfig {
    KellyConfig {
        enabled: true,
        fractional_kelly: 0.5, // Half-Kelly for safety
        min_kelly_fraction: 0.01,
        max_kelly_fraction: 0.10,
        confidence_threshold: 0.7,
        lookback_periods: 100,
        default_position_fraction: 0.02,
    }
}

fn create_test_outcome(
    symbol: &str,
    strategy_id: &str,
    profit_loss: f64,
    win: bool,
) -> TradeOutcome {
    TradeOutcome {
        symbol: Symbol::from(symbol),
        strategy_id: strategy_id.to_string(),
        entry_price: Price::from_f64(100.0).unwrap_or(Price::ZERO),
        exit_price: Price::from_f64(if win { 105.0 } else { 95.0 }).unwrap_or(Price::ZERO),
        quantity: Price::from_f64(10.0).unwrap_or(Price::ZERO),
        profit_loss: Decimal::from_f64(profit_loss).unwrap_or(Decimal::ZERO),
        win,
        trade_date: Utc::now(),
    }
}

// ============================================================================
// Kelly Fraction Calculation Tests
// ============================================================================

#[tokio::test]
async fn test_kelly_insufficient_data_error() {
    let config = create_test_kelly_config();
    let sizer = KellySizer::new(config);

    let result = sizer.calculate_kelly_fraction(&Symbol::from("AAPL"), "test_strategy");

    assert!(result.is_err(), "Should fail with insufficient data");
    match result {
        Err(crate::error::RiskError::DataUnavailable { resource, reason }) => {
            assert_eq!(resource, "trade_history");
            assert!(reason.contains("Insufficient trade history"));
            assert!(reason.contains("minimum 10 required"));
        }
        _ => panic!("Expected DataUnavailable error"),
    }
}

#[tokio::test]
async fn test_kelly_exactly_10_trades_minimum() {
    let config = create_test_kelly_config();
    let sizer = KellySizer::new(config);

    // Add exactly 10 trades (minimum threshold)
    for i in 0..10 {
        let win = i % 2 == 0;
        let outcome = create_test_outcome("AAPL", "test_strategy", if win { 50.0 } else { -30.0 }, win);
        sizer.add_trade_outcome(outcome).unwrap();
    }

    let result = sizer.calculate_kelly_fraction(&Symbol::from("AAPL"), "test_strategy");
    assert!(result.is_ok(), "Should succeed with exactly 10 trades");
}

#[tokio::test]
async fn test_kelly_positive_edge() {
    let config = create_test_kelly_config();
    let sizer = KellySizer::new(config);

    // 60% win rate with 2:1 risk/reward
    for _ in 0..12 {
        let outcome = create_test_outcome("AAPL", "test_strategy", 100.0, true);
        sizer.add_trade_outcome(outcome).unwrap();
    }
    for _ in 0..8 {
        let outcome = create_test_outcome("AAPL", "test_strategy", -50.0, false);
        sizer.add_trade_outcome(outcome).unwrap();
    }

    let result = sizer.calculate_kelly_fraction(&Symbol::from("AAPL"), "test_strategy").unwrap();

    assert_eq!(result.sample_size, 20);
    assert_eq!(result.win_rate, 0.6);
    assert!(result.raw_kelly_fraction > 0.0, "Positive edge should produce positive Kelly");
    assert!(result.adjusted_kelly_fraction > 0.0);
}

#[tokio::test]
async fn test_kelly_negative_edge() {
    let config = create_test_kelly_config();
    let sizer = KellySizer::new(config);

    // 30% win rate with poor risk/reward (losing strategy)
    for _ in 0..6 {
        let outcome = create_test_outcome("AAPL", "test_strategy", 50.0, true);
        sizer.add_trade_outcome(outcome).unwrap();
    }
    for _ in 0..14 {
        let outcome = create_test_outcome("AAPL", "test_strategy", -50.0, false);
        sizer.add_trade_outcome(outcome).unwrap();
    }

    let result = sizer.calculate_kelly_fraction(&Symbol::from("AAPL"), "test_strategy").unwrap();

    assert_eq!(result.raw_kelly_fraction, 0.0, "Negative edge should be filtered to 0");
    assert!(!result.use_kelly, "Should not use Kelly for losing strategy");
}

#[tokio::test]
async fn test_kelly_100_percent_win_rate() {
    let config = create_test_kelly_config();
    let sizer = KellySizer::new(config);

    // Perfect win rate (edge case)
    for _ in 0..30 {
        let outcome = create_test_outcome("AAPL", "test_strategy", 100.0, true);
        sizer.add_trade_outcome(outcome).unwrap();
    }

    let result = sizer.calculate_kelly_fraction(&Symbol::from("AAPL"), "test_strategy").unwrap();

    assert_eq!(result.win_rate, 1.0);
    assert!(result.raw_kelly_fraction > 0.0);
    assert!(result.adjusted_kelly_fraction <= 0.10, "Should be capped at max_kelly_fraction");
}

#[tokio::test]
async fn test_kelly_0_percent_win_rate() {
    let config = create_test_kelly_config();
    let sizer = KellySizer::new(config);

    // Zero win rate (all losses)
    for _ in 0..20 {
        let outcome = create_test_outcome("AAPL", "test_strategy", -50.0, false);
        sizer.add_trade_outcome(outcome).unwrap();
    }

    let result = sizer.calculate_kelly_fraction(&Symbol::from("AAPL"), "test_strategy").unwrap();

    assert_eq!(result.win_rate, 0.0);
    assert_eq!(result.raw_kelly_fraction, 0.0);
    assert!(!result.use_kelly);
}

// ============================================================================
// Fractional Kelly Tests
// ============================================================================

#[tokio::test]
async fn test_kelly_half_kelly_application() {
    let mut config = create_test_kelly_config();
    config.fractional_kelly = 0.5; // Half-Kelly
    let sizer = KellySizer::new(config);

    // Create profitable strategy
    for _ in 0..30 {
        let outcome = create_test_outcome("AAPL", "test_strategy", 100.0, true);
        sizer.add_trade_outcome(outcome).unwrap();
    }
    for _ in 0..10 {
        let outcome = create_test_outcome("AAPL", "test_strategy", -50.0, false);
        sizer.add_trade_outcome(outcome).unwrap();
    }

    let result = sizer.calculate_kelly_fraction(&Symbol::from("AAPL"), "test_strategy").unwrap();

    // Adjusted should be approximately half of raw (accounting for caps)
    assert!(result.adjusted_kelly_fraction <= result.raw_kelly_fraction);
}

#[tokio::test]
async fn test_kelly_max_fraction_cap() {
    let mut config = create_test_kelly_config();
    config.max_kelly_fraction = 0.05; // 5% max
    let sizer = KellySizer::new(config);

    // Very profitable strategy that would suggest high Kelly
    for _ in 0..35 {
        let outcome = create_test_outcome("AAPL", "test_strategy", 200.0, true);
        sizer.add_trade_outcome(outcome).unwrap();
    }
    for _ in 0..5 {
        let outcome = create_test_outcome("AAPL", "test_strategy", -20.0, false);
        sizer.add_trade_outcome(outcome).unwrap();
    }

    let result = sizer.calculate_kelly_fraction(&Symbol::from("AAPL"), "test_strategy").unwrap();

    assert!(result.adjusted_kelly_fraction <= 0.05, "Should be capped at 5%");
    assert!(result.raw_kelly_fraction > result.adjusted_kelly_fraction, "Raw should exceed adjusted");
}

#[tokio::test]
async fn test_kelly_min_fraction_floor() {
    let mut config = create_test_kelly_config();
    config.min_kelly_fraction = 0.02; // 2% min
    let sizer = KellySizer::new(config);

    // Marginally profitable strategy
    for _ in 0..11 {
        let outcome = create_test_outcome("AAPL", "test_strategy", 10.0, true);
        sizer.add_trade_outcome(outcome).unwrap();
    }
    for _ in 0..9 {
        let outcome = create_test_outcome("AAPL", "test_strategy", -9.0, false);
        sizer.add_trade_outcome(outcome).unwrap();
    }

    let result = sizer.calculate_kelly_fraction(&Symbol::from("AAPL"), "test_strategy").unwrap();

    if result.use_kelly {
        assert!(result.adjusted_kelly_fraction >= 0.02, "Should meet minimum floor");
    }
}

// ============================================================================
// Position Sizing Tests
// ============================================================================

#[tokio::test]
async fn test_position_size_calculation() {
    let config = create_test_kelly_config();
    let sizer = KellySizer::new(config);

    // Add profitable history
    for _ in 0..20 {
        let outcome = create_test_outcome("AAPL", "test_strategy", 50.0, true);
        sizer.add_trade_outcome(outcome).unwrap();
    }
    for _ in 0..10 {
        let outcome = create_test_outcome("AAPL", "test_strategy", -30.0, false);
        sizer.add_trade_outcome(outcome).unwrap();
    }

    let capital = Price::from_f64(100000.0).unwrap();
    let entry_price = Price::from_f64(150.0).unwrap();

    let shares = sizer.get_position_size(
        &Symbol::from("AAPL"),
        "test_strategy",
        capital,
        entry_price,
    ).unwrap();

    assert!(shares > Price::ZERO);
    assert!(shares.to_f64() * entry_price.to_f64() < capital.to_f64());
}

#[tokio::test]
async fn test_position_size_zero_entry_price_error() {
    let config = create_test_kelly_config();
    let sizer = KellySizer::new(config);

    // Add history
    for _ in 0..20 {
        let outcome = create_test_outcome("AAPL", "test_strategy", 50.0, true);
        sizer.add_trade_outcome(outcome).unwrap();
    }

    let capital = Price::from_f64(100000.0).unwrap();
    let result = sizer.get_position_size(
        &Symbol::from("AAPL"),
        "test_strategy",
        capital,
        Price::ZERO,
    );

    assert!(result.is_err(), "Should reject zero entry price");
}

// ============================================================================
// Confidence Calculation Tests
// ============================================================================

#[tokio::test]
async fn test_kelly_confidence_with_small_sample() {
    let config = create_test_kelly_config();
    let sizer = KellySizer::new(config);

    // Small sample (20 trades)
    for _ in 0..12 {
        let outcome = create_test_outcome("AAPL", "test_strategy", 50.0, true);
        sizer.add_trade_outcome(outcome).unwrap();
    }
    for _ in 0..8 {
        let outcome = create_test_outcome("AAPL", "test_strategy", -30.0, false);
        sizer.add_trade_outcome(outcome).unwrap();
    }

    let result = sizer.calculate_kelly_fraction(&Symbol::from("AAPL"), "test_strategy").unwrap();

    assert!(result.confidence < 0.7, "Small sample should have lower confidence");
    assert!(!result.use_kelly, "Should not use Kelly with low confidence");
}

#[tokio::test]
async fn test_kelly_confidence_with_large_sample() {
    let config = create_test_kelly_config();
    let sizer = KellySizer::new(config);

    // Large sample (100+ trades)
    for _ in 0..60 {
        let outcome = create_test_outcome("AAPL", "test_strategy", 50.0, true);
        sizer.add_trade_outcome(outcome).unwrap();
    }
    for _ in 0..40 {
        let outcome = create_test_outcome("AAPL", "test_strategy", -30.0, false);
        sizer.add_trade_outcome(outcome).unwrap();
    }

    let result = sizer.calculate_kelly_fraction(&Symbol::from("AAPL"), "test_strategy").unwrap();

    assert!(result.confidence > 0.7, "Large sample should have higher confidence");
    assert!(result.use_kelly, "Should use Kelly with high confidence");
}

// ============================================================================
// Multi-Strategy Tests
// ============================================================================

#[tokio::test]
async fn test_kelly_multiple_strategies_same_symbol() {
    let config = create_test_kelly_config();
    let sizer = KellySizer::new(config);

    // Strategy A: High win rate
    for _ in 0..15 {
        let outcome = create_test_outcome("AAPL", "strategy_a", 60.0, true);
        sizer.add_trade_outcome(outcome).unwrap();
    }
    for _ in 0..5 {
        let outcome = create_test_outcome("AAPL", "strategy_a", -30.0, false);
        sizer.add_trade_outcome(outcome).unwrap();
    }

    // Strategy B: Lower win rate
    for _ in 0..8 {
        let outcome = create_test_outcome("AAPL", "strategy_b", 40.0, true);
        sizer.add_trade_outcome(outcome).unwrap();
    }
    for _ in 0..12 {
        let outcome = create_test_outcome("AAPL", "strategy_b", -25.0, false);
        sizer.add_trade_outcome(outcome).unwrap();
    }

    let result_a = sizer.calculate_kelly_fraction(&Symbol::from("AAPL"), "strategy_a").unwrap();
    let result_b = sizer.calculate_kelly_fraction(&Symbol::from("AAPL"), "strategy_b").unwrap();

    assert!(result_a.win_rate > result_b.win_rate);
    assert!(result_a.raw_kelly_fraction > result_b.raw_kelly_fraction);
}

// ============================================================================
// Trade History Management Tests
// ============================================================================

#[tokio::test]
async fn test_kelly_history_pruning() {
    let mut config = create_test_kelly_config();
    config.lookback_periods = 10; // Small window
    let sizer = KellySizer::new(config);

    // Add more trades than lookback period
    for i in 0..30 {
        let win = i % 2 == 0;
        let outcome = create_test_outcome("AAPL", "test_strategy", if win { 50.0 } else { -30.0 }, win);
        sizer.add_trade_outcome(outcome).unwrap();
    }

    let history = sizer.get_trade_history(&Symbol::from("AAPL"), "test_strategy");

    // Should keep double the lookback period
    assert!(history.len() <= 20, "Should prune old trades");
}

#[tokio::test]
async fn test_kelly_clear_history() {
    let config = create_test_kelly_config();
    let sizer = KellySizer::new(config);

    // Add trades
    for _ in 0..20 {
        let outcome = create_test_outcome("AAPL", "test_strategy", 50.0, true);
        sizer.add_trade_outcome(outcome).unwrap();
    }

    sizer.clear_history();

    let result = sizer.calculate_kelly_fraction(&Symbol::from("AAPL"), "test_strategy");
    assert!(result.is_err(), "Should fail after clearing history");
}

#[tokio::test]
async fn test_kelly_statistics_summary() {
    let config = create_test_kelly_config();
    let sizer = KellySizer::new(config);

    // Add trades for multiple symbols
    for _ in 0..20 {
        sizer.add_trade_outcome(create_test_outcome("AAPL", "strategy_a", 50.0, true)).unwrap();
    }
    for _ in 0..20 {
        sizer.add_trade_outcome(create_test_outcome("MSFT", "strategy_a", 40.0, true)).unwrap();
    }

    let stats = sizer.get_kelly_statistics();

    assert!(stats.len() >= 2, "Should have statistics for multiple symbol-strategy pairs");
}
