//! Performance analysis and metrics calculation for backtesting

use anyhow::Result;
use rust_decimal::{prelude::ToPrimitive, Decimal};
use serde::{Deserialize, Serialize};
use tracing::info;

use crate::strategy_engine::BacktestTrade;
use config::structures::BacktestingPerformanceConfig;

/// Comprehensive performance metrics
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PerformanceMetrics {
    /// Total return (percentage)
    pub total_return: f64,
    /// Annualized return (percentage)
    pub annualized_return: f64,
    /// Sharpe ratio
    pub sharpe_ratio: f64,
    /// Sortino ratio
    pub sortino_ratio: f64,
    /// Maximum drawdown (percentage)
    pub max_drawdown: f64,
    /// Volatility (annualized)
    pub volatility: f64,
    /// Win rate (percentage)
    pub win_rate: f64,
    /// Profit factor
    pub profit_factor: f64,
    /// Total number of trades
    pub total_trades: u64,
    /// Number of winning trades
    pub winning_trades: u64,
    /// Number of losing trades
    pub losing_trades: u64,
    /// Average winning trade
    pub avg_win: f64,
    /// Average losing trade
    pub avg_loss: f64,
    /// Largest winning trade
    pub largest_win: f64,
    /// Largest losing trade
    pub largest_loss: f64,
    /// Calmar ratio
    pub calmar_ratio: f64,
    /// Backtest duration in nanoseconds
    pub backtest_duration_nanos: i64,
    /// Additional metrics
    pub beta: Option<f64>,
    /// Alpha vs benchmark
    pub alpha: Option<f64>,
    /// Information ratio
    pub information_ratio: Option<f64>,
    /// VaR at 95% confidence
    pub var_95: Option<f64>,
    /// Expected Shortfall (CVaR)
    pub expected_shortfall: Option<f64>,
}

/// Equity curve point for visualization
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EquityCurvePoint {
    /// Timestamp
    pub timestamp: chrono::DateTime<chrono::Utc>,
    /// Portfolio equity value
    pub equity: f64,
    /// Drawdown from peak
    pub drawdown: f64,
    /// Benchmark value (if available)
    pub benchmark_equity: Option<f64>,
}

/// Drawdown period analysis
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DrawdownPeriod {
    /// Start time of drawdown
    pub start_time: chrono::DateTime<chrono::Utc>,
    /// End time of drawdown
    pub end_time: chrono::DateTime<chrono::Utc>,
    /// Peak value before drawdown
    pub peak_value: f64,
    /// Trough value during drawdown
    pub trough_value: f64,
    /// Drawdown percentage
    pub drawdown_percent: f64,
    /// Duration in days
    pub duration_days: u32,
}

/// Performance analyzer for backtesting results
#[derive(Debug)]
pub struct PerformanceAnalyzer {
    /// Configuration
    config: BacktestingPerformanceConfig,
}

impl PerformanceAnalyzer {
    /// Create a new performance analyzer
    pub fn new(config: &BacktestingPerformanceConfig) -> Result<Self> {
        info!("Initializing performance analyzer");
        Ok(Self {
            config: config.clone(),
        })
    }

