//! Position Limit Enforcement for PPO
//!
//! Provides risk management constraints to prevent excessive position sizes
//! and maintain minimum cash reserves.
//!
//! # Risk Management Rules
//! 1. **Position Limits**: Prevent position from exceeding `max_position` (e.g., ±2.0)
//! 2. **Cash Reserves**: Maintain minimum cash percentage (e.g., 20%)
//! 3. **Risk Reduction**: Always allow actions that reduce risk (move towards zero)

/// Enforce position limit constraint
///
/// Checks if a proposed position change would violate the maximum position limit.
/// Always allows risk-reducing actions (moves towards zero position).
///
/// # Arguments
///
/// * `current_position` - Current portfolio position (signed)
/// * `proposed_delta` - Proposed change in position (can be positive or negative)
/// * `max_position` - Maximum allowed position magnitude (absolute value)
///
/// # Returns
///
/// - `true` if action is allowed (within limits or reduces risk)
/// - `false` if action would exceed position limits
///
/// # Example
///
/// ```rust
/// use ml::ppo::position_limits::enforce_position_limit;
///
/// let max_position = 2.0;
///
/// // Within limits
/// assert!(enforce_position_limit(1.0, 0.5, max_position)); // 1.0 + 0.5 = 1.5 (OK)
///
/// // Exceeds limits
/// assert!(!enforce_position_limit(1.8, 0.3, max_position)); // 1.8 + 0.3 = 2.1 (BLOCKED)
///
/// // Risk reduction (always allowed)
/// assert!(enforce_position_limit(2.5, -0.5, max_position)); // Reducing position (OK)
/// ```
///
/// # Risk Management
///
/// - Position changes that reduce risk are always allowed (e.g., reducing from 2.5 to 2.0)
/// - Zero delta (hold) is always allowed
/// - Position limit is enforced on absolute value: |`new_position`| <= `max_position`
pub fn enforce_position_limit(
    current_position: f64,
    proposed_delta: f64,
    max_position: f64,
) -> bool {
    // Calculate new position after applying delta
    let new_position = current_position + proposed_delta;

    // Always allow risk reduction (moving towards zero)
    if new_position.abs() < current_position.abs() {
        return true;
    }

    // Allow if new position is within limits
    new_position.abs() <= max_position
}

