//! Asset Parser Unit Tests
//!
//! Comprehensive test suite for asset parsing and validation.
//! Tests cover futures, equities, mixed assets, whitespace handling,
//! case normalization, and error cases.

use ml_training_service::asset_parser::{Asset, AssetParser};

// ============================================================================
// FUTURES PARSING TESTS
// ============================================================================

#[test]
fn test_parse_single_future_es() {
    let result = AssetParser::parse("ES.FUT").unwrap();
    assert_eq!(result.len(), 1);
    match &result[0] {
        Asset::Future { symbol } => assert_eq!(symbol, "ES.FUT"),
        _ => panic!("Expected Future variant"),
    }
}

#[test]
fn test_parse_single_future_nq() {
    let result = AssetParser::parse("NQ.FUT").unwrap();
    assert_eq!(result.len(), 1);
    match &result[0] {
        Asset::Future { symbol } => assert_eq!(symbol, "NQ.FUT"),
        _ => panic!("Expected Future variant"),
    }
}

#[test]
fn test_parse_single_future_6e() {
    let result = AssetParser::parse("6E.FUT").unwrap();
    assert_eq!(result.len(), 1);
    match &result[0] {
        Asset::Future { symbol } => assert_eq!(symbol, "6E.FUT"),
        _ => panic!("Expected Future variant"),
    }
}

#[test]
fn test_parse_single_future_zn() {
    let result = AssetParser::parse("ZN.FUT").unwrap();
    assert_eq!(result.len(), 1);
    match &result[0] {
        Asset::Future { symbol } => assert_eq!(symbol, "ZN.FUT"),
        _ => panic!("Expected Future variant"),
    }
}

#[test]
fn test_parse_multiple_futures() {
    let result = AssetParser::parse("ES.FUT,NQ.FUT").unwrap();
    assert_eq!(result.len(), 2);
    
    let symbols: Vec<String> = result.iter().map(|a| match a {
        Asset::Future { symbol } => symbol.clone(),
        _ => panic!("Expected Future variant"),
    }).collect();
    
    assert!(symbols.contains(&"ES.FUT".to_string()));
    assert!(symbols.contains(&"NQ.FUT".to_string()));
}

#[test]
fn test_parse_four_futures() {
    let result = AssetParser::parse("ES.FUT,NQ.FUT,6E.FUT,ZN.FUT").unwrap();
    assert_eq!(result.len(), 4);
    
    let symbols: Vec<String> = result.iter().map(|a| match a {
        Asset::Future { symbol } => symbol.clone(),
        _ => panic!("Expected Future variant"),
    }).collect();
    
    assert!(symbols.contains(&"ES.FUT".to_string()));
    assert!(symbols.contains(&"NQ.FUT".to_string()));
    assert!(symbols.contains(&"6E.FUT".to_string()));
    assert!(symbols.contains(&"ZN.FUT".to_string()));
}

// ============================================================================
// EQUITIES PARSING TESTS
// ============================================================================

#[test]
fn test_parse_single_equity_aapl() {
    let result = AssetParser::parse("AAPL").unwrap();
    assert_eq!(result.len(), 1);
    match &result[0] {
        Asset::Equity { symbol } => assert_eq!(symbol, "AAPL"),
        _ => panic!("Expected Equity variant"),
    }
}

#[test]
fn test_parse_single_equity_msft() {
    let result = AssetParser::parse("MSFT").unwrap();
    assert_eq!(result.len(), 1);
    match &result[0] {
        Asset::Equity { symbol } => assert_eq!(symbol, "MSFT"),
        _ => panic!("Expected Equity variant"),
    }
}

#[test]
fn test_parse_single_equity_googl() {
    let result = AssetParser::parse("GOOGL").unwrap();
    assert_eq!(result.len(), 1);
    match &result[0] {
        Asset::Equity { symbol } => assert_eq!(symbol, "GOOGL"),
        _ => panic!("Expected Equity variant"),
    }
}

