#![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,
)]
//! Comprehensive tests for continuous transaction costs
//!
//! Tests cost computation, slippage models, gradients, and order type differences
//! for continuous position sizing in PPO.

use ml::ppo::continuous_transaction_costs::{
    conservative_cost_model, default_hft_cost_model, zero_cost_model,
    ContinuousTransactionCosts, OrderType, SlippageModel,
};

#[test]
fn test_order_type_cost_bps_values() {
    assert_eq!(OrderType::Market.cost_bps(), 15.0);
    assert_eq!(OrderType::LimitMaker.cost_bps(), 5.0);
    assert_eq!(OrderType::IoC.cost_bps(), 10.0);
}

#[test]
fn test_order_type_cost_decimal_conversion() {
    assert!((OrderType::Market.cost_decimal() - 0.0015).abs() < 1e-9);
    assert!((OrderType::LimitMaker.cost_decimal() - 0.0005).abs() < 1e-9);
    assert!((OrderType::IoC.cost_decimal() - 0.001).abs() < 1e-9);
}

#[test]
fn test_order_type_cost_ordering() {
    assert!(OrderType::Market.cost_bps() > OrderType::IoC.cost_bps());
    assert!(OrderType::IoC.cost_bps() > OrderType::LimitMaker.cost_bps());
}

#[test]
fn test_compute_cost_no_slippage_small_trade() {
    let costs = ContinuousTransactionCosts::new(
        5.0,                 // 5 bps
        SlippageModel::None,
        OrderType::LimitMaker,
        5000.0,
    );

    // 0.5 contracts @ $5000 = $2500 trade
    // Cost: $2500 × 0.0005 = $1.25
    let cost = costs.compute_cost(0.5, 0.0, 0.5);
    assert!((cost - 1.25).abs() < 0.01);
}

#[test]
fn test_compute_cost_no_slippage_unit_trade() {
    let costs = ContinuousTransactionCosts::new(
        5.0,
        SlippageModel::None,
        OrderType::LimitMaker,
        5000.0,
    );

    // 1 contract @ $5000 = $5000 trade
    // Cost: $5000 × 0.0005 = $2.50
    let cost = costs.compute_cost(1.0, 0.0, 1.0);
    assert!((cost - 2.5).abs() < 0.01);
}

#[test]
fn test_compute_cost_no_slippage_large_trade() {
    let costs = ContinuousTransactionCosts::new(
        5.0,
        SlippageModel::None,
        OrderType::LimitMaker,
        5000.0,
    );

    // 10 contracts @ $5000 = $50,000 trade
    // Cost: $50,000 × 0.0005 = $25.00
    let cost = costs.compute_cost(10.0, 0.0, 10.0);
    assert!((cost - 25.0).abs() < 0.1);
}

#[test]
fn test_compute_cost_linear_slippage_scaling() {
    let costs = ContinuousTransactionCosts::new(
        5.0, // 5 bps base
        SlippageModel::Linear { slope: 0.1 },
        OrderType::LimitMaker,
        5000.0,
    );

    // 1 contract: base=$5000×0.0005=$2.50, slippage=$5000×0.00001=$0.05, total=$2.55
    let cost_1 = costs.compute_cost(1.0, 0.0, 1.0);
    assert!((cost_1 - 2.55).abs() < 0.01);

    // 2 contracts: base=$10000×0.0005=$5.00, slippage=$10000×0.00002=$0.20, total=$5.20
    let cost_2 = costs.compute_cost(2.0, 0.0, 2.0);
    assert!((cost_2 - 5.20).abs() < 0.02);

    // 5 contracts: base=$25000×0.0005=$12.50, slippage=$25000×0.00005=$1.25, total=$13.75
    let cost_5 = costs.compute_cost(5.0, 0.0, 5.0);
    assert!((cost_5 - 13.75).abs() < 0.05);
}

