//! Kelly Criterion Position Sizing Implementation
//!
//! Implements the Kelly Criterion for optimal position sizing in trading.
//! The Kelly Criterion determines the optimal fraction of capital to risk
//! on each trade based on the probability of success and the risk/reward ratio.

// #![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic)] // COMMENTED: Crate-level allows applied

use chrono::{DateTime, Utc};
use rust_decimal::prelude::ToPrimitive;
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use tracing::{debug, info};

use crate::error::{RiskError, RiskResult};
use common::types::{Price, Symbol};
use config::structures::KellyConfig;

// REMOVED: KellyConfig is now imported from config crate
// Use: config::KellyConfig instead of local definition
// Default implementation is provided by the config crate

/// Historical trade outcome for Kelly calculation
///
/// Records the complete details of a completed trade for use in calculating
/// optimal position sizes using the Kelly Criterion. Each trade outcome
/// contributes to the statistical analysis of win rates and profit/loss ratios.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TradeOutcome {
    /// Symbol that was traded
    pub symbol: Symbol,
    /// Strategy identifier that executed this trade
    pub strategy_id: String,
    /// Price at which the position was entered
    pub entry_price: Price,
    /// Price at which the position was exited
    pub exit_price: Price,
    /// Quantity of shares/contracts traded
    pub quantity: Price,
    /// Realized profit or loss from this trade (can be negative for losses)
    pub profit_loss: Decimal,
    /// Whether this trade was profitable (true) or a loss (false)
    pub win: bool,
    /// UTC timestamp when this trade was executed
    pub trade_date: DateTime<Utc>,
}

/// Kelly fraction calculation result
///
/// Contains the complete results of a Kelly Criterion calculation including
/// the raw and adjusted Kelly fractions, confidence metrics, and statistical
/// data used in the calculation.
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct KellyResult {
    /// Symbol for which the Kelly fraction was calculated
    pub symbol: Symbol,
    /// Strategy identifier used in the calculation
    pub strategy_id: String,
    /// Raw Kelly fraction (can be negative)
    pub raw_kelly_fraction: f64,
    /// Adjusted Kelly fraction (capped and floored)
    pub adjusted_kelly_fraction: f64,
    /// Confidence in the calculation (0.0-1.0)
    pub confidence: f64,
    /// Win rate from historical data
    pub win_rate: f64,
    /// Average win amount
    pub average_win: f64,
    /// Average loss amount
    pub average_loss: f64,
    /// Number of trades in sample
    pub sample_size: usize,
    /// Whether to use Kelly sizing for this trade
    pub use_kelly: bool,
    /// Recommended position size as fraction of capital
    pub position_fraction: f64,
}

/// Kelly Criterion Position Sizer
///
/// Implements the Kelly Criterion for optimal position sizing based on historical
/// trade outcomes. Maintains a rolling history of trades and calculates optimal
/// position fractions for each symbol-strategy combination.
pub struct KellySizer {
    /// Configuration parameters for Kelly calculations
    config: KellyConfig,
    /// Historical trade outcomes by symbol and strategy
    trade_history: Arc<dashmap::DashMap<(Symbol, String), Vec<TradeOutcome>>>,
}

impl KellySizer {
    /// Create new Kelly sizer with configuration
    #[must_use]
    pub fn new(config: KellyConfig) -> Self {
        Self {
            config,
            trade_history: Arc::new(dashmap::DashMap::new()),
        }
    }

    /// Add a trade outcome to history for Kelly calculation
    pub fn add_trade_outcome(&self, outcome: TradeOutcome) -> RiskResult<()> {
        let key = (outcome.symbol.clone(), outcome.strategy_id.clone());

        let mut entry = self.trade_history.entry(key).or_default();
        entry.push(outcome.clone());

        // Keep only the last N trades for calculation
        if entry.len() > self.config.lookback_periods * 2 {
            let drain_count = entry.len() - self.config.lookback_periods;
            entry.drain(0..drain_count);
        }

        debug!(
            "Added trade outcome for {} ({}): P&L = {}",
            outcome.symbol, outcome.strategy_id, outcome.profit_loss
        );

        Ok(())
    }

