//! Comprehensive unit tests for DQNReplayStrategy
//!
//! Tests cover:
//! - CSV loading and validation
//! - Action-to-signal conversion logic
//! - Position state machine transitions
//! - Error handling (out of bounds, invalid data)
//! - Integration with Strategy trait

use backtesting::{
    DQNReplayStrategy, PositionState, SignalType, Strategy, StrategyConfig, StrategyContext,
    TradingActionType,
};
use chrono::Utc;
use common::{Position, Price, Quantity, Symbol};
use rust_decimal::Decimal;
use rust_decimal_macros::dec;
use std::collections::HashMap;
use std::io::Write;
use tempfile::NamedTempFile;
use trading_engine::types::events::MarketEvent;
use uuid::Uuid;

/// Helper to create a test CSV file
fn create_test_csv(actions: &[(usize, u8, [f64; 3])]) -> anyhow::Result<NamedTempFile> {
    let mut file = NamedTempFile::new()?;
    writeln!(file, "bar_index,action,q_buy,q_sell,q_hold")?;
    for (bar_index, action, q_values) in actions {
        writeln!(
            file,
            "{},{},{},{},{}",
            bar_index, action, q_values[0], q_values[1], q_values[2]
        )?;
    }
    file.flush()?;
    Ok(file)
}

/// Helper to create a test strategy context
fn create_test_context() -> StrategyContext {
    StrategyContext {
        current_time: Utc::now(),
        account_balance: dec!(100000.0),
        buying_power: dec!(100000.0),
        positions: HashMap::new(),
        open_orders: HashMap::new(),
        market_prices: HashMap::new(),
        performance: backtesting::strategy_tester::PerformanceMetrics::default(),
    }
}

/// Helper to create a test bar event
fn create_bar_event(symbol: &str, close: f64) -> MarketEvent {
    MarketEvent::Bar {
        symbol: Symbol::from(symbol),
        open: Price::from_f64(close).unwrap(),
        high: Price::from_f64(close + 1.0).unwrap(),
        low: Price::from_f64(close - 1.0).unwrap(),
        close: Price::from_f64(close).unwrap(),
        volume: Quantity::from_f64(1000.0).unwrap(),
        timestamp: Utc::now(),
        interval: "1m".to_string(),
        venue: Some("TEST".to_string()),
    }
}

#[test]
fn test_csv_loading_valid() {
    let csv_file = create_test_csv(&[
        (0, 2, [0.1, 0.2, 0.9]), // Hold
        (1, 0, [0.8, 0.1, 0.3]), // Buy
        (2, 1, [0.2, 0.7, 0.4]), // Sell
    ])
    .unwrap();

    let strategy = DQNReplayStrategy::from_csv(
        "test_strategy".to_string(),
        csv_file.path(),
        Symbol::from("ES"),
    )
    .unwrap();

    assert_eq!(strategy.name(), "test_strategy");
}

#[test]
fn test_csv_loading_invalid_action() {
    let csv_file = create_test_csv(&[(0, 3, [0.1, 0.2, 0.3])]).unwrap();

    let result = DQNReplayStrategy::from_csv(
        "test_strategy".to_string(),
        csv_file.path(),
        Symbol::from("ES"),
    );

    assert!(result.is_err());
    let err = result.unwrap_err().to_string();
    assert!(err.contains("Invalid action value"));
}

#[test]
fn test_csv_loading_gap_in_indices() {
    let csv_file = create_test_csv(&[
        (0, 2, [0.1, 0.2, 0.9]),
        (2, 0, [0.8, 0.1, 0.3]), // Gap: missing index 1
    ])
    .unwrap();

    let result = DQNReplayStrategy::from_csv(
        "test_strategy".to_string(),
        csv_file.path(),
        Symbol::from("ES"),
    );

    assert!(result.is_err());
    let err = result.unwrap_err().to_string();
    assert!(err.contains("Bar index mismatch"));
}

#[test]
fn test_position_state_from_quantity() {
    assert_eq!(
        PositionState::from_quantity(dec!(10.0)),
        PositionState::Long
    );
    assert_eq!(
        PositionState::from_quantity(dec!(-5.0)),
        PositionState::Short
    );
    assert_eq!(
        PositionState::from_quantity(Decimal::ZERO),
        PositionState::Flat
    );
}

#[test]
fn test_trading_action_type_from_int() {
    assert_eq!(
        TradingActionType::from_int(0).unwrap(),
        TradingActionType::Buy
    );
    assert_eq!(
        TradingActionType::from_int(1).unwrap(),
        TradingActionType::Sell
    );
    assert_eq!(
        TradingActionType::from_int(2).unwrap(),
        TradingActionType::Hold
    );
    assert!(TradingActionType::from_int(3).is_err());
}

