//! Performance Metrics for DQN Backtesting

use super::engine::Trade;
use serde::{Deserialize, Serialize};

/// Comprehensive performance metrics for backtesting
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PerformanceMetrics {
    /// Total return as percentage
    pub total_return_pct: f64,
    /// Sharpe ratio (risk-adjusted return)
    pub sharpe_ratio: f64,
    /// Maximum drawdown as percentage
    pub max_drawdown_pct: f64,
    /// Win rate (percentage of profitable trades)
    pub win_rate: f64,
    /// Total number of trades executed
    pub total_trades: usize,
    /// Average trade `PnL`
    pub avg_trade_pnl: f64,
    /// Final equity value
    pub final_equity: f64,
    /// Maximum equity achieved
    pub max_equity: f64,
    /// Sortino ratio (measures return against downside deviation only)
    pub sortino_ratio: f64,
    /// Calmar ratio (measures return against maximum drawdown)
    pub calmar_ratio: f64,
    /// Value at Risk 95% (estimates potential loss at 95% confidence)
    pub var_95: f64,
    /// Conditional Value at Risk 95% (average loss beyond `VaR`, tail risk)
    pub cvar_95: f64,
    /// Beta (market correlation - 0.0 if no benchmark)
    pub beta: f64,
    /// Alpha (excess return over benchmark - 0.0 if no benchmark)
    pub alpha: f64,
    /// Information Ratio (risk-adjusted excess return - 0.0 if no benchmark)
    pub information_ratio: f64,
    /// Omega Ratio (upside vs. downside potential)
    pub omega_ratio: f64,
}

impl PerformanceMetrics {
    /// Calculate metrics from a list of completed trades
    ///
    /// # Arguments
    /// * `trades` - Vector of completed trades
    /// * `initial_capital` - Starting capital amount
    /// * `bars` - OHLCV bars for calculating returns
    ///
    /// # Returns
    /// Comprehensive performance metrics
    pub fn from_trades(trades: &[Trade], initial_capital: f32, _bars: &[OHLCVBarF32]) -> Self {
        if trades.is_empty() {
            return Self {
                total_return_pct: 0.0,
                sharpe_ratio: 0.0,
                max_drawdown_pct: 0.0,
                win_rate: 0.0,
                total_trades: 0,
                avg_trade_pnl: 0.0,
                final_equity: initial_capital as f64,
                max_equity: initial_capital as f64,
                sortino_ratio: 0.0,
                calmar_ratio: 0.0,
                var_95: 0.0,
                cvar_95: 0.0,
                beta: 0.0,
                alpha: 0.0,
                information_ratio: 0.0,
                omega_ratio: 0.0,
            };
        }

        // Calculate total PnL
        let total_pnl: f32 = trades.iter().map(|t| t.pnl).sum();
        let final_equity = (initial_capital + total_pnl) as f64;

        // Calculate total return percentage
        let total_return_pct = (total_pnl as f64 / initial_capital as f64) * 100.0;

        // Calculate win rate
        let winning_trades = trades.iter().filter(|t| t.pnl > 0.0).count();
        let win_rate = if trades.is_empty() {
            0.0
        } else {
            (winning_trades as f64 / trades.len() as f64) * 100.0
        };

        // Calculate average trade PnL
        let avg_trade_pnl = if trades.is_empty() {
            0.0
        } else {
            total_pnl as f64 / trades.len() as f64
        };

        // Calculate equity curve for drawdown and Sharpe
        let mut equity_curve = Vec::with_capacity(trades.len() + 1);
        equity_curve.push(initial_capital);

        for trade in trades {
            let last_equity = equity_curve.last().copied().unwrap_or(initial_capital);
            equity_curve.push(last_equity + trade.pnl);
        }

        // Calculate maximum drawdown
        let max_drawdown_pct = calculate_max_drawdown(&equity_curve);

        // Calculate returns for advanced risk metrics
        let returns: Vec<f64> = trades
            .iter()
            .map(|t| (t.pnl as f64 / initial_capital as f64))
            .collect();

        // Calculate Sharpe ratio from trade returns
        let sharpe_ratio = calculate_sharpe_ratio(&returns);

        // Calculate advanced risk metrics
        let sortino_ratio = calculate_sortino_ratio(&returns);
        let annualized_return = if !returns.is_empty() {
            (returns.iter().sum::<f64>() / returns.len() as f64) * 252.0
        } else {
            0.0
        };
        let calmar_ratio = calculate_calmar_ratio(annualized_return, max_drawdown_pct);
        let (var_95, cvar_95) = calculate_var_cvar(&returns, 0.05);
        let omega_ratio = calculate_omega_ratio(&returns, 0.0);
        let (beta, alpha, information_ratio) = calculate_benchmark_metrics(&returns, annualized_return);

        // Find max equity
        let max_equity = equity_curve
            .iter()
            .copied()
            .fold(f32::NEG_INFINITY, f32::max) as f64;

        Self {
            total_return_pct,
            sharpe_ratio,
            max_drawdown_pct,
            win_rate,
            total_trades: trades.len(),
            avg_trade_pnl,
            final_equity,
            max_equity,
            sortino_ratio,
            calmar_ratio,
            var_95,
            cvar_95,
            beta,
            alpha,
            information_ratio,
            omega_ratio,
        }
    }
}