    /// Calculate Kelly fraction for a given symbol and strategy
    pub fn calculate_kelly_fraction(
        &self,
        symbol: &Symbol,
        strategy_id: &str,
    ) -> RiskResult<KellyResult> {
        let key = (symbol.clone(), strategy_id.to_owned());

        // Get historical trades
        let trades = self
            .trade_history
            .get(&key)
            .map(|entry| entry.clone())
            .unwrap_or_default();

        if trades.len() < 10 {
            // CRITICAL: NEVER use default sizing when insufficient data
            // Kelly sizing requires statistical confidence - defaults mask lack of backtesting
            return Err(RiskError::DataUnavailable {
                resource: "trade_history".to_owned(),
                reason: format!(
                    "Insufficient trade history for Kelly calculation: {} trades (minimum 10 required) for symbol {} strategy {}",
                    trades.len(), symbol, strategy_id
                ),
            });
        }

        // Calculate win rate and average win/loss
        let total_trades = trades.len();
        let wins: Vec<&TradeOutcome> = trades.iter().filter(|t| t.win).collect();
        let losses: Vec<&TradeOutcome> = trades.iter().filter(|t| !t.win).collect();

        let win_rate = wins.len() as f64 / total_trades as f64;
        let loss_rate = losses.len() as f64 / total_trades as f64;

        // Calculate average win and loss amounts
        let average_win = if wins.is_empty() {
            0.0
        } else {
            let sum: Decimal = wins.iter().map(|t| t.profit_loss).sum();
            sum.to_f64().unwrap_or(0.0) / wins.len() as f64
        };

        let average_loss = if losses.is_empty() {
            0.0
        } else {
            let sum: Decimal = losses.iter().map(|t| t.profit_loss.abs()).sum();
            sum.to_f64().unwrap_or(0.0) / losses.len() as f64
        };

        // Calculate Kelly fraction: f* = (bp - q) / b
        // where b = odds received on win (average_win / average_loss)
        //       p = probability of winning
        //       q = probability of losing (1 - p)
        let kelly_fraction = if average_loss > 0.0 && average_win > 0.0 && win_rate > 0.0 {
            let b = average_win / average_loss; // Odds ratio
            let p = win_rate;
            let q = loss_rate;

            debug!(
                "Kelly calculation: avg_win={}, avg_loss={}, b={}, p={}, q={}",
                average_win, average_loss, b, p, q
            );

            // Ensure Kelly fraction is positive for profitable strategies
            let raw_kelly = (b * p - q) / b;
            debug!("Raw Kelly before filtering: {}", raw_kelly);

            if raw_kelly > 0.0 {
                raw_kelly
            } else {
                0.0 // Don't use negative Kelly fractions
            }
        } else {
            debug!(
                "Kelly calculation skipped: avg_win={}, avg_loss={}, win_rate={}",
                average_win, average_loss, win_rate
            );
            0.0
        };

        // Calculate confidence based on sample size and win rate consistency
        let confidence = self.calculate_confidence(total_trades, win_rate);

        // Determine if we should use Kelly sizing
        let use_kelly = self.config.enabled
            && confidence >= self.config.confidence_threshold
            && kelly_fraction > 0.0
            && total_trades >= 20;

        // Apply fractional Kelly and caps
        let adjusted_kelly = if use_kelly {
            let fractional_kelly = kelly_fraction * self.config.fractional_kelly;
            fractional_kelly
                .max(self.config.min_kelly_fraction)
                .min(self.config.max_kelly_fraction)
        } else {
            self.config.default_position_fraction
        };

        let result = KellyResult {
            symbol: symbol.clone(),
            strategy_id: strategy_id.to_owned(),
            raw_kelly_fraction: kelly_fraction,
            adjusted_kelly_fraction: adjusted_kelly,
            confidence,
            win_rate,
            average_win,
            average_loss,
            sample_size: total_trades,
            use_kelly,
            position_fraction: adjusted_kelly,
        };

        info!(
            "Kelly calculation for {} ({}): fraction={:.3}, confidence={:.2}, win_rate={:.2}",
            symbol, strategy_id, adjusted_kelly, confidence, win_rate
        );

        Ok(result)
    }