#[test]
fn test_compute_cost_quadratic_slippage_scaling() {
    let costs = ContinuousTransactionCosts::new(
        5.0,
        SlippageModel::Quadratic { coefficient: 0.5 },
        OrderType::LimitMaker,
        5000.0,
    );

    // 1 contract: base=2.50, slippage=0.25, total=2.75
    let cost_1 = costs.compute_cost(1.0, 0.0, 1.0);
    assert!((cost_1 - 2.75).abs() < 0.1);

    // 2 contracts: base=5.00, slippage=2.00, total=7.00
    let cost_2 = costs.compute_cost(2.0, 0.0, 2.0);
    assert!((cost_2 - 7.0).abs() < 0.2);

    // 3 contracts: base=7.50, slippage=6.75, total=14.25
    let cost_3 = costs.compute_cost(3.0, 0.0, 3.0);
    assert!((cost_3 - 14.25).abs() < 0.5);
}

#[test]
fn test_compute_cost_quadratic_grows_faster_than_linear() {
    let linear = ContinuousTransactionCosts::new(
        5.0,
        SlippageModel::Linear { slope: 0.5 },
        OrderType::LimitMaker,
        5000.0,
    );

    let quadratic = ContinuousTransactionCosts::new(
        5.0,
        SlippageModel::Quadratic { coefficient: 0.5 },
        OrderType::LimitMaker,
        5000.0,
    );

    // At 1 contract, they're equal: both have slippage of 0.5 bps
    let linear_1 = linear.compute_cost(1.0, 0.0, 1.0);
    let quadratic_1 = quadratic.compute_cost(1.0, 0.0, 1.0);
    assert!((linear_1 - quadratic_1).abs() < 0.01);

    // At large sizes, quadratic > linear
    // Linear: slope × 5 = 2.5 bps slippage
    // Quadratic: coeff × 25 = 12.5 bps slippage
    let linear_5 = linear.compute_cost(5.0, 0.0, 5.0);
    let quadratic_5 = quadratic.compute_cost(5.0, 0.0, 5.0);
    assert!(quadratic_5 > linear_5);
}

#[test]
fn test_compute_cost_zero_position_change() {
    let costs = default_hft_cost_model();

    let cost = costs.compute_cost(0.0, 0.0, 0.0);
    assert_eq!(cost, 0.0);
}

#[test]
fn test_compute_cost_tiny_position_change() {
    let costs = default_hft_cost_model();

    // Below threshold (1e-6)
    let cost = costs.compute_cost(1e-8, 0.0, 1e-8);
    assert_eq!(cost, 0.0);
}

#[test]
fn test_compute_cost_negative_position_change() {
    let costs = default_hft_cost_model();

    // Should use absolute value
    let cost_pos = costs.compute_cost(1.0, 0.0, 1.0);
    let cost_neg = costs.compute_cost(-1.0, 0.0, -1.0);

    assert!((cost_pos - cost_neg).abs() < 0.01);
}

#[test]
fn test_compute_cost_different_order_types() {
    let market = ContinuousTransactionCosts::new(
        15.0,
        SlippageModel::None,
        OrderType::Market,
        5000.0,
    );

    let limit = ContinuousTransactionCosts::new(
        5.0,
        SlippageModel::None,
        OrderType::LimitMaker,
        5000.0,
    );

    let ioc = ContinuousTransactionCosts::new(
        10.0,
        SlippageModel::None,
        OrderType::IoC,
        5000.0,
    );

    // 1 contract @ $5000
    let market_cost = market.compute_cost(1.0, 0.0, 1.0);
    let limit_cost = limit.compute_cost(1.0, 0.0, 1.0);
    let ioc_cost = ioc.compute_cost(1.0, 0.0, 1.0);

    // Market (15 bps): $7.50
    assert!((market_cost - 7.5).abs() < 0.1);
    // LimitMaker (5 bps): $2.50
    assert!((limit_cost - 2.5).abs() < 0.1);
    // IoC (10 bps): $5.00
    assert!((ioc_cost - 5.0).abs() < 0.1);

    // Ordering
    assert!(market_cost > ioc_cost);
    assert!(ioc_cost > limit_cost);
}

