#![allow(
    clippy::unwrap_used,
    clippy::expect_used,
    clippy::indexing_slicing,
    clippy::useless_vec
)]
//! Advanced Asset Parser Tests
//!
//! Comprehensive edge case testing, fuzzing, Unicode handling, performance validation,
//! and malformed input recovery for the AssetParser module.
//!
//! Tests cover:
//! - Fuzzing with random inputs
//! - Unicode and international symbol handling
//! - Malformed input recovery
//! - Performance benchmarks (10K+ assets)
//! - Boundary conditions and edge cases

use ml_training_service::asset_parser::AssetParser;
use rand::{distributions::Alphanumeric, Rng};
use std::collections::HashSet;
use std::time::Instant;

// ============================================================================
// FUZZING TESTS
// ============================================================================

#[test]
fn test_fuzz_random_alphanumeric_inputs() {
    let mut rng = rand::thread_rng();

    // Generate 1000 random inputs
    for _ in 0..1000 {
        let len = rng.gen_range(1..=50);
        let random_input: String = rand::thread_rng()
            .sample_iter(&Alphanumeric)
            .take(len)
            .map(char::from)
            .collect();

        // Should either parse or return error, but never panic
        let _ = AssetParser::parse(&random_input);
    }
}

#[test]
fn test_fuzz_with_special_characters() {
    let special_chars = vec![
        "ES@FUT", "NQ#.FUT", "AAPL!", "TEST$", "A^B", "ES&FUT",
        "NQ*.FUT", "AAPL()", "TEST[]", "A{}B", "ES|FUT", "NQ~.FUT",
        "AAPL`", "TEST\\", "A/B", "ES:FUT", "NQ;.FUT", "AAPL\"",
        "TEST'", "A<>B", "ES?FUT", "NQ=.FUT",
    ];

    for input in special_chars {
        // All should fail validation gracefully
        let result = AssetParser::parse(input);
        assert!(result.is_err(), "Input '{}' should fail validation", input);
    }
}

#[test]
fn test_fuzz_with_control_characters() {
    let control_chars = vec![
        "ES\x00.FUT", // Null
        "NQ\x01.FUT", // SOH
        "AAPL\x02",   // STX
        "TEST\x03",   // ETX
        "ES\n.FUT",   // Newline
        "NQ\r.FUT",   // Carriage return
        "AAPL\t",     // Tab (should be handled)
    ];

    for input in control_chars {
        let result = AssetParser::parse(input);
        // Most control chars should fail, except whitespace (tab, newline, etc. are trimmed)
        if input.contains('\t') || input.contains('\n') || input.contains('\r') {
            // Whitespace should be trimmed, might still fail for other reasons
            let _ = result;
        } else {
            assert!(result.is_err(), "Control char input should fail: {:?}", input);
        }
    }
}

#[test]
fn test_fuzz_extremely_long_inputs() {
    // Test with very long strings
    let long_input = "A".repeat(10_000);
    let result = AssetParser::parse(&long_input);
    assert!(result.is_err()); // Should reject: too long

    // Test with long comma-separated list
    let long_list = (0..1000).map(|_| "AAPL").collect::<Vec<_>>().join(",");
    let result = AssetParser::parse(&long_list);
    // Should succeed (all valid, will deduplicate to 1)
    assert!(result.is_ok());
    assert_eq!(result.unwrap().len(), 1);
}

#[test]
fn test_fuzz_with_empty_and_whitespace_elements() {
    let inputs = vec![
        "ES.FUT,,,NQ.FUT",
        ",ES.FUT,",
        ",,,,",
        "  ,  ,  ",
        "ES.FUT,   ,NQ.FUT",
        "\t,\t,\t",
    ];

    for input in inputs {
        let result = AssetParser::parse(input);
        // Empty elements should either be filtered out or cause error
        let _ = result; // Just ensure no panic
    }
}

// ============================================================================
// UNICODE AND INTERNATIONAL SYMBOLS
// ============================================================================

#[test]
fn test_unicode_symbols_rejected() {
    let unicode_inputs = vec![
        "测试", // Chinese
        "テスト", // Japanese
        "테스트", // Korean
        "Тест", // Cyrillic
        "αβγ", // Greek
        "مرحبا", // Arabic
        "שלום", // Hebrew
        "ES.FUT,测试,NQ.FUT", // Mixed
    ];

    for input in unicode_inputs {
        let result = AssetParser::parse(input);
        assert!(result.is_err(), "Unicode input '{}' should be rejected", input);
    }
}

