#![allow(clippy::useless_vec, dead_code, unused_variables)]
//! Edge case and error handling tests for backtesting service
//!
//! This test suite focuses on:
//! - DBN data loading error cases (missing files, corrupt data, invalid formats)
//! - Strategy execution edge cases (empty data, gaps, outliers, extreme values)
//! - Performance metrics calculation edge cases (zero trades, negative returns)
//! - Database persistence error handling

use backtesting_service::dbn_data_source::{is_valid_dbn_file, DbnDataSource};
use backtesting_service::performance::PerformanceAnalyzer;
use backtesting_service::strategy_engine::{BacktestTrade, MarketData, TradeSide};
use chrono::{TimeZone, Utc};
use config::structures::BacktestingPerformanceConfig;
use rust_decimal::Decimal;
use std::collections::HashMap;
use std::fs;
use std::io::Write;
use tempfile::TempDir;

// ============================================================================
// DBN Data Loading Error Cases
// ============================================================================

#[tokio::test]
async fn test_dbn_missing_file() {
    // Test loading from non-existent file
    let mut file_mapping = HashMap::new();
    file_mapping.insert(
        "MISSING.FUT".to_string(),
        "/tmp/nonexistent_file_12345.dbn".to_string(),
    );

    let data_source = DbnDataSource::new(file_mapping).await.unwrap();
    let result = data_source.load_ohlcv_bars("MISSING.FUT").await;

    assert!(result.is_err(), "Should fail when file doesn't exist");
    let error_msg = result.unwrap_err().to_string();
    assert!(
        error_msg.contains("not found") || error_msg.contains("No such file"),
        "Error should mention file not found: {}",
        error_msg
    );
}

#[tokio::test]
async fn test_dbn_invalid_symbol() {
    // Test loading symbol that's not in mapping
    let file_mapping = HashMap::new();
    let data_source = DbnDataSource::new(file_mapping).await.unwrap();

    let result = data_source.load_ohlcv_bars("INVALID.SYMBOL").await;

    assert!(result.is_err(), "Should fail for unmapped symbol");
    if let Err(e) = result {
        let error_msg = e.to_string();
        assert!(
            error_msg.contains("No DBN file configured"),
            "Error should mention missing mapping: {}",
            error_msg
        );
    }
}

#[tokio::test]
async fn test_dbn_corrupt_file() {
    // Create a temporary corrupt DBN file
    let temp_dir = TempDir::new().unwrap();
    let corrupt_file = temp_dir.path().join("corrupt.dbn");

    // Write invalid data (not a valid DBN file)
    let mut file = fs::File::create(&corrupt_file).unwrap();
    file.write_all(b"THIS IS NOT A VALID DBN FILE").unwrap();
    file.sync_all().unwrap();

    let mut file_mapping = HashMap::new();
    file_mapping.insert(
        "CORRUPT.FUT".to_string(),
        corrupt_file.to_string_lossy().to_string(),
    );

    let data_source = DbnDataSource::new(file_mapping).await.unwrap();
    let result = data_source.load_ohlcv_bars("CORRUPT.FUT").await;

    assert!(
        result.is_err(),
        "Should fail when DBN file is corrupt/invalid"
    );
    // Verify error message contains useful info
    if let Err(e) = result {
        let error_msg = e.to_string();
        assert!(!error_msg.is_empty(), "Error message should not be empty");
    }
}

#[tokio::test]
async fn test_dbn_empty_file() {
    // Create an empty DBN file
    let temp_dir = TempDir::new().unwrap();
    let empty_file = temp_dir.path().join("empty.dbn");

    fs::File::create(&empty_file).unwrap();

    let mut file_mapping = HashMap::new();
    file_mapping.insert(
        "EMPTY.FUT".to_string(),
        empty_file.to_string_lossy().to_string(),
    );

    let data_source = DbnDataSource::new(file_mapping).await.unwrap();
    let result = data_source.load_ohlcv_bars("EMPTY.FUT").await;

    // Empty file should either error or return empty vector
    if let Ok(bars) = result {
        assert_eq!(bars.len(), 0, "Empty file should have zero bars");
    } else {
        // Also acceptable to error on empty/invalid file
        assert!(result.is_err(), "Empty file may error");
    }
}

