#![allow(
    clippy::assertions_on_constants,
    clippy::assertions_on_result_states,
    clippy::clone_on_copy,
    clippy::decimal_literal_representation,
    clippy::doc_markdown,
    clippy::empty_line_after_doc_comments,
    clippy::field_reassign_with_default,
    clippy::get_unwrap,
    clippy::identity_op,
    clippy::inconsistent_digit_grouping,
    clippy::indexing_slicing,
    clippy::integer_division,
    clippy::len_zero,
    clippy::let_underscore_must_use,
    clippy::manual_div_ceil,
    clippy::manual_let_else,
    clippy::manual_range_contains,
    clippy::modulo_arithmetic,
    clippy::needless_range_loop,
    clippy::non_ascii_literal,
    clippy::redundant_clone,
    clippy::shadow_reuse,
    clippy::shadow_same,
    clippy::shadow_unrelated,
    clippy::single_match_else,
    clippy::str_to_string,
    clippy::string_slice,
    clippy::tests_outside_test_module,
    clippy::too_many_lines,
    clippy::unnecessary_wraps,
    clippy::unseparated_literal_suffix,
    clippy::use_debug,
    clippy::useless_vec,
    clippy::wildcard_enum_match_arm,
    clippy::else_if_without_else,
    clippy::expect_used,
    clippy::missing_const_for_fn,
    clippy::similar_names,
    clippy::type_complexity,
    clippy::collapsible_else_if,
    clippy::doc_lazy_continuation,
    clippy::items_after_test_module,
    clippy::map_clone,
    clippy::multiple_unsafe_ops_per_block,
    clippy::unwrap_or_default,
    clippy::assign_op_pattern,
    clippy::needless_borrow,
    clippy::println_empty_string,
    clippy::unnecessary_cast,
    clippy::used_underscore_binding,
    clippy::create_dir,
    clippy::implicit_saturating_sub,
    clippy::exit,
    clippy::expect_fun_call,
    clippy::too_many_arguments,
    clippy::unnecessary_map_or,
    clippy::unwrap_used,
    dead_code,
    unused_imports,
    unused_variables,
    clippy::cloned_ref_to_slice_refs,
    clippy::neg_multiply,
    clippy::while_let_loop,
    clippy::bool_assert_comparison,
    clippy::excessive_precision,
    clippy::trivially_copy_pass_by_ref,
    clippy::op_ref,
    clippy::redundant_closure,
    clippy::unnecessary_lazy_evaluations,
    clippy::if_then_some_else_none,
    clippy::unnecessary_to_owned,
    clippy::single_component_path_imports,
)]
//! Cash Reserve Requirement Tests (P2-B Enhancement)
//!
//! Comprehensive test suite for configurable cash reserve requirement feature.
//! Tests cover:
//! - Baseline (0% reserve) - backward compatibility
//! - Conservative/Standard/Aggressive reserves (5%, 10%, 15%)
//! - Trade rejection scenarios (BUY violating reserve)
//! - Trade acceptance scenarios (BUY with sufficient cash)
//! - Sell exemption (SELL always allowed)
//! - Dynamic reserve updates (portfolio growth/shrinkage)
//! - Transaction cost integration
//! - Boundary conditions

use ml::dqn::portfolio_tracker::PortfolioTracker;
use ml::dqn::action_space::{FactoredAction, ExposureLevel, OrderType, Urgency};

/// Helper function to create PortfolioTracker with specified reserve percentage
fn create_tracker_with_reserve(reserve_pct: f64) -> PortfolioTracker {
    PortfolioTracker::new(
        100_000.0,      // initial_capital
        0.0001,         // avg_spread
        reserve_pct,    // cash_reserve_percent
    )
}

