#![allow(unexpected_cfgs)]
#![cfg(feature = "__backtesting_integration")]
//! Tests for report generation and result aggregation
//!
//! Target Coverage: 40%+ for result aggregation, report formatting, and data export

use anyhow::Result;
use chrono::{Duration, Utc};
use rust_decimal::Decimal;
use std::collections::HashMap;
use std::sync::Arc;

mod mock_repositories;

use backtesting_service::foxhunt::tli::BacktestStatus;
use backtesting_service::performance::PerformanceAnalyzer;
use backtesting_service::repositories::TradingRepository;
use backtesting_service::strategy_engine::{BacktestTrade, TradeSide};
use config::structures::BacktestingPerformanceConfig;
use mock_repositories::*;

/// Helper to create a sample trade
fn create_trade(
    id: u32,
    symbol: &str,
    entry_price: f64,
    exit_price: f64,
    quantity: f64,
    days_offset: i64,
) -> BacktestTrade {
    let base_time = Utc::now() - Duration::days(100);
    let entry_time = base_time + Duration::days(days_offset);
    let exit_time = entry_time + Duration::days(1);

    let pnl = (exit_price - entry_price) * quantity;
    let return_percent = pnl / (entry_price * quantity);

    BacktestTrade {
        trade_id: format!("trade_{}", id),
        symbol: symbol.to_string(),
        side: TradeSide::Buy,
        quantity: Decimal::from_f64_retain(quantity).unwrap_or(Decimal::ZERO),
        entry_price: Decimal::from_f64_retain(entry_price).unwrap_or(Decimal::ZERO),
        exit_price: Decimal::from_f64_retain(exit_price).unwrap_or(Decimal::ZERO),
        entry_time,
        exit_time,
        pnl: Decimal::from_f64_retain(pnl).unwrap_or(Decimal::ZERO),
        return_percent: Decimal::from_f64_retain(return_percent).unwrap_or(Decimal::ZERO),
        entry_signal: "signal_entry".to_string(),
        exit_signal: "signal_exit".to_string(),
    }
}

/// Test saving backtest results
#[tokio::test]
async fn test_save_backtest_results() -> Result<()> {
    let trading_repo = MockTradingRepository::new();

    let trades = vec![
        create_trade(1, "AAPL", 150.0, 155.0, 100.0, 0),
        create_trade(2, "AAPL", 155.0, 160.0, 100.0, 1),
    ];

    let config = BacktestingPerformanceConfig::default();
    let analyzer = PerformanceAnalyzer::new(&config)?;
    let metrics = analyzer.calculate_metrics(&trades, 100000.0);

    trading_repo
        .save_backtest_results("backtest_001", &trades, &metrics)
        .await?;

    // Verify saved
    let (loaded_trades, loaded_metrics) =
        trading_repo.load_backtest_results("backtest_001").await?;

    assert_eq!(loaded_trades.len(), 2);
    assert_eq!(loaded_metrics.total_trades, 2);

    Ok(())
}

/// Test loading backtest results
#[tokio::test]
async fn test_load_backtest_results() -> Result<()> {
    let trading_repo = MockTradingRepository::new();

    let trades = vec![
        create_trade(1, "MSFT", 200.0, 210.0, 50.0, 0),
        create_trade(2, "MSFT", 210.0, 205.0, 50.0, 1),
    ];

    let config = BacktestingPerformanceConfig::default();
    let analyzer = PerformanceAnalyzer::new(&config)?;
    let metrics = analyzer.calculate_metrics(&trades, 50000.0);

    // Save
    trading_repo
        .save_backtest_results("backtest_002", &trades, &metrics)
        .await?;

    // Load
    let (loaded_trades, loaded_metrics) =
        trading_repo.load_backtest_results("backtest_002").await?;

    assert_eq!(loaded_trades.len(), 2);
    assert_eq!(loaded_trades[0].symbol, "MSFT");
    assert!((loaded_metrics.total_return - metrics.total_return).abs() < 0.01);

    Ok(())
}