#[test]
fn test_is_valid_dbn_file_extensions() {
    // Valid extensions
    assert!(is_valid_dbn_file("test.dbn"));
    assert!(is_valid_dbn_file("ES.FUT_2024-01-02.dbn"));
    assert!(is_valid_dbn_file("/path/to/file.dbn"));
    assert!(is_valid_dbn_file("TEST.DBN")); // Case insensitive

    // Invalid compressed extensions
    assert!(!is_valid_dbn_file("test.dbn.zst"));
    assert!(!is_valid_dbn_file("test.dbn.gz"));
    assert!(!is_valid_dbn_file("test.dbn.bz2"));
    assert!(!is_valid_dbn_file("test.dbn.xz"));

    // Invalid temporary/backup extensions
    assert!(!is_valid_dbn_file("test.dbn.tmp"));
    assert!(!is_valid_dbn_file("test.dbn.old"));
    assert!(!is_valid_dbn_file("test.dbn.backup"));
    assert!(!is_valid_dbn_file("test.dbn.swp"));

    // Invalid intermediate patterns
    assert!(!is_valid_dbn_file("test.backup.dbn"));
    assert!(!is_valid_dbn_file("test.temp.dbn"));
    assert!(!is_valid_dbn_file("test.processed.dbn"));

    // Non-DBN files
    assert!(!is_valid_dbn_file("test.csv"));
    assert!(!is_valid_dbn_file("test.parquet"));
    assert!(!is_valid_dbn_file("test.txt"));
}

#[tokio::test]
async fn test_dbn_from_nonexistent_directory() {
    let result = DbnDataSource::from_directory("/tmp/nonexistent_dir_xyz123").await;

    assert!(result.is_err(), "Should fail for non-existent directory");
    if let Err(e) = result {
        let error_msg = e.to_string();
        assert!(
            error_msg.contains("does not exist"),
            "Error should mention directory doesn't exist: {}",
            error_msg
        );
    }
}

#[tokio::test]
async fn test_dbn_multi_file_partial_missing() {
    // Test loading when some files exist and some don't
    let mut file_mapping = HashMap::new();

    // Get real test file
    let current_dir = std::env::current_dir().expect("INVARIANT: Current directory should be accessible");
    let workspace_root = current_dir
        .ancestors()
        .find(|p| p.join("test_data").exists())
        .expect("Could not find workspace root");
    let test_file = workspace_root.join("test_data/real/databento/ES.FUT_ohlcv-1m_2024-01-02.dbn");

    if !test_file.exists() {
        println!("Real test file not found, skipping test");
        return;
    }

    file_mapping.insert(
        "ES.FUT".to_string(),
        vec![
            test_file.to_string_lossy().to_string(),
            "/tmp/missing_file_12345.dbn".to_string(), // This file doesn't exist
        ],
    );

    let data_source = DbnDataSource::new_multi_file(file_mapping).await.unwrap();
    let result = data_source.load_ohlcv_bars_all("ES.FUT").await;

    // Should fail on second file
    assert!(
        result.is_err(),
        "Should fail when any file in multi-file load is missing"
    );
}

// ============================================================================
// Strategy Execution Edge Cases
// ============================================================================

#[test]
fn test_market_data_empty_dataset() {
    // Empty market data should be handled gracefully
    let bars: Vec<MarketData> = vec![];

    assert_eq!(bars.len(), 0);
    assert!(bars.is_empty());
}

#[test]
fn test_market_data_single_bar() {
    // Single bar edge case
    let bar = MarketData {
        symbol: "ES.FUT".to_string(),
        timestamp: Utc.with_ymd_and_hms(2024, 1, 1, 12, 0, 0).single().expect("INVARIANT: Valid date/time parameters"),
        open: Decimal::new(4500, 0),
        high: Decimal::new(4510, 0),
        low: Decimal::new(4495, 0),
        close: Decimal::new(4505, 0),
        volume: Decimal::new(1000, 0),
    };

    let bars = vec![bar];
    assert_eq!(bars.len(), 1);
}

