#![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,
)]
//! PPO Portfolio Tracker Tests (TDD)
//!
//! These tests validate the PortfolioTracker integration for PPO.
//! Tests written FIRST (TDD Red Phase) before implementation.

use ml::ppo::portfolio_tracker::PortfolioTracker;

#[test]
fn test_portfolio_tracker_initialization() {
    // Test initial state (cash, position, pnl)
    let tracker = PortfolioTracker::new(10_000.0, 0.0001, 0.0);

    assert_eq!(tracker.cash_balance(), 10_000.0, "Initial cash should equal initial capital");
    assert_eq!(tracker.current_position(), 0.0, "Initial position should be 0 (flat)");
    assert_eq!(tracker.realized_pnl(), 0.0, "Initial realized P&L should be 0");
    assert_eq!(tracker.unrealized_pnl(100.0), 0.0, "Initial unrealized P&L should be 0");
    assert_eq!(tracker.total_value(100.0), 10_000.0, "Initial portfolio value should equal cash");
}

#[test]
fn test_portfolio_tracker_buy_action() {
    // Test BUY action updates position and cash
    let mut tracker = PortfolioTracker::new(10_000.0, 0.0001, 0.0);

    // Execute PPO-style action: action index 0 = BUY
    tracker.execute_ppo_action(0, 100.0, 10.0);

    assert_eq!(tracker.current_position(), 10.0, "Position should be 10 contracts after BUY");
    assert_eq!(tracker.cash_balance(), 9_000.0, "Cash should decrease by position cost (10 * 100)");
    assert_eq!(tracker.average_entry_price(), 100.0, "Entry price should be recorded");
}

#[test]
fn test_portfolio_tracker_sell_action() {
    // Test SELL action updates position and cash
    let mut tracker = PortfolioTracker::new(10_000.0, 0.0001, 0.0);

    // Execute PPO-style action: action index 1 = SELL
    tracker.execute_ppo_action(1, 100.0, 10.0);

    assert_eq!(tracker.current_position(), -10.0, "Position should be -10 contracts after SELL (short)");
    assert_eq!(tracker.cash_balance(), 11_000.0, "Cash should increase by short proceeds (10 * 100)");
    assert_eq!(tracker.average_entry_price(), 100.0, "Entry price should be recorded");
}

#[test]
fn test_portfolio_tracker_cash_reserve() {
    // Test cash reserve enforcement
    let mut tracker = PortfolioTracker::new(10_000.0, 0.0001, 20.0); // 20% cash reserve

    // Try to buy with insufficient cash (reserve enforcement)
    // At price 100, max affordable = (10000 - 2000) / 100 = 80 contracts
    // Request 100 contracts -> should be reduced to 80
    tracker.execute_ppo_action(0, 100.0, 100.0);

    let cash = tracker.cash_balance();
    let portfolio_value = tracker.total_value(100.0);
    let reserve_required = portfolio_value * 0.20;

    assert!(
        cash >= reserve_required,
        "Cash reserve should be enforced: cash=${:.2}, reserve=${:.2}",
        cash, reserve_required
    );
}

#[test]
fn test_portfolio_tracker_pnl_calculation() {
    // Test P&L calculation accuracy
    let mut tracker = PortfolioTracker::new(10_000.0, 0.0001, 0.0);

    // Open long position: Buy 10 contracts at $100
    tracker.execute_ppo_action(0, 100.0, 10.0);
    assert_eq!(tracker.cash_balance(), 9_000.0, "Cash after buy");

    // Price rises to $110 -> +$100 unrealized profit
    let unrealized = tracker.unrealized_pnl(110.0);
    assert_eq!(unrealized, 100.0, "Unrealized P&L should be +$100 (10 contracts * $10 gain)");

    // Total portfolio value = cash + position_value
    let total_value = tracker.total_value(110.0);
    assert_eq!(total_value, 10_100.0, "Portfolio value = 9000 (cash) + 1100 (position value)");

    // Close position: Sell 10 contracts at $110
    tracker.execute_ppo_action(1, 110.0, 10.0);

    // After closing, realized P&L should be +$100
    assert_eq!(tracker.current_position(), 0.0, "Position should be closed");
    assert_eq!(tracker.realized_pnl(), 100.0, "Realized P&L should be +$100");
}

#[test]
fn test_portfolio_tracker_hold_action() {
    // Test HOLD action (no change)
    let mut tracker = PortfolioTracker::new(10_000.0, 0.0001, 0.0);

    let initial_cash = tracker.cash_balance();
    let initial_position = tracker.current_position();

    // Execute PPO-style action: action index 2 = HOLD
    tracker.execute_ppo_action(2, 100.0, 10.0);

    assert_eq!(tracker.cash_balance(), initial_cash, "Cash should not change after HOLD");
    assert_eq!(tracker.current_position(), initial_position, "Position should not change after HOLD");
}

#[test]
fn test_portfolio_tracker_reset() {
    // Test reset functionality
    let mut tracker = PortfolioTracker::new(10_000.0, 0.0001, 0.0);

    // Execute trades
    tracker.execute_ppo_action(0, 100.0, 10.0);

    // Reset
    tracker.reset();

    assert_eq!(tracker.cash_balance(), 10_000.0, "Cash should reset to initial capital");
    assert_eq!(tracker.current_position(), 0.0, "Position should reset to 0");
    assert_eq!(tracker.average_entry_price(), 0.0, "Entry price should reset to 0");
}