    /// Calculate comprehensive performance metrics
    pub fn calculate_metrics(
        &self,
        trades: &[BacktestTrade],
        initial_capital: f64,
    ) -> PerformanceMetrics {
        info!(
            "Calculating performance metrics for {} trades",
            trades.len()
        );

        if trades.is_empty() {
            return PerformanceMetrics::default();
        }

        // Calculate basic statistics
        let total_pnl: f64 = trades.iter().filter_map(|t| t.pnl.to_f64()).sum();

        let total_return = if initial_capital > 0.0 {
            let result = total_pnl / initial_capital;
            if !result.is_finite() {
                0.0
            } else {
                result
            }
        } else {
            0.0
        };

        let winning_trades: Vec<&BacktestTrade> =
            trades.iter().filter(|t| t.pnl > Decimal::ZERO).collect();

        let losing_trades: Vec<&BacktestTrade> =
            trades.iter().filter(|t| t.pnl < Decimal::ZERO).collect();

        let win_rate = if trades.is_empty() {
            0.0
        } else {
            let result = (winning_trades.len() as f64 / trades.len() as f64) * 100.0;
            if !result.is_finite() {
                0.0
            } else {
                result
            }
        };

        // Calculate profit factor
        let gross_profit: f64 = winning_trades.iter().filter_map(|t| t.pnl.to_f64()).sum();

        let gross_loss: f64 = losing_trades
            .iter()
            .filter_map(|t| t.pnl.to_f64())
            .map(|v| v.abs())
            .sum();

        let profit_factor = if gross_loss > 0.0 {
            let result = gross_profit / gross_loss;
            if !result.is_finite() {
                info!(
                    "Float overflow in profit factor calculation: {} / {}",
                    gross_profit, gross_loss
                );
                f64::MAX
            } else {
                result
            }
        } else {
            f64::INFINITY
        };

        // Calculate average wins and losses
        let avg_win = if winning_trades.is_empty() {
            0.0
        } else {
            let result = gross_profit / winning_trades.len() as f64;
            if !result.is_finite() {
                0.0
            } else {
                result
            }
        };

        let avg_loss = if losing_trades.is_empty() {
            0.0
        } else {
            let result = -gross_loss / losing_trades.len() as f64;
            if !result.is_finite() {
                0.0
            } else {
                result
            }
        };

        // Find largest win and loss
        let largest_win = winning_trades
            .iter()
            .filter_map(|t| t.pnl.to_f64())
            .fold(0.0, f64::max);

        let largest_loss = losing_trades
            .iter()
            .filter_map(|t| t.pnl.to_f64())
            .fold(0.0, f64::min);

        // Calculate time-based metrics
        let start_time = trades
            .first()
            .map(|t| t.entry_time)
            .unwrap_or_else(chrono::Utc::now);
        let end_time = trades
            .last()
            .map(|t| t.exit_time)
            .unwrap_or_else(chrono::Utc::now);

        let duration = end_time - start_time;
        let duration_years = {
            let result = duration.num_days() as f64 / 365.25;
            if !result.is_finite() {
                0.0
            } else {
                result
            }
        };

        let annualized_return = if duration_years > 0.0 {
            let result = ((1.0 + total_return).powf(1.0 / duration_years) - 1.0) * 100.0;
            if !result.is_finite() {
                0.0
            } else {
                result
            }
        } else {
            0.0
        };

        // Calculate volatility and Sharpe ratio
        let returns: Vec<f64> = trades
            .iter()
            .filter_map(|t| t.return_percent.to_f64())
            .collect();

        let (volatility, sharpe_ratio) =
            self.calculate_volatility_and_sharpe(&returns, duration_years);

        // Calculate Sortino ratio
        let sortino_ratio = self.calculate_sortino_ratio(&returns, duration_years);

        // Calculate maximum drawdown
        let (max_drawdown, _) = self.calculate_max_drawdown(trades, initial_capital);

        // Calculate Calmar ratio
        let calmar_ratio = if max_drawdown > 0.0 {
            let result = annualized_return / (max_drawdown * 100.0);
            if !result.is_finite() {
                0.0
            } else {
                result
            }
        } else {
            0.0
        };

        // Calculate risk metrics
        let var_95 = self.calculate_var(&returns, 0.95);
        let expected_shortfall = self.calculate_expected_shortfall(&returns, 0.95);

        PerformanceMetrics {
            total_return: total_return * 100.0,
            annualized_return,
            sharpe_ratio,
            sortino_ratio,
            max_drawdown: max_drawdown * 100.0,
            volatility: volatility * 100.0,
            win_rate,
            profit_factor,
            total_trades: trades.len() as u64,
            winning_trades: winning_trades.len() as u64,
            losing_trades: losing_trades.len() as u64,
            avg_win,
            avg_loss,
            largest_win,
            largest_loss,
            calmar_ratio,
            backtest_duration_nanos: duration.num_nanoseconds().unwrap_or(0),
            // Benchmark-relative metrics require benchmark data to be passed in
            // These would be calculated as: beta = cov(returns, benchmark) / var(benchmark)
            // alpha = returns - (risk_free_rate + beta * (benchmark_returns - risk_free_rate))
            // information_ratio = (returns - benchmark) / tracking_error
            beta: None,
            alpha: None,
            information_ratio: None,
            var_95: Some(var_95),
            expected_shortfall: Some(expected_shortfall),
        }
    }

