#![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,
)]
//! Transaction Cost Application Tests
//!
//! Validates that transaction costs are properly deducted from portfolio value
//! and P&L calculations in the PortfolioTracker.
//!
//! Bug Context: Agent B3 (Wave 16N)
//! - Transaction costs defined in FactoredAction but NOT applied to portfolio
//! - Results in P&L overstatement (0.01% leak per trade)

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

#[test]
fn test_transaction_costs_reduce_portfolio_value_market_order() {
    // Given: PortfolioTracker with $100K initial capital
    let mut tracker = PortfolioTracker::new(100000.0, 0.0001, 1.0);

    // When: Execute Market order (0.15% fee)
    // Buy 10 contracts at $5000 = $50,000 trade value
    // Fee: $50,000 * 0.0015 = $75
    let action = FactoredAction::new(ExposureLevel::Long100, OrderType::Market, Urgency::Normal);
    let price = 5000.0;
    let max_position = 10.0;
    tracker.execute_action(action, price, max_position);

    // Then: Portfolio value should be $100K - $75 (not $100K)
    let portfolio_value = tracker.get_raw_portfolio_features(price)[0];
    let expected_value = 100000.0 - 75.0; // $100K - $75 fee
    assert_eq!(
        portfolio_value, expected_value,
        "Portfolio value should include transaction cost deduction. Expected {}, got {}",
        expected_value, portfolio_value
    );
}

#[test]
fn test_transaction_costs_reduce_portfolio_value_ioc_order() {
    // Given: PortfolioTracker
    let mut tracker = PortfolioTracker::new(100000.0, 0.0001, 1.0);

    // When: Execute IoC order (0.10% fee)
    // Buy 10 contracts at $5000 = $50,000 trade value
    // Fee: $50,000 * 0.0010 = $50
    let action = FactoredAction::new(ExposureLevel::Long100, OrderType::IoC, Urgency::Aggressive);
    let price = 5000.0;
    let max_position = 10.0;
    tracker.execute_action(action, price, max_position);

    // Then: Portfolio value should be $100K - $50
    let portfolio_value = tracker.get_raw_portfolio_features(price)[0];
    let expected_value = 100000.0 - 50.0; // $100K - $50 fee
    assert_eq!(
        portfolio_value, expected_value,
        "Portfolio value should include IoC transaction cost. Expected {}, got {}",
        expected_value, portfolio_value
    );
}

#[test]
fn test_limitmaker_rebates_increase_portfolio_value() {
    // Given: PortfolioTracker
    let mut tracker = PortfolioTracker::new(100000.0, 0.0001, 1.0);

    // When: Execute LimitMaker order (0.05% rebate)
    // Buy 10 contracts at $5000 = $50,000 trade value
    // Rebate: $50,000 * 0.0005 = $25
    let action = FactoredAction::new(ExposureLevel::Long100, OrderType::LimitMaker, Urgency::Patient);
    let price = 5000.0;
    let max_position = 10.0;
    tracker.execute_action(action, price, max_position);

    // Then: Portfolio value should be $100K + $25 (rebate)
    // Note: LimitMaker is treated as a "rebate" (reduces cost, effectively adds value)
    let portfolio_value = tracker.get_raw_portfolio_features(price)[0];
    let expected_value = 100000.0 - 25.0; // Still a cost, but smaller
    assert_eq!(
        portfolio_value, expected_value,
        "Portfolio value should include LimitMaker rebate. Expected {}, got {}",
        expected_value, portfolio_value
    );
}