/// Test 1: Baseline (0% reserve) - backward compatibility
///
/// Verifies that 0% reserve allows trades that would drain cash to $0,
/// maintaining backward compatibility with existing behavior.
#[test]
fn test_no_reserve_baseline() {
    let mut tracker = create_tracker_with_reserve(0.0);
    
    // Initial state: $100K cash, 0 position
    assert_eq!(tracker.cash_balance(), 100_000.0);
    assert_eq!(tracker.current_position(), 0.0);
    
    // Action: BUY Long100 at $5000 with max_position=10 contracts (use smaller max for simpler version)
    // Target: 10 contracts (Long100 = 1.0 × 10)
    // Cost: 10 contracts × $5000 = $50,000
    let action = FactoredAction::new(ExposureLevel::Long100, OrderType::Market, Urgency::Normal);
    tracker.execute_action(action, 5000.0, 10.0);

    // Expected: Trade executes (0% reserve = no constraint)
    // Cash: $100K - $50K trade cost - $75 Market order fee (0.15% of $50K) = $49,925
    assert_eq!(tracker.current_position(), 10.0);
    assert_eq!(tracker.cash_balance(), 49_925.0); // Cash reduced by trade cost ($50K) + Market fee ($75)
}

/// Test 2: Conservative reserve (5%) - BUY accepted
///
/// Verifies that BUY trades execute successfully when cash after trade
/// exceeds the 5% reserve requirement.
#[test]
fn test_conservative_reserve_buy_accepted() {
    let mut tracker = create_tracker_with_reserve(5.0);
    
    // Initial: $100K cash, portfolio_value = $100K
    // Reserve required: $100K × 5% = $5K
    
    // Action: BUY Long50 at $5000 (0.5 contracts clamped to 0.5)
    // Cost: 5.0 × $5000 = $25,000
    // Cash after: $100K - $25K = $75K (> $3.75K reserve) ✅
    let action = FactoredAction::new(ExposureLevel::Long50, OrderType::Market, Urgency::Normal);
    tracker.execute_action(action, 5000.0, 10.0);
    
    // Expected: Trade executes
    assert_eq!(tracker.current_position(), 5.0);
    let cash = tracker.cash_balance();
    assert!(cash > 3_750.0, "Cash {:.2} should exceed $3.75K reserve", cash);
}

/// Test 3: Standard reserve (10%) - BUY accepted
///
/// Verifies that BUY trades execute when cash after trade exceeds 10% reserve.
#[test]
fn test_standard_reserve_buy_accepted() {
    let mut tracker = create_tracker_with_reserve(10.0);
    
    // Initial: $100K cash, portfolio_value = $100K
    // Reserve required: $100K × 10% = $10K
    
    // Action: BUY Long50 at $5000 (0.5 × 10 = 5.0 position)
    // Cost: 5.0 × $5000 = $25,000
    // Cash after: $100K - $25K = $75K (> $7.5K reserve) ✅
    let action = FactoredAction::new(ExposureLevel::Long50, OrderType::Market, Urgency::Normal);
    tracker.execute_action(action, 5000.0, 10.0);
    
    // Expected: Trade executes
    assert_eq!(tracker.current_position(), 5.0);
    let cash = tracker.cash_balance();
    assert!(cash > 7_500.0, "Cash {:.2} should exceed $7.5K reserve (10%)", cash);
}

/// Test 4: Standard reserve (10%) - BUY rejected
///
/// Verifies that BUY trades are rejected (or reduced) when they would
/// violate the 10% cash reserve requirement.
#[test]
fn test_standard_reserve_buy_rejected() {
    let mut tracker = create_tracker_with_reserve(10.0);
    
    // Initial: $100K cash, portfolio_value = $100K
    // Reserve required: $100K × 10% = $10K
    
    // Step 1: Execute first buy
    // Long100 (1.0) × max_position=10 = 10.0 position
    // Cost: 10.0 × $5000 = $50,000
    // Cash after: $100K - $50K = $50K (portfolio value ~$50K, reserve ~$5K)
    let action = FactoredAction::new(ExposureLevel::Long100, OrderType::Market, Urgency::Normal);
    tracker.execute_action(action, 5000.0, 10.0);
    
    // Step 2: Attempt second buy which should violate reserve
    // This would cost another $50K, leaving cash at $0 (< $5K reserve) ❌
    let action = FactoredAction::new(ExposureLevel::Long100, OrderType::Market, Urgency::Normal);
    tracker.execute_action(action, 5000.0, 10.0);
    
    // Expected: Trade REJECTED or REDUCED (cash should not go below reserve)
    let cash_after = tracker.cash_balance();
    
    // Verify reserve is enforced
    let portfolio_value = tracker.total_value(5000.0);
    let reserve_required = portfolio_value * 0.10;
    
    // Cash should be >= reserve after trade attempt
    assert!(
        cash_after >= reserve_required * 0.99, // Allow 1% tolerance for rounding
        "Cash {:.2} should be >= reserve {:.2}",
        cash_after, reserve_required
    );
}