/// Test creating backtest record
#[tokio::test]
async fn test_create_backtest_record() -> Result<()> {
    let trading_repo = MockTradingRepository::new();

    let start_date = Utc::now() - Duration::days(30);
    let end_date = Utc::now();
    let symbols = vec!["AAPL".to_string(), "MSFT".to_string()];
    let parameters = HashMap::new();

    trading_repo
        .create_backtest_record(
            "backtest_003",
            "buy_and_hold",
            &symbols,
            start_date,
            end_date,
            100000.0,
            &parameters,
            "Test backtest",
        )
        .await?;

    // Verify record created
    let backtests = trading_repo.list_backtests(10, 0, None, None).await?;

    assert_eq!(backtests.len(), 1);
    assert_eq!(backtests[0].backtest_id, "backtest_003");
    assert_eq!(backtests[0].strategy_name, "buy_and_hold");
    assert_eq!(backtests[0].symbols.len(), 2);

    Ok(())
}

/// Test updating backtest status
#[tokio::test]
async fn test_update_backtest_status() -> Result<()> {
    let trading_repo = MockTradingRepository::new();

    // Create record
    let start_date = Utc::now() - Duration::days(10);
    let end_date = Utc::now();

    trading_repo
        .create_backtest_record(
            "backtest_004",
            "ma_crossover",
            &["AAPL".to_string()],
            start_date,
            end_date,
            50000.0,
            &HashMap::new(),
            "Test status update",
        )
        .await?;

    // Update status to running
    trading_repo
        .update_backtest_status("backtest_004", BacktestStatus::Running, None)
        .await?;

    // Update status to completed
    trading_repo
        .update_backtest_status("backtest_004", BacktestStatus::Completed, None)
        .await?;

    // Verify status
    let backtests = trading_repo
        .list_backtests(10, 0, None, Some(BacktestStatus::Completed))
        .await?;

    assert_eq!(backtests.len(), 1);
    assert_eq!(backtests[0].status, BacktestStatus::Completed);

    Ok(())
}

/// Test listing backtests with filters
#[tokio::test]
async fn test_list_backtests_with_filters() -> Result<()> {
    let trading_repo = MockTradingRepository::new();

    let start_date = Utc::now() - Duration::days(10);
    let end_date = Utc::now();

    // Create multiple backtests
    for i in 0..5 {
        let strategy = if i % 2 == 0 {
            "buy_and_hold"
        } else {
            "ma_crossover"
        };
        trading_repo
            .create_backtest_record(
                &format!("backtest_{:03}", i),
                strategy,
                &["AAPL".to_string()],
                start_date,
                end_date,
                100000.0,
                &HashMap::new(),
                &format!("Test backtest {}", i),
            )
            .await?;
    }

    // List all
    let all = trading_repo.list_backtests(10, 0, None, None).await?;
    assert_eq!(all.len(), 5);

    // Filter by strategy
    let buy_hold = trading_repo
        .list_backtests(10, 0, Some("buy_and_hold".to_string()), None)
        .await?;
    assert_eq!(buy_hold.len(), 3);

    let ma_cross = trading_repo
        .list_backtests(10, 0, Some("ma_crossover".to_string()), None)
        .await?;
    assert_eq!(ma_cross.len(), 2);

    Ok(())
}