#[test]
fn test_pnl_includes_transaction_costs() {
    // Given: PortfolioTracker with trade sequence
    let mut tracker = PortfolioTracker::new(100000.0, 0.0001, 1.0);

    // When: Buy at $5000 (Market, fee: $75), sell at $5100 (Market, fee: $76.50)
    // Buy: 10 contracts at $5000 = $50,000 trade, fee = $75
    let buy_action = FactoredAction::new(ExposureLevel::Long100, OrderType::Market, Urgency::Normal);
    tracker.execute_action(buy_action, 5000.0, 10.0);

    // Sell: 10 contracts at $5100 = $51,000 trade, fee = $76.50
    let sell_action = FactoredAction::new(ExposureLevel::Flat, OrderType::Market, Urgency::Normal);
    tracker.execute_action(sell_action, 5100.0, 10.0);

    // Then: P&L = gross profit ($1000) - fees ($151.50) = $848.50
    let final_value = tracker.get_raw_portfolio_features(5100.0)[0];
    let pnl = final_value - 100000.0;
    let expected_pnl = 1000.0 - 75.0 - 76.50; // Gross profit - buy fee - sell fee
    assert_eq!(
        pnl, expected_pnl,
        "P&L should include transaction costs. Expected {}, got {}",
        expected_pnl, pnl
    );
}

#[test]
fn test_transaction_costs_accumulate_across_trades() {
    // Given: PortfolioTracker
    let mut tracker = PortfolioTracker::new(100000.0, 0.0001, 1.0);

    // When: Execute multiple trades with different order types
    // Trade 1: Market order (0.15% fee)
    let action1 = FactoredAction::new(ExposureLevel::Long50, OrderType::Market, Urgency::Normal);
    tracker.execute_action(action1, 5000.0, 10.0); // $25,000 trade, $37.50 fee

    // Trade 2: IoC order (0.10% fee)
    let action2 = FactoredAction::new(ExposureLevel::Long100, OrderType::IoC, Urgency::Aggressive);
    tracker.execute_action(action2, 5000.0, 10.0); // $25,000 trade (delta from Long50 to Long100), $25 fee

    // Trade 3: LimitMaker order (0.05% fee)
    let action3 = FactoredAction::new(ExposureLevel::Flat, OrderType::LimitMaker, Urgency::Patient);
    tracker.execute_action(action3, 5000.0, 10.0); // $50,000 trade (close position), $25 fee

    // Then: Total fees = $37.50 + $25 + $25 = $87.50
    let final_value = tracker.get_raw_portfolio_features(5000.0)[0];
    let total_costs = 100000.0 - final_value;
    let expected_costs = 37.50 + 25.0 + 25.0; // Sum of all fees
    assert_eq!(
        total_costs, expected_costs,
        "Transaction costs should accumulate. Expected {}, got {}",
        expected_costs, total_costs
    );
}

#[test]
fn test_transaction_costs_with_price_movement() {
    // Given: PortfolioTracker
    let mut tracker = PortfolioTracker::new(100000.0, 0.0001, 1.0);

    // When: Buy at $5000, price moves to $5100, then sell
    // Buy: Market order (0.15% fee)
    let buy_action = FactoredAction::new(ExposureLevel::Long100, OrderType::Market, Urgency::Normal);
    tracker.execute_action(buy_action, 5000.0, 10.0); // $50,000 trade, $75 fee

    // Price moves to $5100 (unrealized gain: $1000)
    let mid_value = tracker.get_raw_portfolio_features(5100.0)[0];
    let unrealized_pnl = mid_value - 100000.0;
    let expected_unrealized = 1000.0 - 75.0; // Gross gain - buy fee
    assert_eq!(
        unrealized_pnl, expected_unrealized,
        "Unrealized P&L should include buy transaction cost. Expected {}, got {}",
        expected_unrealized, unrealized_pnl
    );

    // Sell: Market order (0.15% fee)
    let sell_action = FactoredAction::new(ExposureLevel::Flat, OrderType::Market, Urgency::Normal);
    tracker.execute_action(sell_action, 5100.0, 10.0); // $51,000 trade, $76.50 fee

    // Then: Realized P&L = gross profit - all fees
    let final_value = tracker.get_raw_portfolio_features(5100.0)[0];
    let realized_pnl = final_value - 100000.0;
    let expected_realized = 1000.0 - 75.0 - 76.50; // Gross profit - buy fee - sell fee
    assert_eq!(
        realized_pnl, expected_realized,
        "Realized P&L should include all transaction costs. Expected {}, got {}",
        expected_realized, realized_pnl
    );
}