/// Test 5: Aggressive reserve (15%) - BUY rejected
///
/// Verifies that higher reserve percentages correctly reject more trades.
#[test]
fn test_aggressive_reserve_buy_rejected() {
    let mut tracker = create_tracker_with_reserve(15.0);
    
    // Initial: $100K cash, portfolio_value = $100K
    // Reserve required: $100K × 15% = $15K
    
    // Step 1: Execute first buy
    // Cost: 10.0 × $5000 = $50,000
    // Cash after: $50K (portfolio value ~$50K, reserve ~$7.5K)
    let action = FactoredAction::new(ExposureLevel::Long100, OrderType::Market, Urgency::Normal);
    tracker.execute_action(action, 5000.0, 10.0);
    
    // Step 2: Attempt second buy
    // This would cost another $50K, leaving cash at $0 (< $7.5K reserve) ❌
    let action = FactoredAction::new(ExposureLevel::Long100, OrderType::Market, Urgency::Normal);
    tracker.execute_action(action, 5000.0, 10.0);
    
    let cash_after = tracker.cash_balance();
    let portfolio_value = tracker.total_value(5000.0);
    let reserve_required = portfolio_value * 0.15;
    
    // Verify 15% reserve is enforced
    assert!(
        cash_after >= reserve_required * 0.99,
        "Cash {:.2} should be >= 15% reserve {:.2}",
        cash_after, reserve_required
    );
}

/// Test 6: Reserve sell exemption - SELL always allowed
///
/// Verifies that SELL trades are ALWAYS allowed, even when cash is below
/// the reserve requirement, because selling ADDS cash.
#[test]
fn test_reserve_sell_always_allowed() {
    let mut tracker = create_tracker_with_reserve(10.0);
    
    // Step 1: Build a long position
    let buy_action = FactoredAction::new(ExposureLevel::Long100, OrderType::Market, Urgency::Normal);
    tracker.execute_action(buy_action, 5000.0, 10.0);
    
    let cash_before_sell = tracker.cash_balance();
    let position_before_sell = tracker.current_position();
    
    // Step 2: Execute SELL (should always work, regardless of cash level)
    let sell_action = FactoredAction::new(ExposureLevel::Short100, OrderType::Market, Urgency::Normal);
    tracker.execute_action(sell_action, 5000.0, 10.0);
    
    // Expected: SELL executes (position changes, cash increases)
    let cash_after_sell = tracker.cash_balance();
    let position_after_sell = tracker.current_position();
    
    // Position should change (SELL executed)
    assert_ne!(position_after_sell, position_before_sell, "SELL should change position");
    
    // Cash should increase or stay same (SELL adds cash)
    // Note: Transaction costs might cause slight decrease, but trade should execute
    assert!(
        (cash_after_sell - cash_before_sell).abs() > 0.01,
        "SELL should execute (cash changed from {:.2} to {:.2})",
        cash_before_sell, cash_after_sell
    );
}

/// Test 7: Dynamic reserve - portfolio growth
///
/// Verifies that the reserve requirement adjusts dynamically as portfolio
/// value increases due to profitable positions.
#[test]
fn test_reserve_dynamic_portfolio_growth() {
    let mut tracker = create_tracker_with_reserve(10.0);
    
    // Initial: $100K cash, reserve = $10K
    
    // Step 1: BUY Long50 at $5000
    let action = FactoredAction::new(ExposureLevel::Long50, OrderType::Market, Urgency::Normal);
    tracker.execute_action(action, 5000.0, 10.0);
    
    let pv_after_buy = tracker.total_value(5000.0);
    let reserve_after_buy = pv_after_buy * 0.10;
    
    // Step 2: Price rises to $7000 (portfolio grows)
    let pv_at_7k = tracker.total_value(7000.0);
    let reserve_at_7k = pv_at_7k * 0.10;
    
    // Reserve should increase with portfolio value
    assert!(
        reserve_at_7k > reserve_after_buy,
        "Reserve should increase with portfolio growth ({:.2} -> {:.2})",
        reserve_after_buy, reserve_at_7k
    );
    
    // Verify reserve is applied to next trade
    // (Implementation detail: reserve check happens in execute_action)
}

