#![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,
)]
// Test-Driven Development: P&L Calculation Bug Fix
//
// **Problem**: P&L values are 10,000× - 100,000× too large
// - Epoch 10: -$578,040,448 loss (-5,780,405% return)
// - Expected: ±$5,000 range for validation data
//
// **Root Cause**: max_position = 1.0 contract instead of fractional position
// - ES futures price: ~$5,800
// - ES contract multiplier: 50×
// - 1.0 contract = $5,800 × 50 = $290,000 notional
// - For $10K capital, this is 29× leverage (CATASTROPHIC)
//
// **Solution**: Scale max_position to match capital
// - Desired exposure: 1.0× capital ($10,000 notional)
// - Fractional contracts: $10,000 / ($5,800 × 50) = 0.0345 contracts
// - General formula: max_position = initial_capital / (price × contract_multiplier)

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

/// Test 1: Verify P&L calculation with realistic position sizing
#[test]
fn test_pnl_calculation_realistic_sizing() {
    // GIVEN: $10,000 initial capital, ES futures at $5,800
    // NOTE: PortfolioTracker treats position_size as notional units (not contracts)
    // So we scale max_position to represent desired notional exposure
    let initial_capital = 10_000.0;
    let price = 5_800.0;

    // For 1× capital exposure:
    // max_position × price = initial_capital
    // max_position = initial_capital / price = 10000 / 5800 = 1.724 units
    let max_position = initial_capital / price;

    let mut tracker = PortfolioTracker::new(initial_capital, 0.0001, 1.0);

    // WHEN: Execute Long100 (full long position)
    let action = FactoredAction::new(ExposureLevel::Long100, OrderType::Market, Urgency::Normal);
    tracker.execute_action(action, price, max_position);

    // THEN: Position should be ~1.724 units (NOT 1.0 or 0.0345)
    let actual_position = tracker.current_position();
    assert!(
        (actual_position - max_position).abs() < 0.0001,
        "Position size should be ~1.724 units, got {}",
        actual_position
    );

    // Portfolio value should still be ~$10,000 (minus fees)
    let portfolio_value = tracker.total_value(price);
    assert!(
        (portfolio_value - initial_capital).abs() < 100.0,
        "Portfolio value should be ~$10,000, got ${}",
        portfolio_value
    );
}

/// Test 2: Verify P&L stays within ±$5K range for 10% price move
#[test]
fn test_pnl_range_realistic() {
    // GIVEN: $10,000 initial capital, ES futures at $5,800
    // NOTE: PortfolioTracker doesn't model contract multipliers - it treats position_size
    // as notional value directly. So we need to scale max_position accordingly.
    let initial_capital = 10_000.0;
    let price_entry = 5_800.0;

    // max_position should represent the number of "notional units" we want to hold
    // For 1× capital exposure, we want max_position such that:
    // position_size × price = initial_capital
    // So max_position = initial_capital / price
    let max_position = initial_capital / price_entry;

    let mut tracker = PortfolioTracker::new(initial_capital, 0.0001, 1.0);

    // WHEN: Go long at $5,800
    let action = FactoredAction::new(ExposureLevel::Long100, OrderType::Market, Urgency::Normal);
    tracker.execute_action(action, price_entry, max_position);

    // AND: Price increases 10% to $6,380
    let price_exit = price_entry * 1.10;
    let pnl = tracker.unrealized_pnl(price_exit);

    // THEN: P&L should be ~$1,000 (10% of $10K), NOT $290,000
    assert!(
        pnl.abs() < 5_000.0,
        "P&L should be within ±$5K for 10% move, got ${:.2}",
        pnl
    );

    // Expected P&L: max_position × (price_exit - price_entry)
    // = (10000/5800) × (6380 - 5800) = 1.724 × 580 = ~$1,000
    let expected_pnl = max_position * (price_exit - price_entry);
    assert!(
        (pnl - expected_pnl).abs() < 100.0,
        "P&L should be ~${:.2}, got ${:.2}",
        expected_pnl,
        pnl
    );
}