#[test]
fn test_compute_cost_gradient_linear_slippage() {
    let costs = ContinuousTransactionCosts::new(
        5.0,
        SlippageModel::Linear { slope: 0.1 },
        OrderType::LimitMaker,
        5000.0,
    );

    // Gradient = 5000 × (5.0 + 0.1) / 10000 = 2.55
    let grad = costs.compute_cost_gradient(1.0);
    assert!((grad - 2.55).abs() < 0.01);

    // Linear: gradient independent of position
    let grad_2 = costs.compute_cost_gradient(2.0);
    assert!((grad_2 - 2.55).abs() < 0.01);
}

#[test]
fn test_compute_cost_gradient_quadratic_slippage() {
    let costs = ContinuousTransactionCosts::new(
        5.0,
        SlippageModel::Quadratic { coefficient: 0.5 },
        OrderType::LimitMaker,
        5000.0,
    );

    // At pos=1.0: gradient = 5000 × (5.0 + 2×0.5×1.0) / 10000 = 3.0
    let grad_1 = costs.compute_cost_gradient(1.0);
    assert!((grad_1 - 3.0).abs() < 0.01);

    // At pos=2.0: gradient = 5000 × (5.0 + 2×0.5×2.0) / 10000 = 3.5
    let grad_2 = costs.compute_cost_gradient(2.0);
    assert!((grad_2 - 3.5).abs() < 0.01);

    // At pos=5.0: gradient = 5000 × (5.0 + 5.0) / 10000 = 5.0
    let grad_5 = costs.compute_cost_gradient(5.0);
    assert!((grad_5 - 5.0).abs() < 0.01);
}

#[test]
fn test_compute_cost_gradient_no_slippage() {
    let costs = ContinuousTransactionCosts::new(
        5.0,
        SlippageModel::None,
        OrderType::LimitMaker,
        5000.0,
    );

    // Gradient = 5000 × 5.0 / 10000 = 2.5
    let grad = costs.compute_cost_gradient(1.0);
    assert!((grad - 2.5).abs() < 0.01);

    // No slippage: constant gradient
    let grad_5 = costs.compute_cost_gradient(5.0);
    assert!((grad_5 - 2.5).abs() < 0.01);
}

#[test]
fn test_compute_cost_gradient_different_contract_values() {
    let cheap = ContinuousTransactionCosts::new(
        5.0,
        SlippageModel::None,
        OrderType::LimitMaker,
        1000.0,
    );

    let expensive = ContinuousTransactionCosts::new(
        5.0,
        SlippageModel::None,
        OrderType::LimitMaker,
        10000.0,
    );

    let grad_cheap = cheap.compute_cost_gradient(1.0);
    let grad_expensive = expensive.compute_cost_gradient(1.0);

    // 10x contract value → 10x gradient
    assert!((grad_expensive / grad_cheap - 10.0).abs() < 0.1);
}

#[test]
fn test_total_cost_bps_no_slippage() {
    let costs = ContinuousTransactionCosts::new(
        5.0,
        SlippageModel::None,
        OrderType::LimitMaker,
        5000.0,
    );

    assert_eq!(costs.total_cost_bps(1.0), 5.0);
    assert_eq!(costs.total_cost_bps(10.0), 5.0);
}

#[test]
fn test_total_cost_bps_linear_slippage() {
    let costs = ContinuousTransactionCosts::new(
        5.0,
        SlippageModel::Linear { slope: 0.1 },
        OrderType::LimitMaker,
        5000.0,
    );

    // 1 contract: 5.0 + 0.1 = 5.1 bps
    assert!((costs.total_cost_bps(1.0) - 5.1).abs() < 0.01);

    // 2 contracts: 5.0 + 0.2 = 5.2 bps
    assert!((costs.total_cost_bps(2.0) - 5.2).abs() < 0.01);
}