#[tokio::test]
async fn test_action_conversion_flat_to_long() {
    let csv_file = create_test_csv(&[(0, 0, [0.8, 0.1, 0.3])]).unwrap(); // Buy

    let mut strategy = DQNReplayStrategy::from_csv(
        "test_strategy".to_string(),
        csv_file.path(),
        Symbol::from("ES"),
    )
    .unwrap();

    let context = create_test_context();
    let bar_event = create_bar_event("ES", 100.0);

    let signals = strategy
        .on_market_event(&bar_event, &context)
        .await
        .unwrap();

    assert_eq!(signals.len(), 1);
    assert!(matches!(signals[0].signal_type, SignalType::Buy));
    assert_eq!(signals[0].symbol, Symbol::from("ES"));
}

#[tokio::test]
async fn test_action_conversion_long_to_flat_via_sell() {
    let csv_file = create_test_csv(&[
        (0, 0, [0.8, 0.1, 0.3]), // Buy (Flat -> Long)
        (1, 1, [0.2, 0.7, 0.4]), // Sell (Long -> Close)
    ])
    .unwrap();

    let mut strategy = DQNReplayStrategy::from_csv(
        "test_strategy".to_string(),
        csv_file.path(),
        Symbol::from("ES"),
    )
    .unwrap();

    strategy
        .initialize(dec!(100000.0), StrategyConfig::default())
        .await
        .unwrap();

    let context = create_test_context();

    // First bar: Buy signal
    let bar_event_1 = create_bar_event("ES", 100.0);
    let signals_1 = strategy
        .on_market_event(&bar_event_1, &context)
        .await
        .unwrap();
    assert_eq!(signals_1.len(), 1);
    assert!(matches!(signals_1[0].signal_type, SignalType::Buy));

    // Second bar: Sell signal should close long
    let bar_event_2 = create_bar_event("ES", 101.0);
    let signals_2 = strategy
        .on_market_event(&bar_event_2, &context)
        .await
        .unwrap();
    assert_eq!(signals_2.len(), 1);
    assert!(matches!(signals_2[0].signal_type, SignalType::CloseLong));
}

#[tokio::test]
async fn test_action_conversion_hold_generates_no_signal() {
    let csv_file = create_test_csv(&[(0, 2, [0.1, 0.2, 0.9])]).unwrap(); // Hold

    let mut strategy = DQNReplayStrategy::from_csv(
        "test_strategy".to_string(),
        csv_file.path(),
        Symbol::from("ES"),
    )
    .unwrap();

    let context = create_test_context();
    let bar_event = create_bar_event("ES", 100.0);

    let signals = strategy
        .on_market_event(&bar_event, &context)
        .await
        .unwrap();

    assert_eq!(signals.len(), 0);
}

#[tokio::test]
async fn test_out_of_bounds_error() {
    let csv_file = create_test_csv(&[(0, 0, [0.8, 0.1, 0.3])]).unwrap(); // Only 1 action

    let mut strategy = DQNReplayStrategy::from_csv(
        "test_strategy".to_string(),
        csv_file.path(),
        Symbol::from("ES"),
    )
    .unwrap();

    let context = create_test_context();

    // First bar: OK
    let bar_event_1 = create_bar_event("ES", 100.0);
    let result_1 = strategy.on_market_event(&bar_event_1, &context).await;
    assert!(result_1.is_ok());

    // Second bar: Out of bounds
    let bar_event_2 = create_bar_event("ES", 101.0);
    let result_2 = strategy.on_market_event(&bar_event_2, &context).await;
    assert!(result_2.is_err());
    let err = result_2.unwrap_err().to_string();
    assert!(err.contains("Action index out of bounds"));
}

#[tokio::test]
async fn test_ignore_other_symbols() {
    let csv_file = create_test_csv(&[(0, 0, [0.8, 0.1, 0.3])]).unwrap();

    let mut strategy = DQNReplayStrategy::from_csv(
        "test_strategy".to_string(),
        csv_file.path(),
        Symbol::from("ES"),
    )
    .unwrap();

    let context = create_test_context();
    let bar_event = create_bar_event("NQ", 100.0); // Different symbol

    let signals = strategy
        .on_market_event(&bar_event, &context)
        .await
        .unwrap();

    assert_eq!(signals.len(), 0);
}