#[test]
fn test_market_data_extreme_prices() {
    // Test with extreme price values
    let bar_high = MarketData {
        symbol: "ES.FUT".to_string(),
        timestamp: Utc.with_ymd_and_hms(2024, 1, 1, 12, 0, 0).single().expect("INVARIANT: Valid date/time parameters"),
        open: Decimal::new(999999, 0), // Very high price
        high: Decimal::new(999999, 0),
        low: Decimal::new(999999, 0),
        close: Decimal::new(999999, 0),
        volume: Decimal::new(1, 0),
    };

    let bar_low = MarketData {
        symbol: "ES.FUT".to_string(),
        timestamp: Utc.with_ymd_and_hms(2024, 1, 1, 13, 0, 0).single().expect("INVARIANT: Valid date/time parameters"),
        open: Decimal::new(1, 0), // Very low price
        high: Decimal::new(1, 0),
        low: Decimal::new(1, 0),
        close: Decimal::new(1, 0),
        volume: Decimal::new(1, 0),
    };

    assert!(bar_high.close > Decimal::ZERO);
    assert!(bar_low.close > Decimal::ZERO);
}

#[test]
fn test_market_data_zero_volume() {
    // Test with zero volume (edge case)
    let bar = MarketData {
        symbol: "ES.FUT".to_string(),
        timestamp: Utc.with_ymd_and_hms(2024, 1, 1, 12, 0, 0).single().expect("INVARIANT: Valid date/time parameters"),
        open: Decimal::new(4500, 0),
        high: Decimal::new(4500, 0),
        low: Decimal::new(4500, 0),
        close: Decimal::new(4500, 0),
        volume: Decimal::ZERO, // Zero volume
    };

    assert_eq!(bar.volume, Decimal::ZERO);
}

#[test]
fn test_market_data_time_gaps() {
    // Test with large time gaps between bars
    let bar1 = MarketData {
        symbol: "ES.FUT".to_string(),
        timestamp: Utc.with_ymd_and_hms(2024, 1, 1, 12, 0, 0).single().expect("INVARIANT: Valid date/time parameters"),
        open: Decimal::new(4500, 0),
        high: Decimal::new(4510, 0),
        low: Decimal::new(4495, 0),
        close: Decimal::new(4505, 0),
        volume: Decimal::new(1000, 0),
    };

    let bar2 = MarketData {
        symbol: "ES.FUT".to_string(),
        timestamp: Utc.with_ymd_and_hms(2024, 1, 5, 12, 0, 0).single().expect("INVARIANT: Valid date/time parameters"), // 4 days later
        open: Decimal::new(4600, 0),
        high: Decimal::new(4610, 0),
        low: Decimal::new(4595, 0),
        close: Decimal::new(4605, 0),
        volume: Decimal::new(1000, 0),
    };

    let time_gap = (bar2.timestamp - bar1.timestamp).num_days();
    assert_eq!(time_gap, 4, "Should detect 4-day gap between bars");
}

#[test]
fn test_market_data_price_spike() {
    // Test with extreme price spike (>50% move)
    let bar1 = MarketData {
        symbol: "ES.FUT".to_string(),
        timestamp: Utc.with_ymd_and_hms(2024, 1, 1, 12, 0, 0).single().expect("INVARIANT: Valid date/time parameters"),
        open: Decimal::new(4500, 0),
        high: Decimal::new(4510, 0),
        low: Decimal::new(4495, 0),
        close: Decimal::new(4505, 0),
        volume: Decimal::new(1000, 0),
    };

    let bar2 = MarketData {
        symbol: "ES.FUT".to_string(),
        timestamp: Utc.with_ymd_and_hms(2024, 1, 1, 12, 1, 0).single().expect("INVARIANT: Valid date/time parameters"),
        open: Decimal::new(6800, 0), // 51% spike
        high: Decimal::new(6810, 0),
        low: Decimal::new(6795, 0),
        close: Decimal::new(6805, 0),
        volume: Decimal::new(1000, 0),
    };

    let pct_change = (bar2.close - bar1.close) / bar1.close;
    assert!(
        pct_change > Decimal::new(50, 2),
        "Should detect >50% price spike"
    );
}

