#![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,
)]
//! Bug #2: Transaction Cost Weight Fix - Regression Tests
//!
//! **BUG DESCRIPTION**:
//! Transaction costs were hardcoded to 0.05 instead of using proper market rates.
//! This caused the agent to undertrain on transaction cost awareness.
//!
//! **ROOT CAUSE**:
//! File: `/ml/src/dqn/reward.rs:153` (Default impl) and line 221 (Builder)
//! ```rust
//! cost_weight: Decimal::try_from(0.05).unwrap_or(Decimal::ZERO),  // WAS: HARDCODED 0.05
//! ```
//!
//! **STATUS**: ✅ Bug #2 fix IMPLEMENTED
//!
//! **FIX APPLIED**:
//! ```rust
//! cost_weight: Decimal::ONE,  // Apply transaction costs at full weight
//! ```
//!
//! **WHY transaction_cost_multiplier SHOULD BE REMOVED**:
//! - Transaction costs are FIXED market rates (0.05%-0.15%) from OrderType enum
//! - These are real exchange fees, not tunable hyperparameters
//! - Making them tunable allows the agent to "cheat" by learning with fake low costs
//! - Real trading must use actual market fees
//!
//! **CURRENT STATE**:
//! - cost_weight is hardcoded to 0.05 (line 982)
//! - transaction_cost_multiplier is still in hyperopt search space (dimension #8)
//! - These tests verify current behavior, not fixed behavior

use ml::dqn::action_space::{ExposureLevel, FactoredAction, OrderType, Urgency};
use ml::dqn::reward::RewardConfig;
use ml::hyperopt::adapters::dqn::DQNParams;
use ml::hyperopt::ParameterSpace;
use rust_decimal::Decimal;
use tracing::info;

/// Test that cost_weight is now 1.0 (full weight) - verifies Bug #2 fix
///
/// **Before Fix**: cost_weight = 0.05 (hardcoded in reward.rs)
/// **After Fix**: cost_weight = 1.0 (full weight)
/// **Status**: ✅ Bug #2 fix IMPLEMENTED
#[test]
fn test_cost_weight_is_full_weight() {
    // Create reward config using Default (should now have cost_weight = 1.0)
    let reward_config = RewardConfig::default();

    // Verify cost_weight is now 1.0 (full weight)
    assert_eq!(
        reward_config.cost_weight,
        Decimal::ONE,
        "Bug #2 fix: cost_weight should be 1.0 (full weight)"
    );

    info!("cost_weight = 1.0 (Bug #2 fix implemented): before=0.05 (5% of actual costs), after=1.0 (100% of actual costs)");
}

/// Test that transaction_cost_multiplier has been removed from DQNParams.
///
/// After the 14D family-intensity restructuring, transaction_cost_multiplier
/// is no longer a tunable hyperparameter. Transaction costs are fixed market
/// rates from OrderType enum.
#[test]
fn test_transaction_cost_multiplier_removed() {
    // DQNParams now only has 14 fields (3 breakouts + 11 family intensities).
    // transaction_cost_multiplier was removed -- verify the search space is 14D.
    let bounds = DQNParams::continuous_bounds();
    assert_eq!(bounds.len(), 14, "Search space should be 14D after family consolidation");

    let names = DQNParams::param_names();
    assert!(
        !names.contains(&"transaction_cost_multiplier"),
        "transaction_cost_multiplier should NOT be in search space"
    );

    info!("transaction_cost_multiplier removed from DQNParams; transaction costs are fixed market rates from OrderType");
}

/// Test that LimitMaker orders have lowest cost (0.05%)
///
/// **Expected**: LimitMaker cost < IoC cost < Market cost
/// **Verifies**: OrderType enum transaction costs are correct
#[test]
fn test_order_type_transaction_costs() {
    let market_cost = OrderType::Market.transaction_cost();
    let limit_cost = OrderType::LimitMaker.transaction_cost();
    let ioc_cost = OrderType::IoC.transaction_cost();

    // Verify costs are in correct order
    assert!(
        limit_cost < ioc_cost && ioc_cost < market_cost,
        "Order type costs should be: LimitMaker < IoC < Market\n\
         Got: LimitMaker={:.4}, IoC={:.4}, Market={:.4}",
        limit_cost,
        ioc_cost,
        market_cost
    );

    // Verify exact values (from action_space.rs)
    assert_eq!(market_cost, 0.0015, "Market orders should cost 0.15%");
    assert_eq!(limit_cost, 0.0005, "LimitMaker orders should cost 0.05%");
    assert_eq!(ioc_cost, 0.0010, "IoC orders should cost 0.10%");

    info!(market_pct = market_cost * 100.0, limit_pct = limit_cost * 100.0, ioc_pct = ioc_cost * 100.0, "Order type transaction costs are correct");
}

/// Integration test: Verify hyperopt search space dimensions
///
/// Search space is now 14D (3 breakouts + 5 core families + 6 gen families).
/// transaction_cost_multiplier was removed during family-intensity consolidation.
#[test]
fn test_hyperopt_search_space_reduced() {
    let bounds = DQNParams::continuous_bounds();

    assert_eq!(
        bounds.len(),
        14,
        "Hyperopt search space should be 14D (3 breakouts + 5 core + 6 gen families), got {}D",
        bounds.len()
    );

    let names = DQNParams::param_names();
    assert!(
        !names.contains(&"transaction_cost_multiplier"),
        "transaction_cost_multiplier should not be in 14D search space"
    );

    info!(dims = bounds.len(), "Hyperopt search space is 14D; transaction_cost_multiplier removed (fixed market rates)");
}

/// Test that transaction costs correctly reflect factored action order types
#[test]
fn test_factored_action_transaction_costs() {
    // Market order (most expensive)
    let market_action = FactoredAction::new(
        ExposureLevel::LongFull,
        OrderType::Market,
        Urgency::Aggressive,
    );

    // Limit maker order (cheapest)
    let limit_action = FactoredAction::new(
        ExposureLevel::LongFull,
        OrderType::LimitMaker,
        Urgency::Patient,
    );

    // IoC order (middle)
    let ioc_action = FactoredAction::new(
        ExposureLevel::LongFull,
        OrderType::IoC,
        Urgency::Normal,
    );

    // Verify costs are correctly propagated from OrderType
    assert_eq!(market_action.transaction_cost(), 0.0015);
    assert_eq!(limit_action.transaction_cost(), 0.0005);
    assert_eq!(ioc_action.transaction_cost(), 0.0010);

    // Verify cost calculations for $10,000 trade
    let trade_value = 10_000.0;
    assert_eq!(market_action.calculate_transaction_cost(trade_value), 15.0);  // 0.15% × $10k
    assert_eq!(limit_action.calculate_transaction_cost(trade_value), 5.0);    // 0.05% × $10k
    assert_eq!(ioc_action.calculate_transaction_cost(trade_value), 10.0);     // 0.10% × $10k

    info!("Factored actions correctly use OrderType transaction costs");
}