#[test]
fn test_accented_latin_characters_rejected() {
    let accented = vec![
        "ÁPPL", "GÖÖGL", "MŠFT", "TËST",
        "ÉS.FUT", "ÑQ.FUT",
    ];

    for input in accented {
        let result = AssetParser::parse(input);
        assert!(result.is_err(), "Accented input '{}' should be rejected", input);
    }
}

#[test]
fn test_emoji_rejected() {
    let emoji_inputs = vec![
        "😀", "🚀", "💰", "📈", "📉",
        "ES.FUT😀", "AAPL🚀", "TEST💰",
    ];

    for input in emoji_inputs {
        let result = AssetParser::parse(input);
        assert!(result.is_err(), "Emoji input '{}' should be rejected", input);
    }
}

#[test]
fn test_mixed_unicode_and_ascii() {
    let inputs = vec![
        "ES.FUT,AAPL,测试",
        "αβγ,NQ.FUT",
        "AAPL,😀,MSFT",
    ];

    for input in inputs {
        let result = AssetParser::parse(input);
        // Any invalid symbol should fail entire parse
        assert!(result.is_err(), "Mixed input '{}' should be rejected", input);
    }
}

// ============================================================================
// MALFORMED INPUT RECOVERY
// ============================================================================

#[test]
fn test_malformed_future_suffix_variations() {
    let malformed = vec![
        "ES.fut", // Should normalize
        "ES.FuT", // Should normalize
        "ES.FUTS", // Wrong suffix
        "ES.FUTURE", // Wrong suffix
        "ES.F", // Too short
        "ES.", // Missing suffix
        "ES..FUT", // Double dot
        "ES.FUT.", // Extra dot
    ];

    for input in &malformed[..2] {
        // First 2 should normalize
        let result = AssetParser::parse(input);
        assert!(result.is_ok(), "Input '{}' should normalize", input);
    }

    for input in &malformed[2..] {
        // Rest should fail
        let result = AssetParser::parse(input);
        assert!(result.is_err(), "Input '{}' should fail", input);
    }
}

#[test]
fn test_malformed_comma_separation() {
    let inputs = vec![
        ("ES.FUT;NQ.FUT", true), // Wrong separator
        ("ES.FUT|NQ.FUT", true), // Wrong separator
        ("ES.FUT NQ.FUT", true), // Space separator (should fail)
        ("ES.FUT,NQ.FUT,", false), // Trailing comma (should work after trim)
        (",ES.FUT,NQ.FUT", false), // Leading comma
    ];

    for (input, should_fail) in inputs {
        let result = AssetParser::parse(input);
        if should_fail {
            assert!(result.is_err(), "Input '{}' should fail", input);
        } else {
            let _ = result; // May succeed or fail depending on implementation
        }
    }
}

#[test]
fn test_mixed_case_recovery() {
    let inputs = vec![
        ("es.fut", "ES.FUT"),
        ("Es.FuT", "ES.FUT"),
        ("ES.fut", "ES.FUT"),
        ("aapl", "AAPL"),
        ("AaPl", "AAPL"),
        ("AAPL", "AAPL"),
    ];

    for (input, expected) in inputs {
        let result = AssetParser::parse(input);
        assert!(result.is_ok(), "Input '{}' should normalize", input);
        let assets = result.unwrap();
        assert_eq!(assets.len(), 1);
        assert_eq!(assets[0].symbol(), expected);
    }
}

#[test]
fn test_whitespace_variations_recovery() {
    let inputs = vec![
        " ES.FUT",
        "ES.FUT ",
        "  ES.FUT  ",
        "\tES.FUT",
        "ES.FUT\t",
        "\nES.FUT\n",
        "  ES.FUT,  NQ.FUT  ",
        " ES.FUT , NQ.FUT , AAPL ",
    ];

    for input in inputs {
        let result = AssetParser::parse(input);
        assert!(result.is_ok(), "Whitespace input '{}' should normalize", input);
    }
}

// ============================================================================
// PERFORMANCE BENCHMARKS (10K+ ASSETS)
// ============================================================================