// ============================================================================
// Performance Metrics Edge Cases
// ============================================================================

#[test]
fn test_performance_metrics_zero_trades() {
    // Test performance metrics with zero trades
    let config = BacktestingPerformanceConfig {
        risk_free_rate: 0.02,
        equity_curve_resolution: 1000,
        enable_advanced_metrics: Some(true),
    };
    let analyzer = PerformanceAnalyzer::new(&config).unwrap();

    let trades: Vec<BacktestTrade> = vec![];
    let metrics = analyzer.calculate_metrics(&trades, 100000.0);

    assert_eq!(metrics.total_trades, 0);
    assert_eq!(metrics.winning_trades, 0);
    assert_eq!(metrics.losing_trades, 0);
}

#[test]
fn test_performance_metrics_single_trade() {
    // Test performance metrics with single trade
    let config = BacktestingPerformanceConfig {
        risk_free_rate: 0.02,
        equity_curve_resolution: 1000,
        enable_advanced_metrics: Some(true),
    };
    let analyzer = PerformanceAnalyzer::new(&config).unwrap();

    let trade = BacktestTrade {
        trade_id: "TRADE1".to_string(),
        symbol: "ES.FUT".to_string(),
        side: TradeSide::Buy,
        quantity: Decimal::new(1, 0),
        entry_price: Decimal::new(4500, 0),
        exit_price: Decimal::new(4600, 0),
        entry_time: Utc.with_ymd_and_hms(2024, 1, 1, 12, 0, 0).single().expect("INVARIANT: Valid date/time parameters"),
        exit_time: Utc.with_ymd_and_hms(2024, 1, 1, 13, 0, 0).single().expect("INVARIANT: Valid date/time parameters"),
        pnl: Decimal::new(100, 0),
        return_percent: Decimal::new(222, 2), // 2.22%
        entry_signal: "BUY".to_string(),
        exit_signal: "SELL".to_string(),
    };

    let metrics = analyzer.calculate_metrics(&vec![trade], 100000.0);

    assert_eq!(metrics.total_trades, 1);
    assert_eq!(metrics.winning_trades, 1);
    assert_eq!(metrics.losing_trades, 0);
}

#[test]
fn test_performance_metrics_high_volatility() {
    // Test performance metrics with high volatility returns
    let config = BacktestingPerformanceConfig {
        risk_free_rate: 0.02,
        equity_curve_resolution: 1000,
        enable_advanced_metrics: Some(true),
    };
    let analyzer = PerformanceAnalyzer::new(&config).unwrap();

    let trades = vec![
        BacktestTrade {
            trade_id: "TRADE1".to_string(),
            symbol: "ES.FUT".to_string(),
            side: TradeSide::Buy,
            quantity: Decimal::new(1, 0),
            entry_price: Decimal::new(4500, 0),
            exit_price: Decimal::new(4900, 0),
            entry_time: Utc.with_ymd_and_hms(2024, 1, 1, 12, 0, 0).single().expect("INVARIANT: Valid date/time parameters"),
            exit_time: Utc.with_ymd_and_hms(2024, 1, 1, 13, 0, 0).single().expect("INVARIANT: Valid date/time parameters"),
            pnl: Decimal::new(400, 0),
            return_percent: Decimal::new(889, 2), // 8.89%
            entry_signal: "BUY".to_string(),
            exit_signal: "SELL".to_string(),
        },
        BacktestTrade {
            trade_id: "TRADE2".to_string(),
            symbol: "ES.FUT".to_string(),
            side: TradeSide::Buy,
            quantity: Decimal::new(1, 0),
            entry_price: Decimal::new(4900, 0),
            exit_price: Decimal::new(4100, 0),
            entry_time: Utc.with_ymd_and_hms(2024, 1, 1, 14, 0, 0).single().expect("INVARIANT: Valid date/time parameters"),
            exit_time: Utc.with_ymd_and_hms(2024, 1, 1, 15, 0, 0).single().expect("INVARIANT: Valid date/time parameters"),
            pnl: Decimal::new(-800, 0),
            return_percent: Decimal::new(-1633, 2), // -16.33%
            entry_signal: "BUY".to_string(),
            exit_signal: "SELL".to_string(),
        },
    ];

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

    // High volatility should reduce Sharpe ratio
    assert!(
        metrics.sharpe_ratio.is_finite(),
        "Sharpe should be finite for valid returns"
    );
    println!("High volatility Sharpe ratio: {}", metrics.sharpe_ratio);
}