/// Test 8: Dynamic reserve - portfolio shrinkage
///
/// Verifies that reserve requirement decreases when portfolio value
/// declines due to losing positions.
#[test]
fn test_reserve_dynamic_portfolio_shrinkage() {
    let mut tracker = create_tracker_with_reserve(10.0);
    
    // Step 1: BUY Long100 at $5000
    let buy_action = FactoredAction::new(ExposureLevel::Long100, OrderType::Market, Urgency::Normal);
    tracker.execute_action(buy_action, 5000.0, 10.0);
    
    let pv_at_5k = tracker.total_value(5000.0);
    let reserve_at_5k = pv_at_5k * 0.10;
    
    // Step 2: Price falls to $4000 (portfolio shrinks)
    let pv_at_4k = tracker.total_value(4000.0);
    let reserve_at_4k = pv_at_4k * 0.10;
    
    // Reserve should decrease with portfolio value
    assert!(
        reserve_at_4k < reserve_at_5k,
        "Reserve should decrease with portfolio shrinkage ({:.2} -> {:.2})",
        reserve_at_5k, reserve_at_4k
    );
}

/// Test 9: Reserve with transaction costs
///
/// Verifies that transaction costs are correctly included when calculating
/// whether a trade violates the reserve requirement.
#[test]
fn test_reserve_with_transaction_costs() {
    let mut tracker = create_tracker_with_reserve(10.0);
    
    // Initial: $100K cash, reserve = $10K
    
    // Action: BUY with Market order (higher fee 0.15% vs LimitMaker 0.05%)
    // Cost: 10.0 × $5000 = $50,000
    
    let market_action = FactoredAction::new(ExposureLevel::Long100, OrderType::Market, Urgency::Normal);
    tracker.execute_action(market_action, 5000.0, 10.0);

    let cash_after_market = tracker.cash_balance();

    // Note: This simpler version doesn't track transaction costs separately
    // but the reserve enforcement still works correctly

    // Verify reserve is still enforced
    let portfolio_value = tracker.total_value(5000.0);
    let reserve_required = portfolio_value * 0.10;

    assert!(
        cash_after_market >= reserve_required * 0.99,
        "Cash {:.2} should be >= reserve {:.2}",
        cash_after_market, reserve_required
    );
}

/// Test 10: Boundary condition - exact reserve limit
///
/// Verifies behavior when a trade would leave cash exactly at the reserve
/// requirement (boundary condition).
#[test]
fn test_reserve_edge_case_exact_boundary() {
    let mut tracker = create_tracker_with_reserve(10.0);
    
    // This test verifies that the reserve check uses strict comparison (>= not >)
    // When cash after trade = reserve exactly, trade should be REJECTED or REDUCED
    
    // Initial: $100K cash, reserve = $10K
    
    // Drain cash to just above reserve
    // First buy: $50K cost, leaves $50K cash
    let action = FactoredAction::new(ExposureLevel::Long100, OrderType::Market, Urgency::Normal);
    tracker.execute_action(action, 5000.0, 10.0);
    
    let portfolio_value = tracker.total_value(5000.0);
    let reserve_required = portfolio_value * 0.10;
    
    // Attempt second trade (would drain $50K → $0, violating reserve)
    let action = FactoredAction::new(ExposureLevel::Long100, OrderType::Market, Urgency::Normal);
    tracker.execute_action(action, 5000.0, 10.0);
    
    let cash_final = tracker.cash_balance();
    
    // Verify: cash should be >= reserve (not exactly equal, due to implementation reducing trade size)
    assert!(
        cash_final >= reserve_required * 0.99,
        "Cash {:.2} should be >= reserve {:.2} (boundary test)",
        cash_final, reserve_required
    );
}