#[test]
fn test_total_cost_bps_quadratic_slippage() {
    let costs = ContinuousTransactionCosts::new(
        5.0,
        SlippageModel::Quadratic { coefficient: 0.5 },
        OrderType::LimitMaker,
        5000.0,
    );

    // 1 contract: 5.0 + 0.5×1 = 5.5 bps
    assert!((costs.total_cost_bps(1.0) - 5.5).abs() < 0.01);

    // 2 contracts: 5.0 + 0.5×4 = 7.0 bps
    assert!((costs.total_cost_bps(2.0) - 7.0).abs() < 0.01);

    // 3 contracts: 5.0 + 0.5×9 = 9.5 bps
    assert!((costs.total_cost_bps(3.0) - 9.5).abs() < 0.01);
}

#[test]
fn test_default_hft_cost_model_parameters() {
    let costs = default_hft_cost_model();

    assert_eq!(costs.base_cost_bps, 5.0);
    assert_eq!(costs.order_type, OrderType::LimitMaker);
    assert_eq!(costs.contract_value, 5000.0);

    matches!(costs.slippage_model, SlippageModel::Linear { slope: 0.1 });
}

#[test]
fn test_default_hft_cost_model_reasonable_costs() {
    let costs = default_hft_cost_model();

    // 1 contract should cost around $3
    let cost_1 = costs.compute_cost(1.0, 0.0, 1.0);
    assert!(cost_1 > 2.5 && cost_1 < 3.5);

    // 5 contracts should cost around $15
    let cost_5 = costs.compute_cost(5.0, 0.0, 5.0);
    assert!(cost_5 > 13.0 && cost_5 < 17.0);
}

#[test]
fn test_conservative_cost_model_parameters() {
    let costs = conservative_cost_model();

    assert_eq!(costs.base_cost_bps, 15.0);
    assert_eq!(costs.order_type, OrderType::Market);
    assert_eq!(costs.contract_value, 5000.0);

    matches!(costs.slippage_model, SlippageModel::Quadratic { .. });
}

#[test]
fn test_conservative_cost_model_higher_than_default() {
    let default_costs = default_hft_cost_model();
    let conservative_costs = conservative_cost_model();

    let default_cost = default_costs.compute_cost(1.0, 0.0, 1.0);
    let conservative_cost = conservative_costs.compute_cost(1.0, 0.0, 1.0);

    assert!(conservative_cost > default_cost);
}

#[test]
fn test_zero_cost_model_parameters() {
    let costs = zero_cost_model();

    assert_eq!(costs.base_cost_bps, 0.0);
    matches!(costs.slippage_model, SlippageModel::None);
}

#[test]
fn test_zero_cost_model_always_zero() {
    let costs = zero_cost_model();

    assert_eq!(costs.compute_cost(0.0, 0.0, 0.0), 0.0);
    assert_eq!(costs.compute_cost(1.0, 0.0, 1.0), 0.0);
    assert_eq!(costs.compute_cost(100.0, 0.0, 100.0), 0.0);
}

#[test]
fn test_cost_scales_linearly_with_contract_value() {
    let base = ContinuousTransactionCosts::new(
        5.0,
        SlippageModel::None,
        OrderType::LimitMaker,
        5000.0,
    );

    let double = ContinuousTransactionCosts::new(
        5.0,
        SlippageModel::None,
        OrderType::LimitMaker,
        10000.0,
    );

    let cost_base = base.compute_cost(1.0, 0.0, 1.0);
    let cost_double = double.compute_cost(1.0, 0.0, 1.0);

    // 2x contract value → 2x cost
    assert!((cost_double / cost_base - 2.0).abs() < 0.01);
}

#[test]
fn test_fractional_position_changes() {
    let costs = default_hft_cost_model();

    // 0.1 contracts
    let cost_0_1 = costs.compute_cost(0.1, 0.0, 0.1);
    assert!(cost_0_1 > 0.0 && cost_0_1 < 1.0);

    // 0.5 contracts
    let cost_0_5 = costs.compute_cost(0.5, 0.0, 0.5);
    assert!(cost_0_5 > cost_0_1);

    // 0.9 contracts
    let cost_0_9 = costs.compute_cost(0.9, 0.0, 0.9);
    assert!(cost_0_9 > cost_0_5);
}