#[test]
fn test_performance_metrics_all_losing_trades() {
    // Test performance metrics with all losing trades
    let config = BacktestingPerformanceConfig {
        risk_free_rate: 0.02,
        equity_curve_resolution: 1000,
        enable_advanced_metrics: Some(true),
    };
    let analyzer = PerformanceAnalyzer::new(&config).unwrap();

    let trades = vec![
        BacktestTrade {
            trade_id: "TRADE1".to_string(),
            symbol: "ES.FUT".to_string(),
            side: TradeSide::Buy,
            quantity: Decimal::new(1, 0),
            entry_price: Decimal::new(4500, 0),
            exit_price: Decimal::new(4400, 0),
            entry_time: Utc.with_ymd_and_hms(2024, 1, 1, 12, 0, 0).single().expect("INVARIANT: Valid date/time parameters"),
            exit_time: Utc.with_ymd_and_hms(2024, 1, 1, 13, 0, 0).single().expect("INVARIANT: Valid date/time parameters"),
            pnl: Decimal::new(-100, 0),
            return_percent: Decimal::new(-222, 2), // -2.22%
            entry_signal: "BUY".to_string(),
            exit_signal: "SELL".to_string(),
        },
        BacktestTrade {
            trade_id: "TRADE2".to_string(),
            symbol: "ES.FUT".to_string(),
            side: TradeSide::Buy,
            quantity: Decimal::new(1, 0),
            entry_price: Decimal::new(4400, 0),
            exit_price: Decimal::new(4300, 0),
            entry_time: Utc.with_ymd_and_hms(2024, 1, 1, 14, 0, 0).single().expect("INVARIANT: Valid date/time parameters"),
            exit_time: Utc.with_ymd_and_hms(2024, 1, 1, 15, 0, 0).single().expect("INVARIANT: Valid date/time parameters"),
            pnl: Decimal::new(-100, 0),
            return_percent: Decimal::new(-227, 2), // -2.27%
            entry_signal: "BUY".to_string(),
            exit_signal: "SELL".to_string(),
        },
    ];

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

    assert_eq!(metrics.total_trades, 2);
    assert_eq!(metrics.winning_trades, 0);
    assert_eq!(metrics.losing_trades, 2);
    assert!(
        metrics.total_return < 0.0,
        "All losing trades should have negative return"
    );
}