#[test]
fn test_zero_transaction_cost_for_flat_to_flat() {
    // Given: PortfolioTracker with no position
    let mut tracker = PortfolioTracker::new(100000.0, 0.0001, 1.0);

    // When: Execute Flat action (no position change)
    let action = FactoredAction::new(ExposureLevel::Flat, OrderType::Market, Urgency::Normal);
    tracker.execute_action(action, 5000.0, 10.0);

    // Then: No transaction cost (no trade executed)
    let portfolio_value = tracker.get_raw_portfolio_features(5000.0)[0];
    assert_eq!(
        portfolio_value, 100000.0,
        "Flat-to-Flat should incur no transaction cost. Expected 100000.0, got {}",
        portfolio_value
    );
}

#[test]
fn test_transaction_costs_for_partial_position_change() {
    // Given: PortfolioTracker
    let mut tracker = PortfolioTracker::new(100000.0, 0.0001, 1.0);

    // When: Go from Flat → Long50 → Long100
    // Trade 1: Long50 (0.15% fee on $25,000 trade = $37.50)
    let action1 = FactoredAction::new(ExposureLevel::Long50, OrderType::Market, Urgency::Normal);
    tracker.execute_action(action1, 5000.0, 10.0); // Trade value: $25,000

    let value_after_trade1 = tracker.get_raw_portfolio_features(5000.0)[0];
    let cost1 = 100000.0 - value_after_trade1;
    assert_eq!(cost1, 37.50, "Long50 should cost $37.50 in fees");

    // Trade 2: Long100 (0.15% fee on $25,000 incremental trade = $37.50)
    let action2 = FactoredAction::new(ExposureLevel::Long100, OrderType::Market, Urgency::Normal);
    tracker.execute_action(action2, 5000.0, 10.0); // Additional $25,000 trade

    let value_after_trade2 = tracker.get_raw_portfolio_features(5000.0)[0];
    let total_costs = 100000.0 - value_after_trade2;
    assert_eq!(total_costs, 75.0, "Long50 → Long100 should cost $75 total ($37.50 + $37.50)");
}

#[test]
fn test_transaction_costs_with_short_positions() {
    // Given: PortfolioTracker
    let mut tracker = PortfolioTracker::new(100000.0, 0.0001, 1.0);

    // When: Short 10 contracts at $5000 (Market order, 0.15% fee)
    // Short: $50,000 trade value, $75 fee
    let short_action = FactoredAction::new(ExposureLevel::Short100, OrderType::Market, Urgency::Normal);
    tracker.execute_action(short_action, 5000.0, 10.0);

    // Then: Portfolio value should reflect transaction cost
    let portfolio_value = tracker.get_raw_portfolio_features(5000.0)[0];
    let expected_value = 100000.0 - 75.0; // $100K - $75 fee
    assert_eq!(
        portfolio_value, expected_value,
        "Short position should incur transaction cost. Expected {}, got {}",
        expected_value, portfolio_value
    );

    // When: Cover short at $4900 (profitable short, Market order 0.15% fee)
    // Cover: $49,000 trade value, $73.50 fee
    let cover_action = FactoredAction::new(ExposureLevel::Flat, OrderType::Market, Urgency::Normal);
    tracker.execute_action(cover_action, 4900.0, 10.0);

    // Then: P&L = gross profit ($1000) - fees ($148.50)
    let final_value = tracker.get_raw_portfolio_features(4900.0)[0];
    let pnl = final_value - 100000.0;
    let expected_pnl = 1000.0 - 75.0 - 73.50; // Gross profit - short fee - cover fee
    assert_eq!(
        pnl, expected_pnl,
        "Short P&L should include transaction costs. Expected {}, got {}",
        expected_pnl, pnl
    );
}