    /// Generate equity curve from trades
    pub fn generate_equity_curve(
        &self,
        trades: &[BacktestTrade],
        initial_capital: f64,
    ) -> Vec<EquityCurvePoint> {
        if trades.is_empty() {
            return Vec::new();
        }

        let mut curve = Vec::new();
        let mut running_equity = initial_capital;
        let mut peak_equity = initial_capital;

        // Add initial point
        curve.push(EquityCurvePoint {
            timestamp: trades
                .first()
                .map(|t| t.entry_time)
                .unwrap_or_else(chrono::Utc::now),
            equity: initial_capital,
            drawdown: 0.0,
            benchmark_equity: None,
        });

        // Calculate equity at each trade
        for trade in trades {
            running_equity += trade.pnl.to_f64().unwrap_or(0.0);

            if running_equity > peak_equity {
                peak_equity = running_equity;
            }

            let drawdown = if peak_equity > 0.0 {
                (peak_equity - running_equity) / peak_equity
            } else {
                0.0
            };

            curve.push(EquityCurvePoint {
                timestamp: trade.exit_time,
                equity: running_equity,
                drawdown,
                // Benchmark comparison requires an external data source (e.g., SPY or
                // ES continuous futures) aligned to the same timestamps. Until a
                // BenchmarkDataProvider trait is wired in, this field stays None.
                benchmark_equity: None,
            });
        }

        // Resample to target resolution if needed
        if curve.len() > self.config.equity_curve_resolution {
            self.resample_equity_curve(curve)
        } else {
            curve
        }
    }

    /// Identify drawdown periods
    pub fn identify_drawdown_periods(
        &self,
        equity_curve: &[EquityCurvePoint],
    ) -> Vec<DrawdownPeriod> {
        let mut periods = Vec::new();
        let mut in_drawdown = false;
        let mut drawdown_start: Option<usize> = None;
        let mut peak_value = 0.0;

        for (i, point) in equity_curve.iter().enumerate() {
            if !in_drawdown && point.drawdown > 0.0 {
                // Start of new drawdown
                in_drawdown = true;
                drawdown_start = Some(i);
                peak_value = point.equity + (point.equity * point.drawdown);
            } else if in_drawdown && point.drawdown == 0.0 {
                // End of drawdown
                if let Some(start_idx) = drawdown_start {
                    let start_point = &equity_curve[start_idx];
                    let trough_value = equity_curve[start_idx..=i]
                        .iter()
                        .map(|p| p.equity)
                        .fold(f64::INFINITY, f64::min);

                    let drawdown_percent = (peak_value - trough_value) / peak_value * 100.0;
                    let duration_days = (point.timestamp - start_point.timestamp).num_days() as u32;

                    periods.push(DrawdownPeriod {
                        start_time: start_point.timestamp,
                        end_time: point.timestamp,
                        peak_value,
                        trough_value,
                        drawdown_percent,
                        duration_days,
                    });
                }

                in_drawdown = false;
                drawdown_start = None;
            }
        }

        periods
    }

    /// Calculate volatility and Sharpe ratio
    fn calculate_volatility_and_sharpe(&self, returns: &[f64], duration_years: f64) -> (f64, f64) {
        if returns.is_empty() || duration_years <= 0.0 {
            return (0.0, 0.0);
        }

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

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

        let volatility = variance.sqrt();
        let annualized_volatility = volatility * (252.0_f64).sqrt(); // Assuming 252 trading days

        let excess_return = mean_return - self.config.risk_free_rate / 252.0; // Daily risk-free rate
        let sharpe_ratio = if annualized_volatility > 0.0 {
            excess_return * (252.0_f64).sqrt() / annualized_volatility
        } else {
            0.0
        };

        (annualized_volatility, sharpe_ratio)
    }

    /// Calculate Sortino ratio
    fn calculate_sortino_ratio(&self, returns: &[f64], duration_years: f64) -> f64 {
        if returns.is_empty() || duration_years <= 0.0 {
            return 0.0;
        }

        let mean_return = returns.iter().sum::<f64>() / returns.len() as f64;
        let target_return = self.config.risk_free_rate / 252.0; // Daily risk-free rate

        let downside_returns: Vec<f64> = returns
            .iter()
            .map(|r| {
                if *r < target_return {
                    r - target_return
                } else {
                    0.0
                }
            })
            .collect();

        let downside_variance =
            downside_returns.iter().map(|r| r.powi(2)).sum::<f64>() / downside_returns.len() as f64;

        let downside_deviation = downside_variance.sqrt();
        let annualized_downside_deviation = downside_deviation * (252.0_f64).sqrt();

        if annualized_downside_deviation > 0.0 {
            let excess_return = mean_return - target_return;
            excess_return * (252.0_f64).sqrt() / annualized_downside_deviation
        } else {
            0.0
        }
    }