#[tokio::test]
async fn test_strategy_finalize() {
    let csv_file = create_test_csv(&[
        (0, 0, [0.8, 0.1, 0.3]), // Buy
        (1, 2, [0.5, 0.3, 0.7]), // Hold
        (2, 1, [0.2, 0.8, 0.4]), // Sell
    ])
    .unwrap();

    let mut strategy = DQNReplayStrategy::from_csv(
        "test_strategy".to_string(),
        csv_file.path(),
        Symbol::from("ES"),
    )
    .unwrap();

    strategy
        .initialize(dec!(100000.0), StrategyConfig::default())
        .await
        .unwrap();

    let mut context = create_test_context();

    // Process all bars
    for i in 0..3 {
        let bar_event = create_bar_event("ES", 100.0 + i as f64);
        let _ = strategy.on_market_event(&bar_event, &context).await;
    }

    // Update final balance
    context.account_balance = dec!(105000.0);

    let result = strategy.finalize(&context).await.unwrap();

    assert_eq!(result.strategy_name, "test_strategy");
    assert_eq!(result.final_value, dec!(105000.0));
    assert_eq!(result.total_return, dec!(0.05)); // 5% return
}

#[tokio::test]
async fn test_position_update_callback() {
    let csv_file = create_test_csv(&[(0, 0, [0.8, 0.1, 0.3])]).unwrap();

    let mut strategy = DQNReplayStrategy::from_csv(
        "test_strategy".to_string(),
        csv_file.path(),
        Symbol::from("ES"),
    )
    .unwrap();

    let context = create_test_context();

    // Simulate position update with full Position struct
    let position = Position {
        id: Uuid::new_v4(),
        symbol: "ES".to_string(),
        quantity: dec!(10.0),
        avg_price: dec!(100.0),
        avg_cost: dec!(100.0),
        basis: dec!(1000.0),
        average_price: dec!(100.0),
        market_value: dec!(1000.0),
        unrealized_pnl: Decimal::ZERO,
        realized_pnl: Decimal::ZERO,
        created_at: Utc::now(),
        updated_at: Utc::now(),
        last_updated: Utc::now(),
        current_price: Some(dec!(100.0)),
        notional_value: dec!(1000.0),
        margin_requirement: dec!(200.0),
    };

    let result = strategy.on_position_update(&position, &context).await;
    assert!(result.is_ok());
}

#[tokio::test]
async fn test_get_state() {
    let csv_file = create_test_csv(&[
        (0, 0, [0.8, 0.1, 0.3]),
        (1, 1, [0.2, 0.7, 0.4]),
        (2, 2, [0.5, 0.3, 0.7]),
    ])
    .unwrap();

    let strategy = DQNReplayStrategy::from_csv(
        "test_strategy".to_string(),
        csv_file.path(),
        Symbol::from("ES"),
    )
    .unwrap();

    let state = strategy.get_state().await.unwrap();

    assert_eq!(state["name"], "test_strategy");
    assert_eq!(state["current_index"], 0);
    assert_eq!(state["total_actions"], 3);
    assert!(state["metrics"].is_object());
}

#[tokio::test]
async fn test_flat_to_short_transition() {
    let csv_file = create_test_csv(&[(0, 1, [0.2, 0.8, 0.3])]).unwrap(); // Sell from flat

    let mut strategy = DQNReplayStrategy::from_csv(
        "test_strategy".to_string(),
        csv_file.path(),
        Symbol::from("ES"),
    )
    .unwrap();

    let context = create_test_context();
    let bar_event = create_bar_event("ES", 100.0);

    let signals = strategy
        .on_market_event(&bar_event, &context)
        .await
        .unwrap();

    assert_eq!(signals.len(), 1);
    assert!(matches!(signals[0].signal_type, SignalType::Short));
}

#[tokio::test]
async fn test_short_to_long_flip() {
    let csv_file = create_test_csv(&[
        (0, 1, [0.2, 0.8, 0.3]), // Sell (Flat -> Short)
        (1, 0, [0.8, 0.1, 0.3]), // Buy (Short -> Cover)
    ])
    .unwrap();

    let mut strategy = DQNReplayStrategy::from_csv(
        "test_strategy".to_string(),
        csv_file.path(),
        Symbol::from("ES"),
    )
    .unwrap();

    strategy
        .initialize(dec!(100000.0), StrategyConfig::default())
        .await
        .unwrap();

    let context = create_test_context();

    // First bar: Sell to open short
    let bar_event_1 = create_bar_event("ES", 100.0);
    let signals_1 = strategy
        .on_market_event(&bar_event_1, &context)
        .await
        .unwrap();
    assert_eq!(signals_1.len(), 1);
    assert!(matches!(signals_1[0].signal_type, SignalType::Short));

    // Second bar: Buy should cover short
    let bar_event_2 = create_bar_event("ES", 99.0);
    let signals_2 = strategy
        .on_market_event(&bar_event_2, &context)
        .await
        .unwrap();
    assert_eq!(signals_2.len(), 1);
    assert!(matches!(signals_2[0].signal_type, SignalType::Cover));
}