/// Test pagination
#[tokio::test]
async fn test_backtest_list_pagination() -> Result<()> {
    let trading_repo = MockTradingRepository::new();

    let start_date = Utc::now() - Duration::days(10);
    let end_date = Utc::now();

    // Create 10 backtests
    for i in 0..10 {
        trading_repo
            .create_backtest_record(
                &format!("backtest_{:03}", i),
                "buy_and_hold",
                &["AAPL".to_string()],
                start_date,
                end_date,
                100000.0,
                &HashMap::new(),
                &format!("Test {}", i),
            )
            .await?;
    }

    // Get first page (5 items)
    let page1 = trading_repo.list_backtests(5, 0, None, None).await?;
    assert_eq!(page1.len(), 5);

    // Get second page (5 items)
    let page2 = trading_repo.list_backtests(5, 5, None, None).await?;
    assert_eq!(page2.len(), 5);

    // Verify no overlap
    assert_ne!(page1[0].backtest_id, page2[0].backtest_id);

    Ok(())
}

/// Test performance metrics aggregation
#[tokio::test]
async fn test_metrics_aggregation() -> Result<()> {
    let config = BacktestingPerformanceConfig::default();
    let analyzer = PerformanceAnalyzer::new(&config)?;

    let trades = vec![
        create_trade(1, "AAPL", 100.0, 110.0, 100.0, 0), // +$1000
        create_trade(2, "MSFT", 200.0, 210.0, 50.0, 1),  // +$500
        create_trade(3, "GOOGL", 120.0, 115.0, 80.0, 2), // -$400
    ];

    let metrics = analyzer.calculate_metrics(&trades, 100000.0);

    // Verify aggregated metrics
    assert_eq!(metrics.total_trades, 3);
    assert_eq!(metrics.winning_trades, 2);
    assert_eq!(metrics.losing_trades, 1);
    assert!((metrics.total_return - 1.1).abs() < 0.1); // ~$1100 profit on $100k

    Ok(())
}

/// Test drawdown period identification
#[tokio::test]
async fn test_drawdown_period_identification() -> Result<()> {
    let config = BacktestingPerformanceConfig::default();
    let analyzer = PerformanceAnalyzer::new(&config)?;

    // Create equity curve with known drawdown
    let trades = vec![
        create_trade(1, "AAPL", 100.0, 120.0, 100.0, 0), // Peak
        create_trade(2, "AAPL", 120.0, 110.0, 100.0, 1), // Drawdown
        create_trade(3, "AAPL", 110.0, 90.0, 100.0, 2),  // Trough
        create_trade(4, "AAPL", 90.0, 115.0, 100.0, 3),  // Recovery
    ];

    let equity_curve = analyzer.generate_equity_curve(&trades, 100000.0);
    let drawdown_periods = analyzer.identify_drawdown_periods(&equity_curve);

    assert!(
        !drawdown_periods.is_empty(),
        "Should identify drawdown periods"
    );

    if let Some(first_dd) = drawdown_periods.first() {
        assert!(first_dd.drawdown_percent > 0.0);
        assert!(first_dd.peak_value > first_dd.trough_value);
    }

    Ok(())
}

/// Test time series data storage
#[tokio::test]
async fn test_time_series_storage() -> Result<()> {
    let trading_repo = MockTradingRepository::new();

    let timestamp = Utc::now();

    // Store multiple time series points
    for i in 0..10 {
        let ts = timestamp + Duration::hours(i);
        let equity = 100000.0 + (i as f64 * 1000.0);
        let drawdown = if i > 5 { 0.05 } else { 0.0 };

        trading_repo
            .store_time_series_data("backtest_005", ts, equity, drawdown)
            .await?;
    }

    // Mock repository doesn't retrieve time series, but this tests the interface
    Ok(())
}

/// Test result export for different formats
#[tokio::test]
async fn test_result_export_formats() -> Result<()> {
    let trading_repo = MockTradingRepository::new();

    let trades = vec![
        create_trade(1, "AAPL", 150.0, 160.0, 100.0, 0),
        create_trade(2, "AAPL", 160.0, 155.0, 100.0, 1),
    ];

    let config = BacktestingPerformanceConfig::default();
    let analyzer = PerformanceAnalyzer::new(&config)?;
    let metrics = analyzer.calculate_metrics(&trades, 100000.0);

    // Save in standard format
    trading_repo
        .save_backtest_results("export_test", &trades, &metrics)
        .await?;

    // Load and verify can be serialized
    let (loaded_trades, loaded_metrics) = trading_repo.load_backtest_results("export_test").await?;

    // Should be serializable to JSON
    let _trades_json = serde_json::to_string(&loaded_trades)?;
    let _metrics_json = serde_json::to_string(&loaded_metrics)?;

    Ok(())
}