/// Calculate maximum drawdown from equity curve
fn calculate_max_drawdown(equity_curve: &[f32]) -> f64 {
    if equity_curve.len() < 2 {
        return 0.0;
    }

    let mut max_equity = equity_curve[0];
    let mut max_drawdown = 0.0_f64;

    for &equity in equity_curve.iter().skip(1) {
        if equity > max_equity {
            max_equity = equity;
        }

        if max_equity > 0.0 {
            let drawdown = ((max_equity - equity) / max_equity) * 100.0;
            if drawdown > max_drawdown as f32 {
                max_drawdown = drawdown as f64;
            }
        }
    }

    // Cap at 100%: in real markets, equity cannot go below zero (margin call / liquidation).
    // Values > 100% are artifacts of backtests that don't model forced liquidation.
    max_drawdown.min(100.0)
}

/// Calculate Sharpe ratio from returns
///
/// Assumes 252 trading days per year, 0% risk-free rate
fn calculate_sharpe_ratio(returns: &[f64]) -> f64 {
    if returns.len() < 2 {
        return 0.0;
    }

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

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

    let std_dev = variance.sqrt();

    if std_dev == 0.0 {
        return 0.0;
    }

    // Annualize: sqrt(252 trades/year) assuming daily trades
    (mean_return / std_dev) * (252.0_f64).sqrt()
}

/// Calculate Sortino ratio, focusing on downside deviation
///
/// Superior to Sharpe ratio as it only penalizes downside volatility
fn calculate_sortino_ratio(returns: &[f64]) -> f64 {
    if returns.len() < 2 {
        return 0.0;
    }
    const RISK_FREE_RATE: f64 = 0.0;
    let mean_return = returns.iter().sum::<f64>() / returns.len() as f64;

    // Calculate downside deviation (only negative returns)
    let downside_returns: Vec<f64> = returns
        .iter()
        .filter(|&&r| r < RISK_FREE_RATE)
        .map(|&r| (r - RISK_FREE_RATE).powi(2))
        .collect();

    if downside_returns.is_empty() {
        // All returns are non-negative: infinite Sortino, capped at 100.0
        // to avoid distorting composite scores.
        return if mean_return > 0.0 { 100.0 } else { 0.0 };
    }

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

    if downside_deviation > 0.0 {
        let sortino = (mean_return - RISK_FREE_RATE) / downside_deviation;
        sortino * (252.0_f64).sqrt() // Annualize
    } else {
        0.0
    }
}

/// Calculate Calmar ratio (annualized return vs. max drawdown)
///
/// Measures return per unit of maximum drawdown risk
fn calculate_calmar_ratio(annualized_return: f64, max_drawdown_pct: f64) -> f64 {
    if max_drawdown_pct.abs() > 1e-9 {
        // max_drawdown_pct is a percentage (e.g., 10.0 for 10%), so divide by 100
        // annualized_return is a factor (e.g., 0.2 for 20% annual return)
        annualized_return / (max_drawdown_pct / 100.0)
    } else {
        0.0
    }
}

/// Calculate Value at Risk (`VaR`) and Conditional Value at Risk (`CVaR`)
///
/// `VaR`: Maximum expected loss at given confidence level
/// `CVaR`: Average loss beyond `VaR` threshold (tail risk)
pub fn calculate_var_cvar(returns: &[f64], confidence_level: f64) -> (f64, f64) {
    if returns.is_empty() {
        return (0.0, 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 var_idx = (sorted_returns.len() as f64 * confidence_level) as usize;
    if var_idx >= sorted_returns.len() {
        return (0.0, 0.0);
    }

    let var_95 = sorted_returns[var_idx];

    // CVaR is the average of all returns worse than VaR
    let tail_returns = &sorted_returns[..var_idx];
    let cvar_95 = if tail_returns.is_empty() {
        var_95
    } else {
        tail_returns.iter().sum::<f64>() / tail_returns.len() as f64
    };

    (var_95, cvar_95)
}

/// Calculate Omega ratio (upside vs downside probability-weighted returns)
///
/// Ratio of gains to losses above/below threshold (typically 0)
fn calculate_omega_ratio(returns: &[f64], threshold: f64) -> f64 {
    if returns.is_empty() {
        return 0.0;
    }

    let upside: f64 = returns
        .iter()
        .filter(|&&r| r > threshold)
        .map(|&r| r - threshold)
        .sum();
    let downside: f64 = returns
        .iter()
        .filter(|&&r| r < threshold)
        .map(|&r| threshold - r)
        .sum();

    if downside.abs() < 1e-9 {
        0.0 // Avoid division by zero
    } else {
        upside / downside
    }
}

/// Placeholder for benchmark metrics
///
/// Returns zeros as no benchmark is currently used
/// Future implementation would calculate correlation with market index
const fn calculate_benchmark_metrics(_returns: &[f64], _annualized_return: f64) -> (f64, f64, f64) {
    // beta, alpha, information_ratio
    (0.0, 0.0, 0.0)
}

/// Compact f32 OHLCV bar for backtesting evaluation
#[derive(Debug, Clone)]
pub struct OHLCVBarF32 {
    pub timestamp: i64,
    pub open: f32,
    pub high: f32,
    pub low: f32,
    pub close: f32,
    pub volume: f32,
}

impl Default for PerformanceMetrics {
    /// Create default (zero) performance metrics
    ///
    /// Used for dual-phase backtesting when one phase has no trades
    fn default() -> Self {
        Self {
            total_return_pct: 0.0,
            sharpe_ratio: 0.0,
            max_drawdown_pct: 0.0,
            win_rate: 0.0,
            total_trades: 0,
            avg_trade_pnl: 0.0,
            final_equity: 10000.0,
            max_equity: 10000.0,
            sortino_ratio: 0.0,
            calmar_ratio: 0.0,
            var_95: 0.0,
            cvar_95: 0.0,
            beta: 0.0,
            alpha: 0.0,
            information_ratio: 0.0,
            omega_ratio: 0.0,
        }
    }
}