#[test]
fn test_portfolio_tracker_short_position_pnl() {
    // Test short position P&L calculation
    let mut tracker = PortfolioTracker::new(10_000.0, 0.0001, 0.0);

    // Open short position: Sell 10 contracts at $100
    tracker.execute_ppo_action(1, 100.0, 10.0);
    assert_eq!(tracker.current_position(), -10.0, "Position should be -10 (short)");
    assert_eq!(tracker.cash_balance(), 11_000.0, "Cash after short sale");

    // Price falls to $90 -> +$100 unrealized profit (profitable for short)
    let unrealized = tracker.unrealized_pnl(90.0);
    assert_eq!(unrealized, 100.0, "Unrealized P&L should be +$100 (10 contracts * $10 gain from price drop)");

    // Total portfolio value = cash + position_value (position_value is negative for shorts)
    let total_value = tracker.total_value(90.0);
    assert_eq!(total_value, 10_100.0, "Portfolio value = 11000 (cash) - 900 (short liability)");
}

#[test]
fn test_portfolio_tracker_get_portfolio_features() {
    // Test portfolio features retrieval
    let tracker = PortfolioTracker::new(10_000.0, 0.0001, 0.0);

    let features = tracker.get_portfolio_features(100.0);

    // Features: [normalized_value, normalized_position, spread]
    assert_eq!(features[0], 1.0, "Normalized portfolio value should be 1.0 (initial capital)");
    assert_eq!(features[1], 0.0, "Normalized position should be 0.0 (no position)");
    assert_eq!(features[2], 0.0001, "Spread should be 0.0001");
}

// ========== WAVE 2 AGENT 4: POSITION REVERSAL TESTS (TDD RED PHASE) ==========

#[test]
fn test_position_reversal_long_to_short() {
    // Test position reversal from long to short
    let mut tracker = PortfolioTracker::new(10_000.0, 0.0001, 0.0);

    // Step 1: Open long position: Buy 20 contracts at $100
    tracker.execute_ppo_action(0, 100.0, 20.0);
    assert_eq!(tracker.current_position(), 20.0, "Should have long position of 20 contracts");
    assert_eq!(tracker.cash_balance(), 8_000.0, "Cash = 10000 - (20 * 100)");

    // Step 2: Reversal - Sell 40 contracts at $110 (close 20 long + open 20 short)
    // Phase 1: Close 20 long at $110 -> cash += 20 * 110 = +2200
    // Phase 2: Open 20 short at $110 -> cash += 20 * 110 = +2200
    tracker.execute_ppo_action(1, 110.0, 40.0);

    // Expected: Position = -20 (short), Cash = 8000 + 2200 + 2200 = 12400
    assert_eq!(tracker.current_position(), -20.0, "Should have short position of -20 contracts after reversal");
    assert_eq!(tracker.cash_balance(), 12_400.0, "Cash = 8000 + 2200 (close long) + 2200 (open short)");
}

#[test]
fn test_position_reversal_short_to_long() {
    // Test position reversal from short to long
    let mut tracker = PortfolioTracker::new(10_000.0, 0.0001, 0.0);

    // Step 1: Open short position: Sell 20 contracts at $100
    tracker.execute_ppo_action(1, 100.0, 20.0);
    assert_eq!(tracker.current_position(), -20.0, "Should have short position of -20 contracts");
    assert_eq!(tracker.cash_balance(), 12_000.0, "Cash = 10000 + (20 * 100)");

    // Step 2: Reversal - Buy 40 contracts at $90 (close 20 short + open 20 long)
    // Phase 1: Close 20 short at $90 -> cash -= 20 * 90 = -1800
    // Phase 2: Open 20 long at $90 -> cash -= 20 * 90 = -1800
    tracker.execute_ppo_action(0, 90.0, 40.0);

    // Expected: Position = 20 (long), Cash = 12000 - 1800 - 1800 = 8400
    assert_eq!(tracker.current_position(), 20.0, "Should have long position of 20 contracts after reversal");
    assert_eq!(tracker.cash_balance(), 8_400.0, "Cash = 12000 - 1800 (close short) - 1800 (open long)");
}

#[test]
fn test_partial_reversal_insufficient_cash() {
    // Test partial reversal when insufficient cash for full flip
    let mut tracker = PortfolioTracker::new(10_000.0, 0.0001, 20.0); // 20% cash reserve

    // Step 1: Open long position: Buy 50 contracts at $100
    // Max affordable = (10000 - 2000 reserve) / 100 = 80 contracts
    tracker.execute_ppo_action(0, 100.0, 50.0);
    assert_eq!(tracker.current_position(), 50.0, "Should have long position of 50 contracts");

    // Step 2: Attempt reversal - Sell 100 contracts at $110 (close 50 long + open 50 short)
    // Phase 1: Close 50 long at $110 -> cash += 50 * 110 = +5500
    // Phase 2: Try to open 50 short at $110 -> may be partial due to cash reserve
    tracker.execute_ppo_action(1, 110.0, 100.0);

    // After reversal:
    // - Position should be negative (short) but may be less than -50 due to cash constraint
    // - Cash reserve should still be enforced
    let final_position = tracker.current_position();
    let final_cash = tracker.cash_balance();
    let portfolio_value = tracker.total_value(110.0);
    let reserve_required = portfolio_value * 0.20;

    assert!(final_position < 0.0, "Should have short position after reversal (negative)");
    assert!(final_cash >= reserve_required, "Cash reserve should be enforced: cash=${:.2}, reserve=${:.2}", final_cash, reserve_required);
}