#[test]
fn test_parse_single_equity_tsla() {
    let result = AssetParser::parse("TSLA").unwrap();
    assert_eq!(result.len(), 1);
    match &result[0] {
        Asset::Equity { symbol } => assert_eq!(symbol, "TSLA"),
        _ => panic!("Expected Equity variant"),
    }
}

#[test]
fn test_parse_multiple_equities() {
    let result = AssetParser::parse("AAPL,MSFT,GOOGL").unwrap();
    assert_eq!(result.len(), 3);
    
    let symbols: Vec<String> = result.iter().map(|a| match a {
        Asset::Equity { symbol } => symbol.clone(),
        _ => panic!("Expected Equity variant"),
    }).collect();
    
    assert!(symbols.contains(&"AAPL".to_string()));
    assert!(symbols.contains(&"MSFT".to_string()));
    assert!(symbols.contains(&"GOOGL".to_string()));
}

// ============================================================================
// MIXED ASSETS PARSING TESTS
// ============================================================================

#[test]
fn test_parse_mixed_futures_and_equities() {
    let result = AssetParser::parse("ES.FUT,AAPL,NQ.FUT,MSFT").unwrap();
    assert_eq!(result.len(), 4);
    
    // Count futures vs equities
    let futures_count = result.iter().filter(|a| matches!(a, Asset::Future { .. })).count();
    let equities_count = result.iter().filter(|a| matches!(a, Asset::Equity { .. })).count();
    
    assert_eq!(futures_count, 2);
    assert_eq!(equities_count, 2);
}

#[test]
fn test_parse_complex_mixed_list() {
    let result = AssetParser::parse("ES.FUT,AAPL,NQ.FUT,MSFT,6E.FUT,GOOGL,ZN.FUT,TSLA").unwrap();
    assert_eq!(result.len(), 8);
    
    let futures_count = result.iter().filter(|a| matches!(a, Asset::Future { .. })).count();
    let equities_count = result.iter().filter(|a| matches!(a, Asset::Equity { .. })).count();
    
    assert_eq!(futures_count, 4);
    assert_eq!(equities_count, 4);
}

// ============================================================================
// WHITESPACE HANDLING TESTS
// ============================================================================

#[test]
fn test_parse_with_leading_whitespace() {
    let result = AssetParser::parse(" ES.FUT").unwrap();
    assert_eq!(result.len(), 1);
    match &result[0] {
        Asset::Future { symbol } => assert_eq!(symbol, "ES.FUT"),
        _ => panic!("Expected Future variant"),
    }
}

#[test]
fn test_parse_with_trailing_whitespace() {
    let result = AssetParser::parse("ES.FUT ").unwrap();
    assert_eq!(result.len(), 1);
    match &result[0] {
        Asset::Future { symbol } => assert_eq!(symbol, "ES.FUT"),
        _ => panic!("Expected Future variant"),
    }
}

#[test]
fn test_parse_with_surrounding_whitespace() {
    let result = AssetParser::parse(" ES.FUT ").unwrap();
    assert_eq!(result.len(), 1);
    match &result[0] {
        Asset::Future { symbol } => assert_eq!(symbol, "ES.FUT"),
        _ => panic!("Expected Future variant"),
    }
}

#[test]
fn test_parse_with_whitespace_in_list() {
    let result = AssetParser::parse(" ES.FUT , AAPL , NQ.FUT ").unwrap();
    assert_eq!(result.len(), 3);
}

#[test]
fn test_parse_with_tabs_and_spaces() {
    let result = AssetParser::parse("ES.FUT\t,\t AAPL  ,  NQ.FUT").unwrap();
    assert_eq!(result.len(), 3);
}

// ============================================================================
// CASE NORMALIZATION TESTS
// ============================================================================