/// Test comprehensive report generation
#[tokio::test]
async fn test_comprehensive_report() -> Result<()> {
    let config = BacktestingPerformanceConfig::default();
    let analyzer = PerformanceAnalyzer::new(&config)?;

    let trades = vec![
        create_trade(1, "AAPL", 150.0, 165.0, 100.0, 0),
        create_trade(2, "MSFT", 200.0, 220.0, 50.0, 5),
        create_trade(3, "GOOGL", 120.0, 115.0, 80.0, 10),
        create_trade(4, "AAPL", 165.0, 175.0, 100.0, 15),
        create_trade(5, "MSFT", 220.0, 210.0, 50.0, 20),
    ];

    let initial_capital = 100000.0;
    let metrics = analyzer.calculate_metrics(&trades, initial_capital);

    // Generate all report components
    let equity_curve = analyzer.generate_equity_curve(&trades, initial_capital);
    let drawdown_periods = analyzer.identify_drawdown_periods(&equity_curve);
    let rolling_metrics = analyzer.calculate_rolling_metrics(&trades, 7);

    // Verify comprehensive report data
    assert_eq!(metrics.total_trades, 5);
    assert!(!equity_curve.is_empty());
    assert!(!rolling_metrics.rolling_sharpe.is_empty());

    // All components should be present
    assert!(metrics.sharpe_ratio != 0.0 || metrics.total_trades > 0);
    assert!(metrics.max_drawdown >= 0.0);

    Ok(())
}

/// Test empty results handling
#[tokio::test]
async fn test_empty_results() -> Result<()> {
    let trading_repo = MockTradingRepository::new();

    let trades: Vec<BacktestTrade> = vec![];
    let config = BacktestingPerformanceConfig::default();
    let analyzer = PerformanceAnalyzer::new(&config)?;
    let metrics = analyzer.calculate_metrics(&trades, 100000.0);

    trading_repo
        .save_backtest_results("empty_test", &trades, &metrics)
        .await?;

    let (loaded_trades, loaded_metrics) = trading_repo.load_backtest_results("empty_test").await?;

    assert_eq!(loaded_trades.len(), 0);
    assert_eq!(loaded_metrics.total_trades, 0);

    Ok(())
}

/// Test concurrent report generation
#[tokio::test]
async fn test_concurrent_report_generation() -> Result<()> {
    let trading_repo = Arc::new(MockTradingRepository::new());
    let config = BacktestingPerformanceConfig::default();
    let analyzer = Arc::new(PerformanceAnalyzer::new(&config)?);

    let mut handles = Vec::new();

    for i in 0..5 {
        let repo_clone = trading_repo.clone();
        let analyzer_clone = analyzer.clone();

        let handle = tokio::spawn(async move {
            let trades = vec![
                create_trade(1, "AAPL", 150.0, 155.0, 100.0, 0),
                create_trade(2, "AAPL", 155.0, 160.0, 100.0, 1),
            ];

            let metrics = analyzer_clone.calculate_metrics(&trades, 100000.0);

            repo_clone
                .save_backtest_results(&format!("concurrent_{}", i), &trades, &metrics)
                .await
        });

        handles.push(handle);
    }

    // Wait for all concurrent operations
    for handle in handles {
        handle.await??;
    }

    // Verify all saved
    let all = trading_repo.list_backtests(100, 0, None, None).await?;
    assert!(all.len() >= 5, "All concurrent reports should be saved");

    Ok(())
}