/// Test 3: Verify old bug (max_position = 1.0) produces WRONG P&L
#[test]
fn test_old_bug_wrong_pnl() {
    // GIVEN: $10,000 initial capital, ES futures at $5,800
    let initial_capital = 10_000.0;
    let price_entry = 5_800.0;

    // OLD BUG: max_position = 1.0 (WRONG - too small)
    // This gives 1.0 units × $5,800 = $5,800 notional (58% capital exposure)
    let max_position_wrong = 1.0;

    let mut tracker = PortfolioTracker::new(initial_capital, 0.0001, 1.0);

    // WHEN: Go long at $5,800 with 1.0 unit
    let action = FactoredAction::new(ExposureLevel::Long100, OrderType::Market, Urgency::Normal);
    tracker.execute_action(action, price_entry, max_position_wrong);

    // AND: Price increases 10% to $6,380
    let price_exit = price_entry * 1.10;
    let pnl = tracker.unrealized_pnl(price_exit);

    // THEN: P&L is WRONG ($580, not $1,000)
    // 1.0 units × (6,380 - 5,800) = $580 (should be $1,000 for 100% capital exposure)
    let wrong_pnl = max_position_wrong * (price_exit - price_entry);
    assert!(
        (pnl - wrong_pnl).abs() < 100.0,
        "Old bug should produce ~${:.2}, got ${:.2}",
        wrong_pnl,
        pnl
    );

    // Expected with correct sizing: (10000/5800) × 580 = ~$1,000
    let correct_max_position = initial_capital / price_entry;
    let correct_pnl = correct_max_position * (price_exit - price_entry);
    assert!(
        pnl < correct_pnl * 0.9,
        "Old bug P&L (${:.2}) should be <90% of correct P&L (${:.2})",
        pnl,
        correct_pnl
    );
}

/// Test 4: Verify return percentage calculation
#[test]
fn test_return_percentage_scaling() {
    // GIVEN: $10,000 initial capital, 10% return
    let initial_capital = 10_000.0;
    let final_value = 11_000.0;

    // WHEN: Calculate return percentage
    let pnl = final_value - initial_capital;
    let return_pct = (pnl / initial_capital) * 100.0f32;

    // THEN: Should be 10.0%
    assert!(
        (return_pct - 10.0f32).abs() < 0.01,
        "Return should be 10%, got {:.2}%",
        return_pct
    );
}

/// Test 5: Verify Short100 position sizing (symmetric to Long100)
#[test]
fn test_short_position_sizing() {
    // GIVEN: $10,000 initial capital, ES futures at $5,800
    let initial_capital = 10_000.0;
    let price = 5_800.0;
    let max_position = initial_capital / price;

    let mut tracker = PortfolioTracker::new(initial_capital, 0.0001, 1.0);

    // WHEN: Execute Short100 (full short position)
    let action = FactoredAction::new(ExposureLevel::Short100, OrderType::Market, Urgency::Normal);
    tracker.execute_action(action, price, max_position);

    // THEN: Position should be -1.724 units (negative for short)
    let actual_position = tracker.current_position();
    assert!(
        (actual_position + max_position).abs() < 0.0001,
        "Short position should be ~-1.724 units, got {}",
        actual_position
    );

    // Portfolio value should still be ~$10,000 (cash increased, but position liability offsets)
    let portfolio_value = tracker.total_value(price);
    assert!(
        (portfolio_value - initial_capital).abs() < 100.0,
        "Portfolio value should be ~$10,000 after short, got ${}",
        portfolio_value
    );
}

