//! P&L tracking with rolling Sharpe ratio and max drawdown.

use std::collections::VecDeque;

/// Tracks equity curve, computes rolling Sharpe and max drawdown.
#[derive(Debug)]
pub struct PnLTracker {
    equity_history: Vec<f64>,
    returns: VecDeque<f64>,
    window_size: usize,
    peak_equity: f64,
    max_drawdown: f64,
}

impl PnLTracker {
    pub fn new(window_size: usize) -> Self {
        Self {
            equity_history: Vec::new(),
            returns: VecDeque::with_capacity(window_size.saturating_add(1)),
            window_size,
            peak_equity: 0.0,
            max_drawdown: 0.0,
        }
    }

    /// Record a new equity value.
    pub fn record_equity(&mut self, equity: f64) {
        // Compute return from previous equity
        if let Some(&prev) = self.equity_history.last() {
            if prev.abs() > 1e-12 {
                let ret = (equity - prev) / prev;
                self.returns.push_back(ret);
                while self.returns.len() > self.window_size {
                    self.returns.pop_front();
                }
            }
        }

        self.equity_history.push(equity);

        // Track peak and drawdown
        if equity > self.peak_equity {
            self.peak_equity = equity;
        }
        if self.peak_equity > 0.0 {
            let dd = (self.peak_equity - equity) / self.peak_equity;
            if dd > self.max_drawdown {
                self.max_drawdown = dd;
            }
        }
    }

    /// Record a raw return value directly.
    pub fn record_return(&mut self, ret: f64) {
        self.returns.push_back(ret);
        while self.returns.len() > self.window_size {
            self.returns.pop_front();
        }
        // Update equity from returns
        let prev_equity = self.equity_history.last().copied().unwrap_or(100_000.0);
        let new_equity = prev_equity * (1.0 + ret);
        self.record_equity_internal(new_equity);
    }

    fn record_equity_internal(&mut self, equity: f64) {
        self.equity_history.push(equity);
        if equity > self.peak_equity {
            self.peak_equity = equity;
        }
        if self.peak_equity > 0.0 {
            let dd = (self.peak_equity - equity) / self.peak_equity;
            if dd > self.max_drawdown {
                self.max_drawdown = dd;
            }
        }
    }

    /// Rolling annualized Sharpe ratio (assumes 252 trading days).
    pub fn rolling_sharpe(&self) -> f64 {
        if self.returns.len() < 2 {
            return 0.0;
        }
        let n = self.returns.len() as f64;
        let mean: f64 = self.returns.iter().sum::<f64>() / n;
        let variance: f64 =
            self.returns.iter().map(|r| (r - mean).powi(2)).sum::<f64>() / (n - 1.0);
        let std_dev = variance.sqrt();
        if std_dev < 1e-12 {
            return 0.0;
        }
        (mean / std_dev) * 252.0_f64.sqrt()
    }

    /// Maximum drawdown as a fraction [0.0, 1.0].
    pub fn max_drawdown(&self) -> f64 {
        self.max_drawdown.clamp(0.0, 1.0)
    }

    /// Cumulative return since first equity observation.
    pub fn cumulative_return(&self) -> f64 {
        let first = self.equity_history.first().copied().unwrap_or(0.0);
        let last = self.equity_history.last().copied().unwrap_or(0.0);
        if first.abs() < 1e-12 {
            return 0.0;
        }
        (last - first) / first
    }

    pub fn num_observations(&self) -> usize {
        self.equity_history.len()
    }
}

#[cfg(test)]
#[allow(clippy::manual_range_contains)]
mod tests {
    use super::*;

    #[test]
    fn test_sharpe_positive_returns() {
        let mut tracker = PnLTracker::new(252);
        for i in 0..10 {
            tracker.record_return(0.001 + 0.0001 * (i as f64));
        }
        let sharpe = tracker.rolling_sharpe();
        assert!(sharpe.is_finite());
        assert!(
            sharpe > 0.0,
            "Positive returns should yield positive Sharpe, got {}",
            sharpe
        );
    }

    #[test]
    fn test_max_drawdown() {
        let mut tracker = PnLTracker::new(252);
        tracker.record_equity(100.0);
        tracker.record_equity(110.0); // peak
        tracker.record_equity(99.0); // drawdown: (110-99)/110 = 0.1

        let dd = tracker.max_drawdown();
        assert!(
            dd >= 0.09 && dd <= 0.11,
            "Expected ~10% drawdown, got {}",
            dd
        );
    }

    #[test]
    fn test_cumulative_return() {
        let mut tracker = PnLTracker::new(252);
        tracker.record_equity(100.0);
        tracker.record_equity(105.0);
        tracker.record_equity(110.0);

        let ret = tracker.cumulative_return();
        assert!(
            (ret - 0.10).abs() < 0.01,
            "Expected ~10% return, got {}",
            ret
        );
    }

    #[test]
    fn test_empty_tracker() {
        let tracker = PnLTracker::new(252);
        assert_eq!(tracker.rolling_sharpe(), 0.0);
        assert_eq!(tracker.max_drawdown(), 0.0);
        assert_eq!(tracker.cumulative_return(), 0.0);
    }
}