    /// Calculate maximum drawdown
    fn calculate_max_drawdown(&self, trades: &[BacktestTrade], initial_capital: f64) -> (f64, f64) {
        let mut running_equity = initial_capital;
        let mut peak_equity = initial_capital;
        let mut max_drawdown = 0.0;
        let max_drawdown_duration = 0.0;

        for trade in trades {
            running_equity += trade.pnl.to_f64().unwrap_or(0.0);

            if running_equity > peak_equity {
                peak_equity = running_equity;
            }

            let current_drawdown = (peak_equity - running_equity) / peak_equity;
            if current_drawdown > max_drawdown {
                max_drawdown = current_drawdown;
            }
        }

        (max_drawdown, max_drawdown_duration)
    }

    /// Calculate Value at Risk (VaR)
    fn calculate_var(&self, returns: &[f64], confidence_level: f64) -> f64 {
        if returns.is_empty() {
            return 0.0;
        }

        let mut sorted_returns = returns.to_vec();
        sorted_returns.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));

        let index = ((1.0 - confidence_level) * sorted_returns.len() as f64) as usize;
        sorted_returns.get(index).copied().unwrap_or(0.0)
    }

    /// Calculate Expected Shortfall (Conditional VaR)
    fn calculate_expected_shortfall(&self, returns: &[f64], confidence_level: f64) -> f64 {
        let var = self.calculate_var(returns, confidence_level);

        let tail_returns: Vec<f64> = returns.iter().filter(|&&r| r <= var).copied().collect();

        if tail_returns.is_empty() {
            0.0
        } else {
            tail_returns.iter().sum::<f64>() / tail_returns.len() as f64
        }
    }

    /// Resample equity curve to target resolution
    fn resample_equity_curve(&self, curve: Vec<EquityCurvePoint>) -> Vec<EquityCurvePoint> {
        if curve.len() <= self.config.equity_curve_resolution {
            return curve;
        }

        let mut resampled = Vec::new();
        let step = curve.len() / self.config.equity_curve_resolution;

        for i in (0..curve.len()).step_by(step) {
            resampled.push(curve[i].clone());
        }

        // Always include the last point
        if let Some(last) = curve.last() {
            if resampled.last().map(|p| p.timestamp) != Some(last.timestamp) {
                resampled.push(last.clone());
            }
        }

        resampled
    }
}

impl Default for PerformanceMetrics {
    fn default() -> Self {
        Self {
            total_return: 0.0,
            annualized_return: 0.0,
            sharpe_ratio: 0.0,
            sortino_ratio: 0.0,
            max_drawdown: 0.0,
            volatility: 0.0,
            win_rate: 0.0,
            profit_factor: 0.0,
            total_trades: 0,
            winning_trades: 0,
            losing_trades: 0,
            avg_win: 0.0,
            avg_loss: 0.0,
            largest_win: 0.0,
            largest_loss: 0.0,
            calmar_ratio: 0.0,
            backtest_duration_nanos: 0,
            beta: None,
            alpha: None,
            information_ratio: None,
            var_95: None,
            expected_shortfall: None,
        }
    }
}

impl From<PerformanceMetrics> for crate::foxhunt::tli::BacktestMetrics {
    fn from(metrics: PerformanceMetrics) -> Self {
        Self {
            total_return: metrics.total_return,
            annualized_return: metrics.annualized_return,
            sharpe_ratio: metrics.sharpe_ratio,
            sortino_ratio: metrics.sortino_ratio,
            max_drawdown: metrics.max_drawdown,
            volatility: metrics.volatility,
            win_rate: metrics.win_rate,
            profit_factor: metrics.profit_factor,
            total_trades: metrics.total_trades,
            winning_trades: metrics.winning_trades,
            losing_trades: metrics.losing_trades,
            avg_win: metrics.avg_win,
            avg_loss: metrics.avg_loss,
            largest_win: metrics.largest_win,
            largest_loss: metrics.largest_loss,
            calmar_ratio: metrics.calmar_ratio,
            backtest_duration_nanos: metrics.backtest_duration_nanos,
        }
    }
}