#[test]
fn test_parse_lowercase_future_normalizes() {
    let result = AssetParser::parse("es.fut").unwrap();
    assert_eq!(result.len(), 1);
    match &result[0] {
        Asset::Future { symbol } => assert_eq!(symbol, "ES.FUT"),
        _ => panic!("Expected Future variant"),
    }
}

#[test]
fn test_parse_lowercase_equity_normalizes() {
    let result = AssetParser::parse("aapl").unwrap();
    assert_eq!(result.len(), 1);
    match &result[0] {
        Asset::Equity { symbol } => assert_eq!(symbol, "AAPL"),
        _ => panic!("Expected Equity variant"),
    }
}

#[test]
fn test_parse_mixed_case_future_normalizes() {
    let result = AssetParser::parse("Es.FuT").unwrap();
    assert_eq!(result.len(), 1);
    match &result[0] {
        Asset::Future { symbol } => assert_eq!(symbol, "ES.FUT"),
        _ => panic!("Expected Future variant"),
    }
}

#[test]
fn test_parse_mixed_case_equity_normalizes() {
    let result = AssetParser::parse("AaPl").unwrap();
    assert_eq!(result.len(), 1);
    match &result[0] {
        Asset::Equity { symbol } => assert_eq!(symbol, "AAPL"),
        _ => panic!("Expected Equity variant"),
    }
}

// ============================================================================
// DEDUPLICATION TESTS
// ============================================================================

#[test]
fn test_parse_deduplicates_exact_matches() {
    let result = AssetParser::parse("ES.FUT,ES.FUT,ES.FUT").unwrap();
    assert_eq!(result.len(), 1);
    match &result[0] {
        Asset::Future { symbol } => assert_eq!(symbol, "ES.FUT"),
        _ => panic!("Expected Future variant"),
    }
}

#[test]
fn test_parse_deduplicates_case_insensitive() {
    let result = AssetParser::parse("ES.FUT,es.fut,Es.FuT").unwrap();
    assert_eq!(result.len(), 1);
    match &result[0] {
        Asset::Future { symbol } => assert_eq!(symbol, "ES.FUT"),
        _ => panic!("Expected Future variant"),
    }
}

#[test]
fn test_parse_deduplicates_mixed_list() {
    let result = AssetParser::parse("ES.FUT,AAPL,ES.FUT,AAPL,NQ.FUT").unwrap();
    assert_eq!(result.len(), 3);
}

// ============================================================================
// ERROR HANDLING TESTS - FUTURES
// ============================================================================

#[test]
fn test_reject_future_without_suffix() {
    let result = AssetParser::parse("ES");
    assert!(result.is_err());
    let err_msg = result.unwrap_err().to_string();
    assert!(err_msg.contains("Invalid asset format"));
}

#[test]
fn test_reject_future_wrong_suffix() {
    let result = AssetParser::parse("ES.FUTURE");
    assert!(result.is_err());
    let err_msg = result.unwrap_err().to_string();
    assert!(err_msg.contains("Invalid asset format"));
}

#[test]
fn test_reject_future_too_long_symbol() {
    let result = AssetParser::parse("TOOLONG.FUT");
    assert!(result.is_err());
    let err_msg = result.unwrap_err().to_string();
    assert!(err_msg.contains("Invalid asset format"));
}

#[test]
fn test_reject_future_empty_symbol() {
    let result = AssetParser::parse(".FUT");
    assert!(result.is_err());
}

// ============================================================================
// ERROR HANDLING TESTS - EQUITIES
// ============================================================================

#[test]
fn test_reject_equity_too_long() {
    let result = AssetParser::parse("TOOLONG");
    assert!(result.is_err());
    let err_msg = result.unwrap_err().to_string();
    assert!(err_msg.contains("Invalid asset format"));
}

#[test]
fn test_reject_equity_with_numbers() {
    let result = AssetParser::parse("AAPL1");
    assert!(result.is_err());
}

#[test]
fn test_reject_equity_with_special_chars() {
    let result = AssetParser::parse("AAPL@");
    assert!(result.is_err());
}