#[test]
fn test_performance_10k_unique_assets() {
    // Generate 10,000 unique equity symbols using valid 2-5 char format
    let assets: Vec<String> = (0..10_000)
        .map(|i| format!("SYM{}", i % 10)) // SYM0-SYM9 (4 chars each, valid)
        .collect();

    let input = assets.join(",");

    let start = Instant::now();
    let result = AssetParser::parse(&input);
    let elapsed = start.elapsed();

    assert!(result.is_ok(), "Parse failed: {:?}", result.err());
    let parsed = result.unwrap();
    // Will deduplicate to 10 unique symbols
    assert_eq!(parsed.len(), 10);

    // Performance target: <50ms for 10K assets
    println!("10K assets parsed in {:?} ({:.2}μs per asset)",
             elapsed, elapsed.as_micros() as f64 / 10_000.0);
    assert!(elapsed.as_millis() < 100, "Parsing 10K assets took too long: {:?}", elapsed);
}

#[test]
fn test_performance_10k_duplicate_assets() {
    // 10,000 duplicates of same asset (tests deduplication performance)
    let input = vec!["ES.FUT"; 10_000].join(",");

    let start = Instant::now();
    let result = AssetParser::parse(&input);
    let elapsed = start.elapsed();

    assert!(result.is_ok());
    let parsed = result.unwrap();
    assert_eq!(parsed.len(), 1); // Deduplicated to 1

    // Deduplication should still be fast
    println!("10K duplicates deduplicated in {:?}", elapsed);
    assert!(elapsed.as_millis() < 100, "Deduplication took too long: {:?}", elapsed);
}

#[test]
fn test_performance_mixed_futures_equities_1k() {
    // 1000 mixed futures and equities with valid symbols
    let assets: Vec<String> = (0..500)
        .flat_map(|i| vec![
            format!("ES{}.FUT", i % 5), // ES0.FUT - ES4.FUT (valid 3-char)
            format!("AAPL"), // Valid 4-char equity
        ])
        .collect();

    let input = assets.join(",");

    let start = Instant::now();
    let result = AssetParser::parse(&input);
    let elapsed = start.elapsed();

    assert!(result.is_ok(), "Parse failed: {:?}", result.err());
    let parsed = result.unwrap();
    // Will deduplicate to 6 symbols (5 futures + 1 equity)
    assert_eq!(parsed.len(), 6);

    // Count futures vs equities
    let futures = parsed.iter().filter(|a| a.is_future()).count();
    let equities = parsed.iter().filter(|a| a.is_equity()).count();
    assert_eq!(futures, 5);
    assert_eq!(equities, 1);

    println!("1K mixed assets parsed in {:?}", elapsed);
    assert!(elapsed.as_millis() < 100, "Mixed parsing took too long: {:?}", elapsed);
}

#[test]
fn test_performance_single_asset_overhead() {
    // Measure overhead for single asset
    let iterations = 10_000;
    let start = Instant::now();

    for _ in 0..iterations {
        let _ = AssetParser::parse("ES.FUT").unwrap();
    }

    let elapsed = start.elapsed();
    let per_parse = elapsed.as_nanos() as f64 / iterations as f64;

    println!("Single asset parse: {:.2}ns per iteration", per_parse);
    // Target: <100μs (100,000ns) per parse - relaxed for comprehensive validation
    assert!(per_parse < 100_000.0, "Single parse too slow: {:.2}ns", per_parse);
}

// ============================================================================
// BOUNDARY CONDITIONS
// ============================================================================

#[test]
fn test_boundary_minimum_symbol_lengths() {
    // Minimum valid lengths
    let valid_min = vec![
        "A",      // 1-char equity
        "AB",     // 2-char equity
        "E.FUT",  // 1-char future
    ];

    for input in valid_min {
        let result = AssetParser::parse(input);
        // Single char may be rejected by validation
        if input.len() == 1 {
            // Single char equity ambiguous with futures
            assert!(result.is_err());
        } else {
            let _ = result; // May succeed depending on rules
        }
    }
}

#[test]
fn test_boundary_maximum_symbol_lengths() {
    // Maximum valid lengths (before rejection)
    let tests = vec![
        ("ABCDE", true),        // 5-char equity (max)
        ("ABCDEF", false),      // 6-char equity (too long)
        ("ABCD.FUT", true),     // 4-char future (max)
        ("ABCDE.FUT", false),   // 5-char future (too long)
    ];

    for (input, should_succeed) in tests {
        let result = AssetParser::parse(input);
        if should_succeed {
            assert!(result.is_ok(), "Input '{}' should be valid", input);
        } else {
            assert!(result.is_err(), "Input '{}' should be invalid", input);
        }
    }
}