#[test]
fn test_large_position_changes() {
    let costs = ContinuousTransactionCosts::new(
        5.0,
        SlippageModel::Quadratic { coefficient: 0.5 },
        OrderType::LimitMaker,
        5000.0,
    );

    // 10 contracts
    let cost_10 = costs.compute_cost(10.0, 0.0, 10.0);

    // 100 contracts
    let cost_100 = costs.compute_cost(100.0, 0.0, 100.0);

    // Quadratic slippage: 100 contracts >> 10x cost of 10 contracts
    assert!(cost_100 > cost_10 * 50.0);
}

#[test]
fn test_cost_symmetry_buy_vs_sell() {
    let costs = default_hft_cost_model();

    // Buy: 0 → 5
    let buy_cost = costs.compute_cost(5.0, 0.0, 5.0);

    // Sell: 5 → 0
    let sell_cost = costs.compute_cost(-5.0, 5.0, 0.0);

    // Should be identical (abs value used)
    assert!((buy_cost - sell_cost).abs() < 0.01);
}

#[test]
fn test_new_constructor_custom_values() {
    let costs = ContinuousTransactionCosts::new(
        10.0,
        SlippageModel::Linear { slope: 0.5 },
        OrderType::IoC,
        10000.0,
    );

    assert_eq!(costs.base_cost_bps, 10.0);
    assert_eq!(costs.order_type, OrderType::IoC);
    assert_eq!(costs.contract_value, 10000.0);
}

#[test]
fn test_extreme_slippage_coefficient() {
    let high_slippage = ContinuousTransactionCosts::new(
        5.0,
        SlippageModel::Quadratic { coefficient: 5.0 }, // Very high
        OrderType::LimitMaker,
        5000.0,
    );

    // 1 contract: base=2.50, slippage=2.50, total=5.00
    let cost_1 = high_slippage.compute_cost(1.0, 0.0, 1.0);
    assert!((cost_1 - 5.0).abs() < 0.1);

    // 2 contracts: base=5.00, slippage=20.00, total=25.00
    let cost_2 = high_slippage.compute_cost(2.0, 0.0, 2.0);
    assert!((cost_2 - 25.0).abs() < 0.5);
}

#[test]
fn test_gradient_consistency_with_cost() {
    let costs = ContinuousTransactionCosts::new(
        5.0,
        SlippageModel::Linear { slope: 0.1 },
        OrderType::LimitMaker,
        5000.0,
    );

    // Numerical gradient check (finite difference)
    let pos = 1.0;
    let delta = 0.0001;

    let cost_plus = costs.compute_cost(pos + delta, 0.0, pos + delta);
    let cost_minus = costs.compute_cost(pos - delta, 0.0, pos - delta);
    let numerical_grad = (cost_plus - cost_minus) / (2.0 * delta);

    let analytical_grad = costs.compute_cost_gradient(pos);

    // Should be very close
    assert!((analytical_grad - numerical_grad).abs() < 0.1);
}

#[test]
fn test_cost_components_breakdown() {
    let costs = ContinuousTransactionCosts::new(
        5.0,
        SlippageModel::Linear { slope: 0.2 },
        OrderType::LimitMaker,
        5000.0,
    );

    // 1 contract @ $5000
    let total_cost = costs.compute_cost(1.0, 0.0, 1.0);

    // Base cost: $5000 × 0.0005 = $2.50
    let expected_base = 2.5;

    // Slippage: $5000 × (0.2 / 10000) = $5000 × 0.00002 = $0.10
    let expected_slippage = 0.10;

    let expected_total = expected_base + expected_slippage;

    assert!((total_cost - expected_total).abs() < 0.01);
}