/// Enforce minimum cash reserve constraint
///
/// Checks if current cash reserves meet the minimum required percentage.
/// Helps prevent over-leveraging and ensures liquidity for risk management.
///
/// # Arguments
///
/// * `current_cash` - Current cash balance in dollars
/// * `total_portfolio` - Total portfolio value in dollars (cash + positions)
/// * `min_reserve_pct` - Minimum required cash reserve as percentage (0.0 to 1.0)
///
/// # Returns
///
/// - `true` if cash reserve meets minimum requirement
/// - `false` if cash reserve is below minimum
///
/// # Example
///
/// ```rust
/// use ml::ppo::position_limits::enforce_cash_reserve;
///
/// let min_reserve = 0.20; // 20% minimum
///
/// // Sufficient cash (30%)
/// assert!(enforce_cash_reserve(30_000.0, 100_000.0, min_reserve));
///
/// // Insufficient cash (15%)
/// assert!(!enforce_cash_reserve(15_000.0, 100_000.0, min_reserve));
/// ```
///
/// # Edge Cases
///
/// - Zero portfolio: Returns true (no trading happening)
/// - Negative cash: Returns false (margin/leverage scenario - blocked)
/// - 100% cash: Returns true (fully liquid)
pub fn enforce_cash_reserve(current_cash: f64, total_portfolio: f64, min_reserve_pct: f64) -> bool {
    // Handle zero portfolio edge case (no trading)
    if total_portfolio == 0.0 {
        return true;
    }

    // Negative cash is always blocked (margin/leverage)
    if current_cash < 0.0 {
        return false;
    }

    // Calculate current cash reserve percentage
    let cash_reserve_pct = current_cash / total_portfolio;

    // Check if meets minimum requirement
    cash_reserve_pct >= min_reserve_pct
}

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

    #[test]
    fn test_enforce_position_limit_within_bounds() {
        let max_position = 2.0;

        // Long direction
        assert!(enforce_position_limit(1.0, 0.5, max_position));
        assert!(enforce_position_limit(0.0, 1.5, max_position));

        // Short direction
        assert!(enforce_position_limit(-1.0, -0.5, max_position));
        assert!(enforce_position_limit(0.0, -1.5, max_position));

        // Exactly at limit
        assert!(enforce_position_limit(1.5, 0.5, max_position));
        assert!(enforce_position_limit(-1.5, -0.5, max_position));
    }

    #[test]
    fn test_enforce_position_limit_exceeds_bounds() {
        let max_position = 2.0;

        // Long exceeds
        assert!(!enforce_position_limit(1.8, 0.3, max_position)); // 2.1 > 2.0

        // Short exceeds
        assert!(!enforce_position_limit(-1.5, -0.6, max_position)); // -2.1 < -2.0

        // From zero exceeds
        assert!(!enforce_position_limit(0.0, 2.5, max_position));
        assert!(!enforce_position_limit(0.0, -2.5, max_position));
    }

    #[test]
    fn test_enforce_position_limit_risk_reduction() {
        let max_position = 2.0;

        // Reducing from over-limit (always allowed)
        assert!(enforce_position_limit(2.5, -0.5, max_position)); // 2.5 -> 2.0

        // Reducing from within limit
        assert!(enforce_position_limit(1.5, -0.5, max_position)); // 1.5 -> 1.0

        // Moving towards zero from negative
        assert!(enforce_position_limit(-1.5, 0.5, max_position)); // -1.5 -> -1.0
    }

    #[test]
    fn test_enforce_position_limit_zero_delta() {
        let max_position = 2.0;

        // Zero delta (hold) always allowed
        assert!(enforce_position_limit(1.9, 0.0, max_position));
        assert!(enforce_position_limit(-1.9, 0.0, max_position));
        assert!(enforce_position_limit(0.0, 0.0, max_position));
    }

    #[test]
    fn test_enforce_cash_reserve_sufficient() {
        let min_reserve = 0.20; // 20%

        // 30% cash (above minimum)
        assert!(enforce_cash_reserve(30_000.0, 100_000.0, min_reserve));

        // Exactly at minimum
        assert!(enforce_cash_reserve(20_000.0, 100_000.0, min_reserve));

        // 100% cash
        assert!(enforce_cash_reserve(100_000.0, 100_000.0, min_reserve));
    }

    #[test]
    fn test_enforce_cash_reserve_insufficient() {
        let min_reserve = 0.20; // 20%

        // 15% cash (below minimum)
        assert!(!enforce_cash_reserve(15_000.0, 100_000.0, min_reserve));

        // 1% cash (far below)
        assert!(!enforce_cash_reserve(1_000.0, 100_000.0, min_reserve));
    }

    #[test]
    fn test_enforce_cash_reserve_edge_cases() {
        let min_reserve = 0.20;

        // Zero portfolio (allowed)
        assert!(enforce_cash_reserve(0.0, 0.0, min_reserve));

        // Negative cash (blocked)
        assert!(!enforce_cash_reserve(-10_000.0, 100_000.0, min_reserve));
    }

    #[test]
    fn test_enforce_cash_reserve_different_thresholds() {
        let portfolio = 50_000.0;

        // 10% threshold
        let cash_5pct = 2_500.0;
        let cash_12pct = 6_000.0;
        assert!(!enforce_cash_reserve(cash_5pct, portfolio, 0.10));
        assert!(enforce_cash_reserve(cash_12pct, portfolio, 0.10));

        // 30% threshold
        let cash_25pct = 12_500.0;
        let cash_35pct = 17_500.0;
        assert!(!enforce_cash_reserve(cash_25pct, portfolio, 0.30));
        assert!(enforce_cash_reserve(cash_35pct, portfolio, 0.30));
    }

    #[test]
    fn test_enforce_position_limit_fractional_max() {
        let max_position = 0.6;

        // Within limit
        assert!(enforce_position_limit(0.3, 0.2, max_position)); // 0.5 <= 0.6

        // Exceeds limit
        assert!(!enforce_position_limit(0.5, 0.15, max_position)); // 0.65 > 0.6
    }
}