    /// Calculate confidence in Kelly fraction based on sample size and consistency
    fn calculate_confidence(&self, sample_size: usize, win_rate: f64) -> f64 {
        // Sample size confidence (larger samples = higher confidence)
        let size_confidence = (sample_size as f64 / 100.0).min(1.0);

        // Win rate confidence (avoid extreme win rates which may be overfitting)
        let rate_confidence = if (0.3..=0.7).contains(&win_rate) {
            1.0 // Reasonable win rates
        } else if (0.2..=0.8).contains(&win_rate) {
            0.8 // Slightly extreme but acceptable
        } else {
            0.5 // Very extreme win rates - lower confidence
        };

        // Combined confidence
        (size_confidence * rate_confidence).min(1.0)
    }

    /// Get recommended position size for a trade
    pub fn get_position_size(
        &self,
        symbol: &Symbol,
        strategy_id: &str,
        capital: Price,
        entry_price: Price,
    ) -> RiskResult<Price> {
        let kelly_result = self.calculate_kelly_fraction(symbol, strategy_id)?;

        let position_fraction = Price::from_f64(kelly_result.position_fraction).map_err(|_| {
            RiskError::ValidationError {
                message: "Invalid position fraction for position sizing".to_owned(),
            }
        })?;

        let position_value =
            (capital * position_fraction).map_err(|_| RiskError::ValidationError {
                message: "Failed to calculate position value".to_owned(),
            })?;

        if entry_price > Price::ZERO {
            let shares = (position_value / entry_price.to_f64()).map_err(|e| {
                RiskError::ValidationError {
                    message: format!("Failed to calculate shares: {e:?}"),
                }
            })?;
            Ok(shares)
        } else {
            Err(RiskError::ValidationError {
                message: "Invalid entry price for position sizing".to_owned(),
            })
        }
    }

    /// Update configuration
    pub fn update_config(&mut self, new_config: KellyConfig) {
        self.config = new_config;
        info!("Kelly sizing configuration updated");
    }

    /// Get current configuration
    #[must_use]
    pub const fn get_config(&self) -> &KellyConfig {
        &self.config
    }

    /// Get trade history for a symbol and strategy
    #[must_use]
    pub fn get_trade_history(&self, symbol: &Symbol, strategy_id: &str) -> Vec<TradeOutcome> {
        let key = (symbol.clone(), strategy_id.to_owned());
        self.trade_history
            .get(&key)
            .map(|entry| entry.clone())
            .unwrap_or_default()
    }

    /// Clear trade history (useful for testing or reset)
    pub fn clear_history(&self) {
        self.trade_history.clear();
        info!("Kelly sizer trade history cleared");
    }

    /// Get Kelly statistics for all tracked symbols and strategies
    #[must_use]
    pub fn get_kelly_statistics(&self) -> HashMap<(Symbol, String), KellyResult> {
        let mut stats = HashMap::new();

        for entry in self.trade_history.iter() {
            let (symbol, strategy_id) = entry.key();
            if let Ok(kelly_result) = self.calculate_kelly_fraction(symbol, strategy_id) {
                stats.insert((symbol.clone(), strategy_id.to_string()), kelly_result);
            }
        }

        stats
    }
}

#[cfg(test)]
#[allow(clippy::float_cmp, clippy::str_to_string, clippy::use_debug)]
mod tests {
    use super::*;
    use chrono::Utc;

    fn create_test_outcome(
        symbol: &str,
        strategy_id: &str,
        profit_loss: f64,
        win: bool,
    ) -> TradeOutcome {
        use rust_decimal::prelude::FromPrimitive;

        TradeOutcome {
            symbol: symbol.to_string().into(),
            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(),
        }
    }

    #[tokio::test]
    async fn test_kelly_calculation_insufficient_data() {
        let config = KellyConfig::default();
        let sizer = KellySizer::new(config);

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

        // Should return error with insufficient data (< 10 trades)
        assert!(
            result.is_err(),
            "Kelly calculation should fail with insufficient data"
        );

        // Verify error message contains expected information
        if let Err(RiskError::DataUnavailable { resource, reason }) = result {
            assert_eq!(resource, "trade_history");
            assert!(reason.contains("Insufficient trade history"));
            assert!(reason.contains("0 trades"));
            assert!(reason.contains("minimum 10 required"));
        } else {
            panic!("Expected DataUnavailable error");
        }
    }