/// Test 6: Verify $100K initial capital (matches DQN trainer)
#[test]
fn test_trainer_initial_capital() {
    // GIVEN: $100K initial capital (same as DQN trainer line 535)
    let initial_capital = 100_000.0;
    let price = 5_800.0;
    let max_position = initial_capital / price; // Should be ~17.24 units

    let mut tracker = PortfolioTracker::new(initial_capital, 0.0001, 1.0);

    // WHEN: Execute Long100 at $5,800
    let action = FactoredAction::new(ExposureLevel::Long100, OrderType::Market, Urgency::Normal);
    tracker.execute_action(action, price, max_position);

    // THEN: Position should be ~17.24 units
    let actual_position = tracker.current_position();
    assert!(
        (actual_position - max_position).abs() < 0.01,
        "Position size should be ~17.24 units, got {}",
        actual_position
    );

    // Portfolio value should still be ~$100K
    let portfolio_value = tracker.total_value(price);
    assert!(
        (portfolio_value - initial_capital).abs() < 1000.0,
        "Portfolio value should be ~$100K, got ${}",
        portfolio_value
    );

    // 10% price move should produce ~$10K P&L (10% of capital)
    let price_exit = price * 1.10;
    let pnl = tracker.unrealized_pnl(price_exit);
    let expected_pnl = 10_000.0; // 10% of $100K
    assert!(
        (pnl - expected_pnl).abs() < 1000.0,
        "P&L should be ~$10K for 10% move, got ${:.2}",
        pnl
    );
}

/// Test 7: Verify last_price is updated after execute_action
#[test]
fn test_last_price_updated() {
    // GIVEN: Fresh portfolio tracker
    let mut tracker = PortfolioTracker::new(100_000.0, 0.0001, 1.0);

    // WHEN: Execute action at $5,800
    let action = FactoredAction::new(ExposureLevel::Long100, OrderType::Market, Urgency::Normal);
    let price = 5_800.0;
    let max_position = 100_000.0 / price;
    tracker.execute_action(action, price, max_position);

    // THEN: unrealized_pnl_cached() should work (not NaN)
    let pnl = tracker.unrealized_pnl_cached();
    assert!(
        !pnl.is_nan(),
        "P&L should not be NaN after execute_action, got {}",
        pnl
    );

    // P&L should be 0.0 (no price change yet)
    assert!(
        pnl.abs() < 0.01,
        "P&L should be ~0.0 (no price change), got ${:.2}",
        pnl
    );
}

/// Test 8: Integration test - Full training epoch P&L range
#[test]
fn test_epoch_pnl_range() {
    // GIVEN: Realistic ES futures price range ($5,700 - $5,900)
    let initial_capital = 10_000.0;
    let price_min = 5_700.0;
    let price_max = 5_900.0;
    let price_avg = (price_min + price_max) / 2.0;

    // Calculate max_position at average price (dynamic sizing)
    // This will be recalculated per-trade in real implementation
    let max_position = initial_capital / price_avg;

    let mut tracker = PortfolioTracker::new(initial_capital, 0.0001, 1.0);

    // Simulate 10 trades across price range
    let actions = vec![
        FactoredAction::new(ExposureLevel::Long100, OrderType::Market, Urgency::Normal),
        FactoredAction::new(ExposureLevel::Long50, OrderType::Market, Urgency::Normal),
        FactoredAction::new(ExposureLevel::Flat, OrderType::Market, Urgency::Normal),
        FactoredAction::new(ExposureLevel::Short50, OrderType::Market, Urgency::Normal),
        FactoredAction::new(ExposureLevel::Short100, OrderType::Market, Urgency::Normal),
        FactoredAction::new(ExposureLevel::Flat, OrderType::Market, Urgency::Normal),
        FactoredAction::new(ExposureLevel::Long50, OrderType::Market, Urgency::Normal),
        FactoredAction::new(ExposureLevel::Flat, OrderType::Market, Urgency::Normal),
        FactoredAction::new(ExposureLevel::Short50, OrderType::Market, Urgency::Normal),
        FactoredAction::new(ExposureLevel::Flat, OrderType::Market, Urgency::Normal),
    ];

    let prices = vec![
        5700.0, 5720.0, 5750.0, 5780.0, 5800.0,
        5820.0, 5850.0, 5870.0, 5890.0, 5900.0,
    ];

    // Execute all trades (in real implementation, max_position would be recalculated per trade)
    for (action, &price) in actions.iter().zip(prices.iter()) {
        tracker.execute_action(*action, price, max_position);
    }

    // THEN: Final P&L should be within ±$10K (realistic for validation data)
    let final_pnl = tracker.unrealized_pnl(prices[prices.len() - 1]);
    assert!(
        final_pnl.abs() < 10_000.0,
        "Epoch P&L should be within ±$10K, got ${:.2}",
        final_pnl
    );
}