#[test]
fn test_performance_metrics_extreme_values() {
    // Test performance metrics with extreme values
    let config = BacktestingPerformanceConfig {
        risk_free_rate: 0.02,
        equity_curve_resolution: 1000,
        enable_advanced_metrics: Some(true),
    };
    let analyzer = PerformanceAnalyzer::new(&config).unwrap();

    let trades = vec![
        BacktestTrade {
            trade_id: "TRADE1".to_string(),
            symbol: "ES.FUT".to_string(),
            side: TradeSide::Buy,
            quantity: Decimal::new(1, 0),
            entry_price: Decimal::new(1000, 0),
            exit_price: Decimal::new(11000, 0), // 1000% gain
            entry_time: Utc.with_ymd_and_hms(2024, 1, 1, 12, 0, 0).single().expect("INVARIANT: Valid date/time parameters"),
            exit_time: Utc.with_ymd_and_hms(2024, 1, 1, 13, 0, 0).single().expect("INVARIANT: Valid date/time parameters"),
            pnl: Decimal::new(10000, 0),
            return_percent: Decimal::new(1000, 0), // 1000%
            entry_signal: "BUY".to_string(),
            exit_signal: "SELL".to_string(),
        },
        BacktestTrade {
            trade_id: "TRADE2".to_string(),
            symbol: "ES.FUT".to_string(),
            side: TradeSide::Buy,
            quantity: Decimal::new(1, 0),
            entry_price: Decimal::new(10000, 0),
            exit_price: Decimal::new(100, 0), // 99% loss
            entry_time: Utc.with_ymd_and_hms(2024, 1, 1, 14, 0, 0).single().expect("INVARIANT: Valid date/time parameters"),
            exit_time: Utc.with_ymd_and_hms(2024, 1, 1, 15, 0, 0).single().expect("INVARIANT: Valid date/time parameters"),
            pnl: Decimal::new(-9900, 0),
            return_percent: Decimal::new(-99, 0), // -99%
            entry_signal: "BUY".to_string(),
            exit_signal: "SELL".to_string(),
        },
    ];

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

    // Metrics should handle extreme values without panic
    assert!(metrics.sharpe_ratio.is_finite() || metrics.sharpe_ratio.is_nan());
    assert!(metrics.max_drawdown.is_finite());
}

// ============================================================================
// Data Validation Edge Cases
// ============================================================================

#[tokio::test]
async fn test_dbn_check_data_availability_no_symbol() {
    // Test data availability for unmapped symbol
    let file_mapping = HashMap::new();
    let data_source = DbnDataSource::new(file_mapping).await.unwrap();

    let start_time = Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).single().expect("INVARIANT: Valid date/time parameters");
    let end_time = Utc.with_ymd_and_hms(2024, 1, 2, 0, 0, 0).single().expect("INVARIANT: Valid date/time parameters");

    let available = data_source
        .check_data_availability("NONEXISTENT.FUT", start_time, end_time)
        .await
        .unwrap();

    assert!(!available, "Should return false for unmapped symbol");
}

#[tokio::test]
async fn test_dbn_available_symbols_empty() {
    // Test available symbols with no mappings
    let file_mapping = HashMap::new();
    let data_source = DbnDataSource::new(file_mapping).await.unwrap();

    let symbols = data_source.available_symbols();

    assert_eq!(symbols.len(), 0, "Should have zero available symbols");
}

#[tokio::test]
async fn test_dbn_get_file_path_nonexistent() {
    // Test getting file path for unmapped symbol
    let file_mapping = HashMap::new();
    let data_source = DbnDataSource::new(file_mapping).await.unwrap();

    let path = data_source.get_file_path("NONEXISTENT.FUT");

    assert!(path.is_none(), "Should return None for unmapped symbol");
}

#[tokio::test]
async fn test_dbn_add_symbol_mapping() {
    // Test adding symbol mapping after creation
    let file_mapping = HashMap::new();
    let mut data_source = DbnDataSource::new(file_mapping).await.unwrap();

    assert_eq!(data_source.available_symbols().len(), 0);

    data_source.add_symbol_mapping("NEW.FUT".to_string(), "/path/to/new.dbn".to_string());

    assert_eq!(data_source.available_symbols().len(), 1);
    assert!(data_source
        .available_symbols()
        .contains(&"NEW.FUT".to_string()));
}

#[tokio::test]
async fn test_dbn_get_file_count() {
    // Test getting file count for symbols
    let file_mapping = HashMap::new();
    let mut data_source = DbnDataSource::new(file_mapping).await.unwrap();

    data_source.add_symbol_mapping_multi(
        "MULTI.FUT".to_string(),
        vec![
            "/path/to/day1.dbn".to_string(),
            "/path/to/day2.dbn".to_string(),
            "/path/to/day3.dbn".to_string(),
        ],
    );

    assert_eq!(data_source.get_file_count("MULTI.FUT"), 3);
    assert_eq!(data_source.get_file_count("NONEXISTENT.FUT"), 0);
}