    #[tokio::test]
    async fn test_kelly_calculation_with_history() {
        let config = KellyConfig::default();
        let sizer = KellySizer::new(config);

        // Add winning trades
        for _ in 0..15 {
            let outcome = create_test_outcome("AAPL", "test_strategy", 50.0, true);
            let result = sizer.add_trade_outcome(outcome);
            assert!(
                result.is_ok(),
                "Adding trade outcome should not fail in test: {:?}",
                result.err()
            );
        }

        // Add losing trades
        for _ in 0..10 {
            let outcome = create_test_outcome("AAPL", "test_strategy", -30.0, false);
            let result = sizer.add_trade_outcome(outcome);
            assert!(
                result.is_ok(),
                "Adding trade outcome should not fail in test: {:?}",
                result.err()
            );
        }

        let result =
            sizer.calculate_kelly_fraction(&Symbol::from("AAPL".to_string()), "test_strategy");
        assert!(
            result.is_ok(),
            "Kelly calculation should not fail with sufficient data: {:?}",
            result.err()
        );
        let result = result.unwrap_or_else(|e| {
            eprintln!(
                "Warning: Kelly calculation failed with sufficient data: {:?}",
                e
            );
            KellyResult::default() // Use default fallback instead of expect
        });

        assert_eq!(result.sample_size, 25);
        assert_eq!(result.win_rate, 0.6); // 15/25
        assert!(result.raw_kelly_fraction > 0.0);
        assert!(result.position_fraction > 0.0);
    }

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

        // Add some trade history
        for _ in 0..20 {
            let outcome = create_test_outcome("AAPL", "test_strategy", 25.0, true);
            let result = sizer.add_trade_outcome(outcome);
            assert!(
                result.is_ok(),
                "Adding trade outcome should not fail in test: {:?}",
                result.err()
            );
        }

        for _ in 0..10 {
            let outcome = create_test_outcome("AAPL", "test_strategy", -20.0, false);
            let result = sizer.add_trade_outcome(outcome);
            assert!(
                result.is_ok(),
                "Adding trade outcome should not fail in test: {:?}",
                result.err()
            );
        }

        let capital = Price::from_f64(100000.0).unwrap_or(Price::ZERO); // $100k capital
        let entry_price = Price::from_f64(150.0).unwrap_or(Price::ZERO); // $150 per share

        let symbol = Symbol::from("AAPL");
        let position_size = sizer.get_position_size(&symbol, "test_strategy", capital, entry_price);
        assert!(
            position_size.is_ok(),
            "Position size calculation should not fail with valid inputs: {:?}",
            position_size.err()
        );
        let position_size = position_size.unwrap_or_else(|e| {
            eprintln!("Warning: Position size calculation failed in test: {:?}", e);
            Price::ZERO // Use zero fallback instead of panic
        });

        assert!(position_size > Price::ZERO);
        assert!(position_size < capital); // Position should be less than total capital
    }

    #[tokio::test]
    async fn test_kelly_fraction_caps() {
        let mut config = KellyConfig::default();
        config.max_kelly_fraction = 0.1; // Cap at 10%
        config.min_kelly_fraction = 0.01; // Floor at 1%

        let sizer = KellySizer::new(config);

        // Add very profitable trades to generate high Kelly fraction
        for _ in 0..30 {
            let outcome = create_test_outcome("AAPL", "test_strategy", 100.0, true);
            let result = sizer.add_trade_outcome(outcome);
            assert!(
                result.is_ok(),
                "Adding trade outcome should not fail in test: {:?}",
                result.err()
            );
        }

        // Add few small losses
        for _ in 0..5 {
            let outcome = create_test_outcome("AAPL", "test_strategy", -10.0, false);
            let result = sizer.add_trade_outcome(outcome);
            assert!(
                result.is_ok(),
                "Adding trade outcome should not fail in test: {:?}",
                result.err()
            );
        }

        let result =
            sizer.calculate_kelly_fraction(&Symbol::from("AAPL".to_string()), "test_strategy");
        assert!(
            result.is_ok(),
            "Kelly calculation should not fail with sufficient data: {:?}",
            result.err()
        );
        let result = result.unwrap_or_else(|e| {
            eprintln!("Warning: Kelly calculation failed in caps test: {:?}", e);
            KellyResult::default() // Use default fallback instead of expect
        });

        // Should be capped at max_kelly_fraction
        assert!(result.adjusted_kelly_fraction <= 0.1);
        assert!(result.raw_kelly_fraction > result.adjusted_kelly_fraction);
    }
}