// ============================================================================
// ERROR HANDLING TESTS - GENERAL
// ============================================================================

#[test]
fn test_reject_empty_string() {
    let result = AssetParser::parse("");
    assert!(result.is_err());
    let err_msg = result.unwrap_err().to_string();
    assert!(err_msg.contains("empty") || err_msg.contains("asset list"));
}

#[test]
fn test_reject_only_whitespace() {
    let result = AssetParser::parse("   ");
    assert!(result.is_err());
}

#[test]
fn test_reject_only_commas() {
    let result = AssetParser::parse(",,,");
    assert!(result.is_err());
}

#[test]
fn test_reject_invalid_characters_in_future() {
    let result = AssetParser::parse("ES@.FUT");
    assert!(result.is_err());
}

#[test]
fn test_reject_invalid_characters_in_equity() {
    let result = AssetParser::parse("A#PPL");
    assert!(result.is_err());
}

#[test]
fn test_partial_invalid_list_fails_all() {
    // If any asset is invalid, the entire parse should fail
    let result = AssetParser::parse("ES.FUT,INVALID_FORMAT,AAPL");
    assert!(result.is_err());
}

// ============================================================================
// VALIDATE METHODS TESTS
// ============================================================================

#[test]
fn test_validate_future_valid_es() {
    let result = AssetParser::validate_future("ES.FUT");
    assert!(result.is_ok());
    match result.unwrap() {
        Asset::Future { symbol } => assert_eq!(symbol, "ES.FUT"),
        _ => panic!("Expected Future variant"),
    }
}

#[test]
fn test_validate_future_valid_6e() {
    let result = AssetParser::validate_future("6E.FUT");
    assert!(result.is_ok());
}

#[test]
fn test_validate_future_invalid_missing_suffix() {
    let result = AssetParser::validate_future("ES");
    assert!(result.is_err());
}

#[test]
fn test_validate_equity_valid_aapl() {
    let result = AssetParser::validate_equity("AAPL");
    assert!(result.is_ok());
    match result.unwrap() {
        Asset::Equity { symbol } => assert_eq!(symbol, "AAPL"),
        _ => panic!("Expected Equity variant"),
    }
}

#[test]
fn test_validate_equity_valid_googl() {
    let result = AssetParser::validate_equity("GOOGL");
    assert!(result.is_ok());
}

#[test]
fn test_validate_equity_invalid_too_long() {
    let result = AssetParser::validate_equity("TOOLONG");
    assert!(result.is_err());
}

// ============================================================================
// DISPLAY METHODS TESTS
// ============================================================================

#[test]
fn test_asset_display_future() {
    let asset = Asset::Future { symbol: "ES.FUT".to_string() };
    assert_eq!(format!("{}", asset), "ES.FUT");
}

#[test]
fn test_asset_display_equity() {
    let asset = Asset::Equity { symbol: "AAPL".to_string() };
    assert_eq!(format!("{}", asset), "AAPL");
}

// ============================================================================
// EDGE CASES
// ============================================================================

#[test]
fn test_reject_single_character_equity_ambiguous() {
    // Single character symbols rejected to avoid futures ambiguity
    // Use .FUT suffix for futures: X.FUT
    let result = AssetParser::parse("X");
    assert!(result.is_err());
}

#[test]
fn test_parse_one_character_future() {
    let result = AssetParser::parse("E.FUT");
    assert!(result.is_ok());
    match &result.unwrap()[0] {
        Asset::Future { symbol } => assert_eq!(symbol, "E.FUT"),
        _ => panic!("Expected Future variant"),
    }
}

#[test]
fn test_parse_max_length_future() {
    let result = AssetParser::parse("ABCD.FUT");
    assert!(result.is_ok());
}

#[test]
fn test_parse_max_length_equity() {
    let result = AssetParser::parse("ABCDE");
    assert!(result.is_ok());
}