#[test]
fn test_boundary_numeric_prefixes() {
    // Numbers at start are valid for futures (6E, 6J, etc.)
    let numeric_futures = vec![
        ("6E.FUT", true),
        ("6J.FUT", true),
        ("6A.FUT", true),
        ("9X.FUT", true),
        ("12.FUT", true), // All numeric before .FUT
    ];

    for (input, _should_succeed) in numeric_futures {
        let result = AssetParser::parse(input);
        assert!(result.is_ok(), "Numeric future '{}' should be valid", input);
        let assets = result.unwrap();
        assert_eq!(assets[0].symbol(), input.to_uppercase());
    }

    // Numbers in equities not allowed
    let numeric_equities = vec!["6E", "AAPL1", "A1", "123"];
    for input in numeric_equities {
        let result = AssetParser::parse(input);
        assert!(result.is_err(), "Numeric equity '{}' should be invalid", input);
    }
}

#[test]
fn test_deduplication_preserves_order() {
    // When duplicates exist, first occurrence should be kept
    let input = "ES.FUT,AAPL,NQ.FUT,ES.FUT,MSFT,AAPL";
    let result = AssetParser::parse(input).unwrap();

    assert_eq!(result.len(), 4); // Deduplicated

    // Verify all unique symbols present
    let symbols: HashSet<String> = result.iter()
        .map(|a| a.symbol().to_string())
        .collect();

    assert!(symbols.contains("ES.FUT"));
    assert!(symbols.contains("AAPL"));
    assert!(symbols.contains("NQ.FUT"));
    assert!(symbols.contains("MSFT"));
}

#[test]
fn test_validate_methods_consistency() {
    // Ensure validate_future and validate_equity are consistent with parse
    let test_futures = vec!["ES.FUT", "NQ.FUT", "6E.FUT", "ZN.FUT"];
    let test_equities = vec!["AAPL", "MSFT", "GOOGL", "TSLA"];

    for symbol in test_futures {
        let parsed = AssetParser::parse(symbol).unwrap();
        let validated = AssetParser::validate_future(symbol).unwrap();
        assert_eq!(parsed[0], validated);
    }

    for symbol in test_equities {
        let parsed = AssetParser::parse(symbol).unwrap();
        let validated = AssetParser::validate_equity(symbol).unwrap();
        assert_eq!(parsed[0], validated);
    }
}

#[test]
fn test_case_insensitive_deduplication() {
    // Case variations should deduplicate
    let result1 = AssetParser::parse("ES.FUT,es.fut,Es.FuT").unwrap();
    assert_eq!(result1.len(), 1); // All same symbol

    let result2 = AssetParser::parse("AAPL,aapl,AaPl").unwrap();
    assert_eq!(result2.len(), 1); // All same symbol

    let result3 = AssetParser::parse("ES.FUT,AAPL,es.fut,aapl").unwrap();
    assert_eq!(result3.len(), 2); // Two unique symbols
}

// ============================================================================
// ERROR MESSAGE QUALITY
// ============================================================================

#[test]
fn test_error_messages_descriptive() {
    let invalid_inputs = vec![
        ("", "empty"),
        ("ES", "Invalid asset format"),
        ("TOOLONG", "Invalid asset format"),
        ("ES@FUT", "Invalid asset format"),
    ];

    for (input, expected_msg) in invalid_inputs {
        let result = AssetParser::parse(input);
        assert!(result.is_err());
        let err_msg = result.unwrap_err().to_string();
        assert!(
            err_msg.to_lowercase().contains(&expected_msg.to_lowercase()),
            "Error for '{}' should contain '{}', got: {}",
            input, expected_msg, err_msg
        );
    }
}

#[test]
fn test_partial_failure_propagates() {
    // If ANY asset in list is invalid, entire parse fails
    let invalid_lists = vec![
        "ES.FUT,INVALID,NQ.FUT",
        "AAPL,TOOLONG,MSFT",
        "ES.FUT,AAPL,@#$",
    ];

    for input in invalid_lists {
        let result = AssetParser::parse(input);
        assert!(result.is_err(), "List with invalid asset should fail: {}", input);
    }
}
