//! Comprehensive tests for Parquet persistence functionality
//! Target: 35+ test functions for full coverage
#![allow(
    clippy::absurd_extreme_comparisons,
    clippy::assertions_on_constants,
    clippy::assertions_on_result_states,
    clippy::clone_on_copy,
    clippy::decimal_literal_representation,
    clippy::doc_lazy_continuation,
    clippy::doc_markdown,
    clippy::double_comparisons,
    clippy::else_if_without_else,
    clippy::empty_drop,
    clippy::expect_used,
    clippy::field_reassign_with_default,
    clippy::format_push_string,
    clippy::if_then_some_else_none,
    clippy::indexing_slicing,
    clippy::integer_division,
    clippy::len_zero,
    clippy::let_and_return,
    clippy::let_underscore_must_use,
    clippy::manual_let_else,
    clippy::manual_range_contains,
    clippy::map_err_ignore,
    clippy::missing_const_for_fn,
    clippy::modulo_arithmetic,
    clippy::needless_range_loop,
    clippy::new_without_default,
    clippy::non_ascii_literal,
    clippy::nonminimal_bool,
    clippy::octal_escapes,
    clippy::overly_complex_bool_expr,
    clippy::redundant_clone,
    clippy::shadow_reuse,
    clippy::shadow_unrelated,
    clippy::similar_names,
    clippy::single_component_path_imports,
    clippy::single_match_else,
    clippy::str_to_string,
    clippy::string_slice,
    clippy::tests_outside_test_module,
    clippy::unnecessary_cast,
    clippy::unnecessary_get_then_check,
    clippy::unnecessary_unwrap,
    clippy::unseparated_literal_suffix,
    clippy::unwrap_or_default,
    clippy::unwrap_used,
    clippy::use_debug,
    clippy::useless_vec,
    clippy::wildcard_enum_match_arm,
    dead_code,
    deprecated,
    unreachable_pub,
    unused_assignments,
    unused_comparisons,
    unused_imports,
    unused_variables
)]

use data::parquet_persistence::{
    MarketDataEvent, ParquetConfig, ParquetMarketDataReader, ParquetMarketDataWriter,
};
use parquet::basic::Compression;
use parquet::file::properties::EnabledStatistics;
use std::fs;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use tempfile::TempDir;
use tokio::time::{sleep, Duration};
use tracing_subscriber;

mod real_data_helpers;
use real_data_helpers::RealDataLoader;

// Test utilities and setup
struct TestSetup {
    temp_dir: TempDir,
    config: ParquetConfig,
}

impl TestSetup {
    fn new() -> Self {
        let temp_dir = tempfile::tempdir().expect("Failed to create temp directory");
        let config = ParquetConfig {
            base_path: temp_dir.path().to_string_lossy().to_string(),
            batch_size: 100,
            flush_interval_ms: 1000,
            compression: Compression::SNAPPY,
            enable_dictionary: true,
            enable_statistics: EnabledStatistics::Page,
        };
        Self { temp_dir, config }
    }

    fn custom_config(batch_size: usize, flush_interval_ms: u64) -> Self {
        let temp_dir = tempfile::tempdir().expect("Failed to create temp directory");
        let config = ParquetConfig {
            base_path: temp_dir.path().to_string_lossy().to_string(),
            batch_size,
            flush_interval_ms,
            compression: Compression::SNAPPY,
            enable_dictionary: true,
            enable_statistics: EnabledStatistics::Page,
        };
        Self { temp_dir, config }
    }

    fn with_compression(compression: Compression) -> Self {
        let temp_dir = tempfile::tempdir().expect("Failed to create temp directory");
        let config = ParquetConfig {
            base_path: temp_dir.path().to_string_lossy().to_string(),
            batch_size: 100,
            flush_interval_ms: 1000,
            compression,
            enable_dictionary: true,
            enable_statistics: EnabledStatistics::Page,
        };
        Self { temp_dir, config }
    }
}

fn create_test_event(timestamp_ns: u64, symbol: &str, sequence: u64) -> MarketDataEvent {
    MarketDataEvent {
        timestamp_ns,
        symbol: symbol.to_string(),
        venue: "test_venue".to_string(),
        event_type: trading_engine::types::metrics::MarketDataEventType::Trade,
        price: Some(100.0 + sequence as f64),
        quantity: Some(1.0),
        sequence,
        latency_ns: Some(1000),
        open: None,
        high: None,
        low: None,
    }
}

/// Load real DBN data events for testing
async fn load_real_btc_events(count: usize) -> Option<Vec<MarketDataEvent>> {
    let loader = RealDataLoader::new();
    if !loader.files_exist() {
        return None;
    }

    let reader = ParquetMarketDataReader::new(
        std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
            .parent()
            .unwrap()
            .join("test_data/real/parquet")
            .to_string_lossy()
            .to_string(),
    );

    match reader.read_file("BTC-USD_30day_2024-09.parquet").await {
        Ok(events) => Some(events.into_iter().take(count).collect()),
        Err(_) => None,
    }
}

/// Load real DBN data events for ETH
async fn load_real_eth_events(count: usize) -> Option<Vec<MarketDataEvent>> {
    let loader = RealDataLoader::new();
    if !loader.files_exist() {
        return None;
    }

    let reader = ParquetMarketDataReader::new(
        std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
            .parent()
            .unwrap()
            .join("test_data/real/parquet")
            .to_string_lossy()
            .to_string(),
    );

    match reader.read_file("ETH-USD_30day_2024-09.parquet").await {
        Ok(events) => Some(events.into_iter().take(count).collect()),
        Err(_) => None,
    }
}

fn create_quote_event(timestamp_ns: u64, symbol: &str, sequence: u64) -> MarketDataEvent {
    MarketDataEvent {
        timestamp_ns,
        symbol: symbol.to_string(),
        venue: "test_venue".to_string(),
        event_type: trading_engine::types::metrics::MarketDataEventType::Quote,
        price: Some(99.0 + sequence as f64 * 0.1),
        quantity: Some(100.0),
        sequence,
        latency_ns: Some(500),
        open: None,
        high: None,
        low: None,
    }
}

// Initialize test logging (call once per test process)
fn init_logging() {
    let _ = tracing_subscriber::fmt().with_test_writer().try_init();
}

// === BASIC FUNCTIONALITY TESTS ===

#[tokio::test]
async fn test_parquet_config_default() {
    let config = ParquetConfig::default();
    assert_eq!(config.base_path, "./market_data");
    assert_eq!(config.batch_size, 10000);
    assert_eq!(config.flush_interval_ms, 5000);
    assert_eq!(config.compression, Compression::SNAPPY);
    assert!(config.enable_dictionary);
    assert_eq!(config.enable_statistics, EnabledStatistics::Page);
}

#[tokio::test]
async fn test_market_data_event_creation() {
    let event = create_test_event(1234567890000000000, "BTCUSD", 1);
    assert_eq!(event.timestamp_ns, 1234567890000000000);
    assert_eq!(event.symbol, "BTCUSD");
    assert_eq!(event.venue, "test_venue");
    assert_eq!(
        event.event_type,
        trading_engine::types::metrics::MarketDataEventType::Trade
    );
    assert_eq!(event.price, Some(101.0));
    assert_eq!(event.sequence, 1);
}

#[tokio::test]
async fn test_parquet_writer_creation() {
    init_logging();
    let setup = TestSetup::new();

    let writer = ParquetMarketDataWriter::new(setup.config).await;
    assert!(writer.is_ok(), "Failed to create ParquetMarketDataWriter");
}

#[tokio::test]
async fn test_parquet_writer_creation_invalid_path() {
    init_logging();
    let config = ParquetConfig {
        base_path: "/invalid/path/that/cannot/be/created".to_string(),
        ..Default::default()
    };

    let writer = ParquetMarketDataWriter::new(config).await;
    assert!(writer.is_err(), "Should fail with invalid path");
}

#[tokio::test]
async fn test_single_event_recording() {
    init_logging();
    let setup = TestSetup::custom_config(1, 100); // Immediate flush

    let writer = ParquetMarketDataWriter::new(setup.config).await.unwrap();
    let event = create_test_event(1234567890000000000, "ETHUSD", 1);

    let result = writer.record(event);
    assert!(result.is_ok(), "Failed to record event");

    // Wait for background processing
    sleep(Duration::from_millis(200)).await;

    // Check that file was created
    let files: Vec<_> = fs::read_dir(setup.temp_dir.path())
        .unwrap()
        .filter_map(|entry| {
            let entry = entry.ok()?;
            let path = entry.path();
            if path.extension()?.to_str()? == "parquet" {
                Some(path)
            } else {
                None
            }
        })
        .collect();

    assert_eq!(files.len(), 1, "Expected exactly one parquet file");
}

// === BATCH PROCESSING TESTS ===

#[tokio::test]
async fn test_batch_size_flush() {
    init_logging();
    let setup = TestSetup::custom_config(5, 10000); // Large flush interval

    let writer = ParquetMarketDataWriter::new(setup.config).await.unwrap();

    // Send exactly batch_size events
    for i in 0..5 {
        let event = create_test_event(1234567890000000000 + i * 1000, "BTCUSD", i);
        writer.record(event).unwrap();
    }

    // Wait for batch flush
    sleep(Duration::from_millis(200)).await;

    let files: Vec<_> = fs::read_dir(setup.temp_dir.path())
        .unwrap()
        .filter_map(|entry| {
            let entry = entry.ok()?;
            let path = entry.path();
            if path.extension()?.to_str()? == "parquet" {
                Some(path)
            } else {
                None
            }
        })
        .collect();

    assert_eq!(
        files.len(),
        1,
        "Expected one parquet file after batch flush"
    );
}

#[tokio::test]
async fn test_time_based_flush() {
    init_logging();
    let setup = TestSetup::custom_config(1000, 100); // Small flush interval

    let writer = ParquetMarketDataWriter::new(setup.config).await.unwrap();

    // Send fewer events than batch size
    for i in 0..3 {
        let event = create_test_event(1234567890000000000 + i * 1000, "ETHUSD", i);
        writer.record(event).unwrap();
    }

    // Wait for time-based flush
    sleep(Duration::from_millis(300)).await;

    let files: Vec<_> = fs::read_dir(setup.temp_dir.path())
        .unwrap()
        .filter_map(|entry| {
            let entry = entry.ok()?;
            let path = entry.path();
            if path.extension()?.to_str()? == "parquet" {
                Some(path)
            } else {
                None
            }
        })
        .collect();

    assert_eq!(files.len(), 1, "Expected one parquet file after time flush");
}

#[tokio::test]
async fn test_multiple_batches() {
    init_logging();
    let setup = TestSetup::custom_config(3, 10000);

    let writer = ParquetMarketDataWriter::new(setup.config).await.unwrap();

    // Send two full batches
    for i in 0..6 {
        let event = create_test_event(1234567890000000000 + i * 1000, "BTCUSD", i);
        writer.record(event).unwrap();
    }

    sleep(Duration::from_millis(300)).await;

    let files: Vec<_> = fs::read_dir(setup.temp_dir.path())
        .unwrap()
        .filter_map(|entry| {
            let entry = entry.ok()?;
            let path = entry.path();
            if path.extension()?.to_str()? == "parquet" {
                Some(path)
            } else {
                None
            }
        })
        .collect();

    assert_eq!(files.len(), 2, "Expected two parquet files for two batches");
}

// === COMPRESSION TESTS ===

#[tokio::test]
async fn test_snappy_compression() {
    init_logging();
    let setup = TestSetup::with_compression(Compression::SNAPPY);

    let writer = ParquetMarketDataWriter::new(setup.config).await.unwrap();
    let event = create_test_event(1234567890000000000, "BTCUSD", 1);

    writer.record(event).unwrap();
    sleep(Duration::from_millis(200)).await;

    let files: Vec<_> = fs::read_dir(setup.temp_dir.path())
        .unwrap()
        .filter_map(|entry| {
            let entry = entry.ok()?;
            let path = entry.path();
            if path.extension()?.to_str()? == "parquet" {
                Some(path)
            } else {
                None
            }
        })
        .collect();

    assert_eq!(files.len(), 1);
    assert!(files[0].metadata().unwrap().len() > 0);
}

#[tokio::test]
async fn test_gzip_compression() {
    init_logging();
    let setup =
        TestSetup::with_compression(Compression::GZIP(parquet::basic::GzipLevel::default()));

    let writer = ParquetMarketDataWriter::new(setup.config).await.unwrap();
    let event = create_test_event(1234567890000000000, "ETHUSD", 1);

    writer.record(event).unwrap();
    sleep(Duration::from_millis(200)).await;

    let files: Vec<_> = fs::read_dir(setup.temp_dir.path())
        .unwrap()
        .filter_map(|entry| {
            let entry = entry.ok()?;
            let path = entry.path();
            if path.extension()?.to_str()? == "parquet" {
                Some(path)
            } else {
                None
            }
        })
        .collect();

    assert_eq!(files.len(), 1);
}

#[tokio::test]
async fn test_lz4_compression() {
    init_logging();
    let setup = TestSetup::with_compression(Compression::LZ4);

    let writer = ParquetMarketDataWriter::new(setup.config).await.unwrap();
    let event = create_test_event(1234567890000000000, "ADAUSD", 1);

    writer.record(event).unwrap();
    sleep(Duration::from_millis(200)).await;

    let files: Vec<_> = fs::read_dir(setup.temp_dir.path())
        .unwrap()
        .filter_map(|entry| {
            let entry = entry.ok()?;
            let path = entry.path();
            if path.extension()?.to_str()? == "parquet" {
                Some(path)
            } else {
                None
            }
        })
        .collect();

    assert_eq!(files.len(), 1);
}

#[tokio::test]
async fn test_uncompressed() {
    init_logging();
    let setup = TestSetup::with_compression(Compression::UNCOMPRESSED);

    let writer = ParquetMarketDataWriter::new(setup.config).await.unwrap();
    let event = create_test_event(1234567890000000000, "SOLUSD", 1);

    writer.record(event).unwrap();
    sleep(Duration::from_millis(200)).await;

    let files: Vec<_> = fs::read_dir(setup.temp_dir.path())
        .unwrap()
        .filter_map(|entry| {
            let entry = entry.ok()?;
            let path = entry.path();
            if path.extension()?.to_str()? == "parquet" {
                Some(path)
            } else {
                None
            }
        })
        .collect();

    assert_eq!(files.len(), 1);
}

// === DATA TYPE TESTS ===

#[tokio::test]
async fn test_trade_events() {
    init_logging();
    let setup = TestSetup::custom_config(1, 100);

    let writer = ParquetMarketDataWriter::new(setup.config).await.unwrap();
    let event = MarketDataEvent {
        timestamp_ns: 1234567890000000000,
        symbol: "BTCUSD".to_string(),
        venue: "binance".to_string(),
        event_type: trading_engine::types::metrics::MarketDataEventType::Trade,
        price: Some(50000.0),
        quantity: Some(0.1),
        sequence: 1,
        latency_ns: Some(1000),
        open: None,
        high: None,
        low: None,
    };

    writer.record(event).unwrap();
    sleep(Duration::from_millis(200)).await;

    let files: Vec<_> = fs::read_dir(setup.temp_dir.path())
        .unwrap()
        .filter_map(|entry| {
            let entry = entry.ok()?;
            let path = entry.path();
            if path.extension()?.to_str()? == "parquet" {
                Some(path)
            } else {
                None
            }
        })
        .collect();

    assert_eq!(files.len(), 1);
}

#[tokio::test]
async fn test_quote_events() {
    init_logging();
    let setup = TestSetup::custom_config(1, 100);

    let writer = ParquetMarketDataWriter::new(setup.config).await.unwrap();
    let event = create_quote_event(1234567890000000000, "ETHUSD", 1);

    writer.record(event).unwrap();
    sleep(Duration::from_millis(200)).await;

    let files: Vec<_> = fs::read_dir(setup.temp_dir.path())
        .unwrap()
        .filter_map(|entry| {
            let entry = entry.ok()?;
            let path = entry.path();
            if path.extension()?.to_str()? == "parquet" {
                Some(path)
            } else {
                None
            }
        })
        .collect();

    assert_eq!(files.len(), 1);
}

#[tokio::test]
async fn test_orderbook_events() {
    init_logging();
    let setup = TestSetup::custom_config(1, 100);

    let writer = ParquetMarketDataWriter::new(setup.config).await.unwrap();
    let event = MarketDataEvent {
        timestamp_ns: 1234567890000000000,
        symbol: "ADAUSD".to_string(),
        venue: "coinbase".to_string(),
        event_type: trading_engine::types::metrics::MarketDataEventType::OrderBookUpdate,
        price: None,
        quantity: None,
        sequence: 1,
        latency_ns: Some(2000),
        open: None,
        high: None,
        low: None,
    };

    writer.record(event).unwrap();
    sleep(Duration::from_millis(200)).await;

    let files: Vec<_> = fs::read_dir(setup.temp_dir.path())
        .unwrap()
        .filter_map(|entry| {
            let entry = entry.ok()?;
            let path = entry.path();
            if path.extension()?.to_str()? == "parquet" {
                Some(path)
            } else {
                None
            }
        })
        .collect();

    assert_eq!(files.len(), 1);
}

#[tokio::test]
async fn test_mixed_event_types() {
    init_logging();
    let setup = TestSetup::custom_config(10, 10000);

    let writer = ParquetMarketDataWriter::new(setup.config).await.unwrap();

    // Mix of different event types
    let events = vec![
        create_test_event(1234567890000000000, "BTCUSD", 1),
        create_quote_event(1234567890000001000, "BTCUSD", 2),
        MarketDataEvent {
            timestamp_ns: 1234567890000002000,
            symbol: "BTCUSD".to_string(),
            venue: "binance".to_string(),
            event_type: trading_engine::types::metrics::MarketDataEventType::StatusUpdate,
            price: None,
            quantity: None,
            sequence: 3,
            latency_ns: None,
            open: None,
            high: None,
            low: None,
        },
    ];

    for event in events {
        writer.record(event).unwrap();
    }

    sleep(Duration::from_millis(200)).await;
}

// === LARGE DATASET TESTS ===

#[tokio::test]
async fn test_large_batch_processing() {
    init_logging();
    let setup = TestSetup::custom_config(1000, 5000);

    let writer = ParquetMarketDataWriter::new(setup.config).await.unwrap();

    // Send 2500 events (2.5 batches)
    for i in 0..2500 {
        let event = create_test_event(1234567890000000000 + i * 1000, "BTCUSD", i);
        writer.record(event).unwrap();
    }

    sleep(Duration::from_millis(1000)).await;

    let files: Vec<_> = fs::read_dir(setup.temp_dir.path())
        .unwrap()
        .filter_map(|entry| {
            let entry = entry.ok()?;
            let path = entry.path();
            if path.extension()?.to_str()? == "parquet" {
                Some(path)
            } else {
                None
            }
        })
        .collect();

    assert!(
        files.len() >= 2,
        "Expected at least 2 files for large dataset"
    );
}

#[tokio::test]
async fn test_high_frequency_events() {
    init_logging();
    let setup = TestSetup::custom_config(100, 1000);

    let writer = ParquetMarketDataWriter::new(setup.config).await.unwrap();

    // Simulate high-frequency trading events
    let start_time = 1234567890000000000u64;
    for i in 0..500 {
        let event = create_test_event(start_time + i * 1000, "ETHUSD", i);
        writer.record(event).unwrap();
    }

    sleep(Duration::from_millis(2000)).await;

    let files: Vec<_> = fs::read_dir(setup.temp_dir.path())
        .unwrap()
        .filter_map(|entry| {
            let entry = entry.ok()?;
            let path = entry.path();
            if path.extension()?.to_str()? == "parquet" {
                Some(path)
            } else {
                None
            }
        })
        .collect();

    assert!(
        files.len() >= 1,
        "Expected at least 1 file for high-frequency data"
    );
}

// === BUFFER MANAGEMENT TESTS ===

#[tokio::test]
async fn test_buffer_stats() {
    init_logging();
    let setup = TestSetup::custom_config(1000, 10000); // Large batch, long interval

    let writer = ParquetMarketDataWriter::new(setup.config).await.unwrap();

    // Initial stats should show empty buffer
    let stats = writer.get_buffer_stats().await;
    assert_eq!(stats.buffered_events, 0);
    assert!(stats.buffer_capacity > 0);
    assert_eq!(stats.utilization_percent, 0.0);

    // Add some events
    for i in 0..10 {
        let event = create_test_event(1234567890000000000 + i * 1000, "BTCUSD", i);
        writer.record(event).unwrap();
    }

    // Give time for events to be queued
    sleep(Duration::from_millis(50)).await;

    let stats = writer.get_buffer_stats().await;
    // Note: Events might be processed quickly, so we can't guarantee exact count
    assert!(stats.buffer_capacity > 0);
}

#[tokio::test]
async fn test_buffer_utilization() {
    init_logging();
    let setup = TestSetup::custom_config(100, 10000); // Medium batch, long interval

    let writer = ParquetMarketDataWriter::new(setup.config).await.unwrap();

    // Fill buffer partially
    for i in 0..50 {
        let event = create_test_event(1234567890000000000 + i * 1000, "ETHUSD", i);
        writer.record(event).unwrap();
    }

    sleep(Duration::from_millis(100)).await;

    let stats = writer.get_buffer_stats().await;
    assert!(stats.buffer_capacity >= 100); // At least batch_size * 2
}

// === MULTITHREADING AND CONCURRENCY TESTS ===

#[tokio::test]
async fn test_concurrent_writes() {
    init_logging();
    let setup = TestSetup::custom_config(50, 1000);

    let writer = Arc::new(ParquetMarketDataWriter::new(setup.config).await.unwrap());
    let sequence_counter = Arc::new(AtomicU64::new(0));

    // Spawn multiple concurrent tasks
    let mut handles = Vec::new();
    for task_id in 0..5 {
        let writer_clone = writer.clone();
        let counter_clone = sequence_counter.clone();

        let handle = tokio::spawn(async move {
            for _i in 0..20 {
                let seq = counter_clone.fetch_add(1, Ordering::SeqCst);
                let event = create_test_event(
                    1234567890000000000 + seq * 1000,
                    &format!("SYM{}", task_id),
                    seq,
                );
                writer_clone.record(event).unwrap();
            }
        });
        handles.push(handle);
    }

    // Wait for all tasks to complete
    for handle in handles {
        handle.await.unwrap();
    }

    sleep(Duration::from_millis(2000)).await;

    let files: Vec<_> = fs::read_dir(setup.temp_dir.path())
        .unwrap()
        .filter_map(|entry| {
            let entry = entry.ok()?;
            let path = entry.path();
            if path.extension()?.to_str()? == "parquet" {
                Some(path)
            } else {
                None
            }
        })
        .collect();

    assert!(files.len() >= 1, "Expected files from concurrent writes");
}

// === SCHEMA AND PARTITIONING TESTS ===

#[tokio::test]
async fn test_multiple_symbols() {
    init_logging();
    let setup = TestSetup::custom_config(10, 1000);

    let writer = ParquetMarketDataWriter::new(setup.config).await.unwrap();

    let symbols = vec!["BTCUSD", "ETHUSD", "ADAUSD", "SOLUSD", "DOTUSD"];

    for (i, symbol) in symbols.into_iter().enumerate() {
        let event = create_test_event(1234567890000000000 + i as u64 * 1000, symbol, i as u64);
        writer.record(event).unwrap();
    }

    sleep(Duration::from_millis(2000)).await;

    let files: Vec<_> = fs::read_dir(setup.temp_dir.path())
        .unwrap()
        .filter_map(|entry| {
            let entry = entry.ok()?;
            let path = entry.path();
            if path.extension()?.to_str()? == "parquet" {
                Some(path)
            } else {
                None
            }
        })
        .collect();

    assert!(files.len() >= 1);
}

#[tokio::test]
async fn test_multiple_venues() {
    init_logging();
    let setup = TestSetup::custom_config(10, 1000);

    let writer = ParquetMarketDataWriter::new(setup.config).await.unwrap();

    let venues = vec!["binance", "coinbase", "kraken", "bitstamp", "gemini"];

    for (i, venue) in venues.into_iter().enumerate() {
        let mut event =
            create_test_event(1234567890000000000 + i as u64 * 1000, "BTCUSD", i as u64);
        event.venue = venue.to_string();
        writer.record(event).unwrap();
    }

    sleep(Duration::from_millis(2000)).await;
}

// === FILE NAMING AND ORGANIZATION TESTS ===

#[tokio::test]
async fn test_file_naming_convention() {
    init_logging();
    let setup = TestSetup::custom_config(1, 100);

    let writer = ParquetMarketDataWriter::new(setup.config).await.unwrap();
    let event = create_test_event(1234567890000000000, "BTCUSD", 1);

    writer.record(event).unwrap();
    sleep(Duration::from_millis(200)).await;

    let files: Vec<_> = fs::read_dir(setup.temp_dir.path())
        .unwrap()
        .filter_map(|entry| {
            let entry = entry.ok()?;
            let path = entry.path();
            if path.extension()?.to_str()? == "parquet" {
                path.file_name()?.to_str().map(|s| s.to_string())
            } else {
                None
            }
        })
        .collect();

    assert_eq!(files.len(), 1);
    let filename = &files[0];
    assert!(filename.starts_with("market_data_"));
    assert!(filename.ends_with(".parquet"));
    assert!(filename.contains("_")); // Contains timestamp and UUID
}

#[tokio::test]
async fn test_directory_creation() {
    init_logging();
    let temp_dir = tempfile::tempdir().unwrap();
    let nested_path = temp_dir.path().join("nested").join("path");

    let config = ParquetConfig {
        base_path: nested_path.to_string_lossy().to_string(),
        batch_size: 1,
        flush_interval_ms: 100,
        compression: Compression::SNAPPY,
        enable_dictionary: true,
        enable_statistics: EnabledStatistics::Page,
    };

    let writer = ParquetMarketDataWriter::new(config).await;
    assert!(writer.is_ok(), "Should create nested directories");

    let event = create_test_event(1234567890000000000, "TESTCOIN", 1);
    writer.unwrap().record(event).unwrap();

    sleep(Duration::from_millis(200)).await;
    assert!(nested_path.exists());
}

// === ERROR HANDLING AND EDGE CASES ===

#[tokio::test]
async fn test_empty_symbol() {
    init_logging();
    let setup = TestSetup::custom_config(1, 100);

    let writer = ParquetMarketDataWriter::new(setup.config).await.unwrap();
    let mut event = create_test_event(1234567890000000000, "", 1);
    event.symbol = "".to_string();

    let result = writer.record(event);
    assert!(result.is_ok(), "Should handle empty symbol");

    sleep(Duration::from_millis(200)).await;
}

#[tokio::test]
async fn test_extreme_values() {
    init_logging();
    let setup = TestSetup::custom_config(1, 100);

    let writer = ParquetMarketDataWriter::new(setup.config).await.unwrap();
    let event = MarketDataEvent {
        timestamp_ns: u64::MAX,
        symbol: "EXTREME".to_string(),
        venue: "test".to_string(),
        event_type: trading_engine::types::metrics::MarketDataEventType::Trade,
        price: Some(f64::MAX),
        quantity: Some(f64::MIN_POSITIVE),
        sequence: u64::MAX,
        latency_ns: Some(u64::MAX),
        open: None,
        high: None,
        low: None,
    };

    let result = writer.record(event);
    assert!(result.is_ok(), "Should handle extreme values");

    sleep(Duration::from_millis(200)).await;
}

#[tokio::test]
async fn test_unicode_symbols() {
    init_logging();
    let setup = TestSetup::custom_config(1, 100);

    let writer = ParquetMarketDataWriter::new(setup.config).await.unwrap();
    let mut event = create_test_event(1234567890000000000, "测试币", 1);
    event.venue = "交易所".to_string();

    let result = writer.record(event);
    assert!(result.is_ok(), "Should handle Unicode strings");

    sleep(Duration::from_millis(200)).await;
}

#[tokio::test]
async fn test_null_optional_fields() {
    init_logging();
    let setup = TestSetup::custom_config(1, 100);

    let writer = ParquetMarketDataWriter::new(setup.config).await.unwrap();
    let event = MarketDataEvent {
        timestamp_ns: 1234567890000000000,
        symbol: "NULLTEST".to_string(),
        venue: "test".to_string(),
        event_type: trading_engine::types::metrics::MarketDataEventType::Trade,
        price: None,
        quantity: None,
        sequence: 1,
        latency_ns: None,
        open: None,
        high: None,
        low: None,
    };

    let result = writer.record(event);
    assert!(result.is_ok(), "Should handle all null optional fields");

    sleep(Duration::from_millis(200)).await;
}

// === READER TESTS ===

#[tokio::test]
async fn test_reader_creation() {
    let temp_dir = tempfile::tempdir().unwrap();
    let reader = ParquetMarketDataReader::new(temp_dir.path().to_string_lossy().to_string());

    // Just test that reader can be created
    assert_eq!(
        reader.base_path(),
        temp_dir.path().to_string_lossy().to_string()
    );
}

#[tokio::test]
async fn test_reader_list_empty_directory() {
    let temp_dir = tempfile::tempdir().unwrap();
    let reader = ParquetMarketDataReader::new(temp_dir.path().to_string_lossy().to_string());

    let files = reader.list_available_files().await.unwrap();
    assert_eq!(files.len(), 0, "Empty directory should have no files");
}

#[tokio::test]
async fn test_reader_list_with_parquet_files() {
    let temp_dir = tempfile::tempdir().unwrap();

    // Create some test files
    fs::write(temp_dir.path().join("test1.parquet"), b"fake parquet").unwrap();
    fs::write(temp_dir.path().join("test2.parquet"), b"fake parquet").unwrap();
    fs::write(temp_dir.path().join("test.txt"), b"not parquet").unwrap();

    let reader = ParquetMarketDataReader::new(temp_dir.path().to_string_lossy().to_string());
    let files = reader.list_available_files().await.unwrap();

    assert_eq!(files.len(), 2, "Should find only parquet files");
    assert!(files.contains(&"test1.parquet".to_string()));
    assert!(files.contains(&"test2.parquet".to_string()));
    assert!(files[0] <= files[1], "Files should be sorted");
}

#[tokio::test]
async fn test_reader_invalid_directory() {
    let reader = ParquetMarketDataReader::new("/invalid/path/that/does/not/exist".to_string());

    let result = reader.list_available_files().await;
    assert!(result.is_err(), "Should fail for invalid directory");
}

#[tokio::test]
async fn test_reader_read_placeholder() {
    let temp_dir = tempfile::tempdir().unwrap();
    let reader = ParquetMarketDataReader::new(temp_dir.path().to_string_lossy().to_string());

    // Test placeholder implementation
    let events = reader.read_file("nonexistent.parquet").await.unwrap();
    assert_eq!(events.len(), 0, "Placeholder should return empty vec");
}

// === PERFORMANCE AND MONITORING TESTS ===

#[tokio::test]
async fn test_metrics_integration() {
    init_logging();
    let setup = TestSetup::custom_config(1, 100);

    let writer = ParquetMarketDataWriter::new(setup.config).await.unwrap();
    let event = create_test_event(1234567890000000000, "METRICSTEST", 1);

    writer.record(event).unwrap();
    sleep(Duration::from_millis(300)).await;

    // Metrics should be recorded, but we can't easily test them without
    // accessing the actual metrics registry
}

#[tokio::test]
async fn test_performance_timing() {
    init_logging();
    let setup = TestSetup::custom_config(100, 5000);

    let writer = ParquetMarketDataWriter::new(setup.config).await.unwrap();

    let start = std::time::Instant::now();

    // Write many events quickly
    for i in 0..1000 {
        let event = create_test_event(1234567890000000000 + i * 1000, "PERFTEST", i);
        writer.record(event).unwrap();
    }

    let record_duration = start.elapsed();
    println!("Recorded 1000 events in {:?}", record_duration);

    // Should be very fast for recording (just queuing)
    assert!(
        record_duration.as_millis() < 100,
        "Recording should be fast"
    );

    sleep(Duration::from_millis(2000)).await;
}

// === ERROR HANDLING TESTS (NEW) ===

#[tokio::test]
async fn test_write_to_readonly_directory() {
    init_logging();
    let temp_dir = tempfile::tempdir().unwrap();
    let readonly_path = temp_dir.path().join("readonly");
    fs::create_dir_all(&readonly_path).unwrap();

    // Set directory to read-only on Unix systems
    #[cfg(unix)]
    {
        use std::os::unix::fs::PermissionsExt;
        let mut perms = fs::metadata(&readonly_path).unwrap().permissions();
        perms.set_mode(0o444); // Read-only
        fs::set_permissions(&readonly_path, perms).unwrap();
    }

    let config = ParquetConfig {
        base_path: readonly_path.to_string_lossy().to_string(),
        batch_size: 1,
        flush_interval_ms: 100,
        compression: Compression::SNAPPY,
        enable_dictionary: true,
        enable_statistics: EnabledStatistics::Page,
    };

    let writer = ParquetMarketDataWriter::new(config).await.unwrap();
    let event = create_test_event(1234567890000000000, "TESTCOIN", 1);
    writer.record(event).unwrap();

    // Wait for background processing to attempt write
    sleep(Duration::from_millis(300)).await;

    // The error should be logged, but writer continues to operate
    // This tests error resilience in background task

    // Cleanup: Reset permissions so temp_dir can be deleted
    #[cfg(unix)]
    {
        use std::os::unix::fs::PermissionsExt;
        let mut perms = fs::metadata(&readonly_path).unwrap().permissions();
        perms.set_mode(0o755); // Restore write permissions
        fs::set_permissions(&readonly_path, perms).unwrap();
    }
}

#[tokio::test]
async fn test_writer_with_invalid_parent_path() {
    init_logging();
    // Try to create writer in a path that cannot exist
    let config = ParquetConfig {
        base_path: "/proc/self/mem/invalid".to_string(),
        ..Default::default()
    };

    let result = ParquetMarketDataWriter::new(config).await;
    assert!(
        result.is_err(),
        "Should fail to create writer with invalid path"
    );
}

#[tokio::test]
async fn test_empty_batch_edge_case() {
    init_logging();
    let setup = TestSetup::custom_config(0, 100); // Zero batch size edge case

    // This tests the behavior when batch_size is 0
    let result = ParquetMarketDataWriter::new(setup.config).await;
    assert!(result.is_ok(), "Should handle zero batch size gracefully");
}

#[tokio::test]
async fn test_very_long_symbol_name() {
    init_logging();
    let setup = TestSetup::custom_config(1, 100);

    let writer = ParquetMarketDataWriter::new(setup.config).await.unwrap();

    // Create symbol with 1000 characters
    let long_symbol = "A".repeat(1000);
    let mut event = create_test_event(1234567890000000000, "BTC", 1);
    event.symbol = long_symbol;

    let result = writer.record(event);
    assert!(result.is_ok(), "Should handle very long symbol names");

    sleep(Duration::from_millis(200)).await;
}

#[tokio::test]
async fn test_very_long_venue_name() {
    init_logging();
    let setup = TestSetup::custom_config(1, 100);

    let writer = ParquetMarketDataWriter::new(setup.config).await.unwrap();

    // Create venue with 1000 characters
    let long_venue = "V".repeat(1000);
    let mut event = create_test_event(1234567890000000000, "BTC", 1);
    event.venue = long_venue;

    let result = writer.record(event);
    assert!(result.is_ok(), "Should handle very long venue names");

    sleep(Duration::from_millis(200)).await;
}

#[tokio::test]
async fn test_special_characters_in_paths() {
    init_logging();
    let temp_dir = tempfile::tempdir().unwrap();

    // Create path with special characters (spaces, unicode, etc)
    let special_path = temp_dir.path().join("special path 测试 !@#");

    let config = ParquetConfig {
        base_path: special_path.to_string_lossy().to_string(),
        batch_size: 1,
        flush_interval_ms: 100,
        compression: Compression::SNAPPY,
        enable_dictionary: true,
        enable_statistics: EnabledStatistics::Page,
    };

    let result = ParquetMarketDataWriter::new(config).await;
    assert!(result.is_ok(), "Should handle special characters in path");

    if let Ok(writer) = result {
        let event = create_test_event(1234567890000000000, "TEST", 1);
        writer.record(event).unwrap();
        sleep(Duration::from_millis(200)).await;

        assert!(
            special_path.exists(),
            "Directory with special chars should exist"
        );
    }
}

#[tokio::test]
async fn test_rapid_writer_creation_and_drop() {
    init_logging();

    // Test that creating and dropping writers rapidly doesn't cause issues
    for i in 0..10 {
        let temp_dir = tempfile::tempdir().unwrap();
        let config = ParquetConfig {
            base_path: temp_dir.path().to_string_lossy().to_string(),
            batch_size: 1,
            flush_interval_ms: 100,
            ..Default::default()
        };

        let writer = ParquetMarketDataWriter::new(config).await.unwrap();
        let event = create_test_event(1234567890000000000 + i, "RAPID", i);
        writer.record(event).unwrap();

        // Writer drops immediately - test cleanup handling
    }

    sleep(Duration::from_millis(500)).await;
}

#[tokio::test]
async fn test_negative_timestamp_handling() {
    init_logging();
    let setup = TestSetup::custom_config(1, 100);

    let writer = ParquetMarketDataWriter::new(setup.config).await.unwrap();

    // Test with timestamp that would be negative when cast to i64
    let event = MarketDataEvent {
        timestamp_ns: 0, // Minimum valid timestamp
        symbol: "ZEROTIME".to_string(),
        venue: "test".to_string(),
        event_type: trading_engine::types::metrics::MarketDataEventType::Trade,
        price: Some(100.0),
        quantity: Some(1.0),
        sequence: 1,
        latency_ns: Some(1000),
        open: None,
        high: None,
        low: None,
    };

    let result = writer.record(event);
    assert!(result.is_ok(), "Should handle zero timestamp");

    sleep(Duration::from_millis(200)).await;
}

#[tokio::test]
async fn test_nan_and_infinity_values() {
    init_logging();
    let setup = TestSetup::custom_config(1, 100);

    let writer = ParquetMarketDataWriter::new(setup.config).await.unwrap();

    // Test NaN
    let event_nan = MarketDataEvent {
        timestamp_ns: 1234567890000000000,
        symbol: "NANTEST".to_string(),
        venue: "test".to_string(),
        event_type: trading_engine::types::metrics::MarketDataEventType::Trade,
        price: Some(f64::NAN),
        quantity: Some(f64::NAN),
        sequence: 1,
        latency_ns: Some(1000),
        open: None,
        high: None,
        low: None,
    };

    let result = writer.record(event_nan);
    assert!(result.is_ok(), "Should handle NaN values");

    sleep(Duration::from_millis(100)).await;

    // Test Infinity
    let event_inf = MarketDataEvent {
        timestamp_ns: 1234567890000000001,
        symbol: "INFTEST".to_string(),
        venue: "test".to_string(),
        event_type: trading_engine::types::metrics::MarketDataEventType::Trade,
        price: Some(f64::INFINITY),
        quantity: Some(f64::NEG_INFINITY),
        sequence: 2,
        latency_ns: Some(2000),
        open: None,
        high: None,
        low: None,
    };

    let result = writer.record(event_inf);
    assert!(result.is_ok(), "Should handle Infinity values");

    sleep(Duration::from_millis(200)).await;
}

#[tokio::test]
async fn test_sequence_overflow() {
    init_logging();
    let setup = TestSetup::custom_config(1, 100);

    let writer = ParquetMarketDataWriter::new(setup.config).await.unwrap();

    // Test with maximum sequence number
    let event = MarketDataEvent {
        timestamp_ns: 1234567890000000000,
        symbol: "SEQMAX".to_string(),
        venue: "test".to_string(),
        event_type: trading_engine::types::metrics::MarketDataEventType::Trade,
        price: Some(100.0),
        quantity: Some(1.0),
        sequence: u64::MAX,
        latency_ns: Some(u64::MAX),
        open: None,
        high: None,
        low: None,
    };

    let result = writer.record(event);
    assert!(result.is_ok(), "Should handle maximum sequence number");

    sleep(Duration::from_millis(200)).await;
}

#[tokio::test]
async fn test_buffer_stats_during_flush() {
    init_logging();
    let setup = TestSetup::custom_config(10, 5000); // Long flush interval

    let writer = ParquetMarketDataWriter::new(setup.config).await.unwrap();

    // Fill buffer close to capacity
    for i in 0..8 {
        let event = create_test_event(1234567890000000000 + i * 1000, "BTCUSD", i);
        writer.record(event).unwrap();
    }

    sleep(Duration::from_millis(50)).await;

    // Check stats while buffer is full
    let stats_before = writer.get_buffer_stats().await;

    // Trigger flush by sending more events
    for i in 8..12 {
        let event = create_test_event(1234567890000000000 + i * 1000, "BTCUSD", i);
        writer.record(event).unwrap();
    }

    sleep(Duration::from_millis(200)).await;

    let stats_after = writer.get_buffer_stats().await;

    // After flush, buffer should have fewer events
    assert!(stats_after.buffered_events <= stats_before.buffered_events + 4);
}

#[tokio::test]
async fn test_reader_with_mixed_file_types() {
    let temp_dir = tempfile::tempdir().unwrap();

    // Create various file types
    fs::write(temp_dir.path().join("data1.parquet"), b"fake").unwrap();
    fs::write(temp_dir.path().join("data2.PARQUET"), b"fake").unwrap(); // Uppercase extension
    fs::write(temp_dir.path().join("data.parquet.tmp"), b"fake").unwrap();
    fs::write(temp_dir.path().join("data.txt"), b"fake").unwrap();
    fs::write(temp_dir.path().join(".hidden.parquet"), b"fake").unwrap();

    let reader = ParquetMarketDataReader::new(temp_dir.path().to_string_lossy().to_string());
    let files = reader.list_available_files().await.unwrap();

    // Should only find .parquet files (lowercase extension)
    assert!(files.contains(&"data1.parquet".to_string()));
    assert!(!files.contains(&"data2.PARQUET".to_string())); // Uppercase not matched
    assert!(!files.contains(&"data.parquet.tmp".to_string()));
    assert!(!files.contains(&"data.txt".to_string()));
}

#[tokio::test]
async fn test_reader_with_subdirectories() {
    let temp_dir = tempfile::tempdir().unwrap();

    // Create subdirectory with parquet file
    let subdir = temp_dir.path().join("subdir");
    fs::create_dir_all(&subdir).unwrap();
    fs::write(subdir.join("nested.parquet"), b"fake").unwrap();
    fs::write(temp_dir.path().join("root.parquet"), b"fake").unwrap();

    let reader = ParquetMarketDataReader::new(temp_dir.path().to_string_lossy().to_string());
    let files = reader.list_available_files().await.unwrap();

    // Should only list files in root directory, not subdirectories
    assert_eq!(files.len(), 1);
    assert!(files.contains(&"root.parquet".to_string()));
    assert!(!files.contains(&"nested.parquet".to_string()));
}

#[tokio::test]
async fn test_reader_list_after_permission_denied() {
    let temp_dir = tempfile::tempdir().unwrap();

    #[cfg(unix)]
    {
        use std::os::unix::fs::PermissionsExt;

        // Create a file and make directory unreadable
        fs::write(temp_dir.path().join("test.parquet"), b"fake").unwrap();

        let mut perms = fs::metadata(temp_dir.path()).unwrap().permissions();
        perms.set_mode(0o000); // No permissions
        fs::set_permissions(temp_dir.path(), perms.clone()).unwrap();

        let reader = ParquetMarketDataReader::new(temp_dir.path().to_string_lossy().to_string());
        let result = reader.list_available_files().await;

        // Restore permissions for cleanup
        perms.set_mode(0o755);
        fs::set_permissions(temp_dir.path(), perms).unwrap();

        assert!(
            result.is_err(),
            "Should fail when directory is not readable"
        );
    }

    // On Windows, skip this test as permission model is different
    #[cfg(not(unix))]
    {
        let reader = ParquetMarketDataReader::new(temp_dir.path().to_string_lossy().to_string());
        let _ = reader.list_available_files().await; // Just ensure it doesn't panic
    }
}

#[tokio::test]
async fn test_concurrent_readers() {
    let temp_dir = tempfile::tempdir().unwrap();

    // Create some parquet files
    for i in 0..5 {
        fs::write(
            temp_dir.path().join(format!("data{}.parquet", i)),
            b"fake data",
        )
        .unwrap();
    }

    let reader = Arc::new(ParquetMarketDataReader::new(
        temp_dir.path().to_string_lossy().to_string(),
    ));

    // Spawn multiple concurrent read tasks
    let mut handles = Vec::new();
    for _ in 0..10 {
        let reader_clone = reader.clone();
        let handle = tokio::spawn(async move {
            let files = reader_clone.list_available_files().await.unwrap();
            assert_eq!(files.len(), 5);
        });
        handles.push(handle);
    }

    // Wait for all readers to complete
    for handle in handles {
        handle.await.unwrap();
    }
}

#[tokio::test]
async fn test_reader_file_sorting() {
    let temp_dir = tempfile::tempdir().unwrap();

    // Create files with specific names to test sorting
    let filenames = vec![
        "c.parquet",
        "a.parquet",
        "b.parquet",
        "10.parquet",
        "2.parquet",
    ];
    for name in &filenames {
        fs::write(temp_dir.path().join(name), b"fake").unwrap();
    }

    let reader = ParquetMarketDataReader::new(temp_dir.path().to_string_lossy().to_string());
    let files = reader.list_available_files().await.unwrap();

    // Verify files are sorted
    assert_eq!(files.len(), 5);
    for i in 0..files.len() - 1 {
        assert!(
            files[i] <= files[i + 1],
            "Files should be sorted alphabetically"
        );
    }
}

#[tokio::test]
async fn test_zero_flush_interval() {
    init_logging();
    let setup = TestSetup::custom_config(1000, 0); // Zero flush interval

    let writer = ParquetMarketDataWriter::new(setup.config).await.unwrap();
    let event = create_test_event(1234567890000000000, "ZEROFLUSH", 1);

    writer.record(event).unwrap();

    // Even with zero interval, should handle gracefully
    sleep(Duration::from_millis(200)).await;
}

#[tokio::test]
async fn test_very_large_batch_size() {
    init_logging();
    let setup = TestSetup::custom_config(1_000_000, 10000); // Very large batch

    let result = ParquetMarketDataWriter::new(setup.config).await;
    assert!(result.is_ok(), "Should handle very large batch size");

    if let Ok(writer) = result {
        let event = create_test_event(1234567890000000000, "LARGEBATCH", 1);
        writer.record(event).unwrap();
        sleep(Duration::from_millis(100)).await;
    }
}

#[tokio::test]
async fn test_statistics_disabled() {
    init_logging();
    let temp_dir = tempfile::tempdir().unwrap();

    let config = ParquetConfig {
        base_path: temp_dir.path().to_string_lossy().to_string(),
        batch_size: 1,
        flush_interval_ms: 100,
        compression: Compression::SNAPPY,
        enable_dictionary: false,
        enable_statistics: EnabledStatistics::None,
    };

    let writer = ParquetMarketDataWriter::new(config).await.unwrap();
    let event = create_test_event(1234567890000000000, "NOSTATS", 1);

    let result = writer.record(event);
    assert!(result.is_ok(), "Should work with statistics disabled");

    sleep(Duration::from_millis(200)).await;
}

#[tokio::test]
async fn test_dictionary_disabled() {
    init_logging();
    let temp_dir = tempfile::tempdir().unwrap();

    let config = ParquetConfig {
        base_path: temp_dir.path().to_string_lossy().to_string(),
        batch_size: 1,
        flush_interval_ms: 100,
        compression: Compression::SNAPPY,
        enable_dictionary: false,
        enable_statistics: EnabledStatistics::Page,
    };

    let writer = ParquetMarketDataWriter::new(config).await.unwrap();
    let event = create_test_event(1234567890000000000, "NODICT", 1);

    let result = writer.record(event);
    assert!(result.is_ok(), "Should work with dictionary disabled");

    sleep(Duration::from_millis(200)).await;
}

#[tokio::test]
async fn test_all_event_types() {
    init_logging();
    let setup = TestSetup::custom_config(10, 1000);

    let writer = ParquetMarketDataWriter::new(setup.config).await.unwrap();

    // Test all possible event types
    let event_types = vec![
        trading_engine::types::metrics::MarketDataEventType::Trade,
        trading_engine::types::metrics::MarketDataEventType::Quote,
        trading_engine::types::metrics::MarketDataEventType::OrderBookUpdate,
        trading_engine::types::metrics::MarketDataEventType::StatusUpdate,
    ];

    for (i, event_type) in event_types.into_iter().enumerate() {
        let event = MarketDataEvent {
            timestamp_ns: 1234567890000000000 + i as u64 * 1000,
            symbol: "ALLTYPE".to_string(),
            venue: "test".to_string(),
            event_type,
            price: Some(100.0),
            quantity: Some(1.0),
            sequence: i as u64,
            latency_ns: Some(1000),
            open: None,
            high: None,
            low: None,
        };
        writer.record(event).unwrap();
    }

    sleep(Duration::from_millis(2000)).await;

    let files: Vec<_> = fs::read_dir(setup.temp_dir.path())
        .unwrap()
        .filter_map(|entry| {
            let entry = entry.ok()?;
            let path = entry.path();
            if path.extension()?.to_str()? == "parquet" {
                Some(path)
            } else {
                None
            }
        })
        .collect();

    assert!(
        files.len() >= 1,
        "Should create parquet files for all event types"
    );
}

#[tokio::test]
async fn test_writer_channel_closure() {
    init_logging();
    let setup = TestSetup::custom_config(1, 100);

    let writer = ParquetMarketDataWriter::new(setup.config).await.unwrap();
    let event = create_test_event(1234567890000000000, "TESTCLOSE", 1);
    writer.record(event).unwrap();

    // Drop writer to trigger channel closure
    drop(writer);

    // Wait for background task cleanup
    sleep(Duration::from_millis(300)).await;

    // Test passes if no panic occurs
}

#[tokio::test]
async fn test_empty_event_batch_handling() {
    init_logging();
    let setup = TestSetup::custom_config(1, 50); // Very short flush interval

    let _writer = ParquetMarketDataWriter::new(setup.config).await.unwrap();

    // Don't send any events, just wait for flush timer
    sleep(Duration::from_millis(200)).await;

    // Should handle empty flushes gracefully without creating files
    let files: Vec<_> = fs::read_dir(setup.temp_dir.path())
        .unwrap()
        .filter_map(|entry| {
            let entry = entry.ok()?;
            let path = entry.path();
            if path.extension()?.to_str()? == "parquet" {
                Some(path)
            } else {
                None
            }
        })
        .collect();

    assert_eq!(files.len(), 0, "Should not create files for empty batches");
}

// ============================================================================
// REAL DBN DATA TESTS
// ============================================================================

#[tokio::test]
async fn test_parquet_write_real_btc_data() {
    init_logging();

    // Load real BTC data
    let real_events = match load_real_btc_events(1000).await {
        Some(events) if !events.is_empty() => events,
        _ => {
            println!("Skipping test - real DBN BTC data not available");
            return;
        },
    };

    println!(
        "✓ Loaded {} real BTC events from DBN data",
        real_events.len()
    );

    let setup = TestSetup::custom_config(500, 1000);
    let writer = ParquetMarketDataWriter::new(setup.config).await.unwrap();

    // Write real events
    for event in real_events {
        writer.record(event).unwrap();
    }

    sleep(Duration::from_millis(2000)).await;

    let files: Vec<_> = fs::read_dir(setup.temp_dir.path())
        .unwrap()
        .filter_map(|entry| {
            let entry = entry.ok()?;
            let path = entry.path();
            if path.extension()?.to_str()? == "parquet" {
                Some(path)
            } else {
                None
            }
        })
        .collect();

    assert!(
        files.len() >= 2,
        "Expected at least 2 files for real BTC data"
    );

    // Verify file sizes
    let total_size: u64 = files
        .iter()
        .filter_map(|f| f.metadata().ok())
        .map(|m| m.len())
        .sum();

    println!(
        "✓ Total Parquet size: {} bytes for {} events",
        total_size, 1000
    );
    println!(
        "✓ Compression ratio: {:.2}:1",
        1000.0 * 100.0 / total_size as f64
    );
    assert!(total_size > 1000, "Files should contain actual data");
}

#[tokio::test]
async fn test_parquet_write_real_eth_data() {
    init_logging();

    // Load real ETH data
    let real_events = match load_real_eth_events(1000).await {
        Some(events) if !events.is_empty() => events,
        _ => {
            println!("Skipping test - real DBN ETH data not available");
            return;
        },
    };

    println!(
        "✓ Loaded {} real ETH events from DBN data",
        real_events.len()
    );

    let setup = TestSetup::custom_config(500, 1000);
    let writer = ParquetMarketDataWriter::new(setup.config).await.unwrap();

    // Write real events
    for event in real_events {
        writer.record(event).unwrap();
    }

    sleep(Duration::from_millis(2000)).await;

    let files: Vec<_> = fs::read_dir(setup.temp_dir.path())
        .unwrap()
        .filter_map(|entry| {
            let entry = entry.ok()?;
            let path = entry.path();
            if path.extension()?.to_str()? == "parquet" {
                Some(path)
            } else {
                None
            }
        })
        .collect();

    assert!(
        files.len() >= 2,
        "Expected at least 2 files for real ETH data"
    );

    // Verify file sizes
    let total_size: u64 = files
        .iter()
        .filter_map(|f| f.metadata().ok())
        .map(|m| m.len())
        .sum();

    println!(
        "✓ Total Parquet size: {} bytes for {} events",
        total_size, 1000
    );
    assert!(total_size > 1000, "Files should contain actual data");
}

#[tokio::test]
async fn test_parquet_compression_with_real_data() {
    init_logging();

    // Load real data for compression testing
    let real_events = match load_real_btc_events(5000).await {
        Some(events) if !events.is_empty() => events,
        _ => {
            println!("Skipping test - real DBN data not available");
            return;
        },
    };

    println!(
        "✓ Loaded {} real events for compression test",
        real_events.len()
    );

    let temp_dir_snappy = TempDir::new().unwrap();
    let temp_dir_gzip = TempDir::new().unwrap();

    let config_snappy = ParquetConfig {
        base_path: temp_dir_snappy.path().to_string_lossy().to_string(),
        batch_size: 1000,
        flush_interval_ms: 100,
        compression: parquet::basic::Compression::SNAPPY,
        enable_dictionary: true,
        enable_statistics: EnabledStatistics::Page,
    };

    let config_gzip = ParquetConfig {
        base_path: temp_dir_gzip.path().to_string_lossy().to_string(),
        batch_size: 1000,
        flush_interval_ms: 100,
        compression: parquet::basic::Compression::GZIP(parquet::basic::GzipLevel::default()),
        enable_dictionary: true,
        enable_statistics: EnabledStatistics::Page,
    };

    let writer_snappy = ParquetMarketDataWriter::new(config_snappy.clone())
        .await
        .unwrap();
    let writer_gzip = ParquetMarketDataWriter::new(config_gzip.clone())
        .await
        .unwrap();

    // Write same real data to both
    for event in real_events.clone() {
        writer_snappy.record(event.clone()).unwrap();
        writer_gzip.record(event).unwrap();
    }

    sleep(Duration::from_millis(3000)).await;

    // Compare file sizes
    let snappy_files: Vec<_> = fs::read_dir(temp_dir_snappy.path())
        .unwrap()
        .filter_map(|e| e.ok())
        .collect();
    let gzip_files: Vec<_> = fs::read_dir(temp_dir_gzip.path())
        .unwrap()
        .filter_map(|e| e.ok())
        .collect();

    let snappy_size: u64 = snappy_files
        .iter()
        .filter_map(|f| f.metadata().ok())
        .map(|m| m.len())
        .sum();
    let gzip_size: u64 = gzip_files
        .iter()
        .filter_map(|f| f.metadata().ok())
        .map(|m| m.len())
        .sum();

    println!(
        "✓ SNAPPY: {} bytes, GZIP: {} bytes (real data)",
        snappy_size, gzip_size
    );
    println!(
        "✓ GZIP saves: {:.1}% vs SNAPPY",
        (1.0 - gzip_size as f64 / snappy_size as f64) * 100.0
    );

    assert!(
        snappy_size > 0 && gzip_size > 0,
        "Both compressions should produce data"
    );
    // GZIP typically achieves better compression
    assert!(
        gzip_size < snappy_size * 2,
        "GZIP should be competitive with SNAPPY"
    );
}

#[tokio::test]
async fn test_parquet_read_write_cycle_real_data() {
    init_logging();

    // Load real data
    let real_events = match load_real_btc_events(100).await {
        Some(events) if !events.is_empty() => events,
        _ => {
            println!("Skipping test - real DBN data not available");
            return;
        },
    };

    println!(
        "✓ Loaded {} real events for read/write cycle test",
        real_events.len()
    );

    let temp_dir = TempDir::new().unwrap();
    let config = ParquetConfig {
        base_path: temp_dir.path().to_string_lossy().to_string(),
        batch_size: 50,
        flush_interval_ms: 100,
        compression: parquet::basic::Compression::SNAPPY,
        enable_dictionary: true,
        enable_statistics: EnabledStatistics::Page,
    };

    // Write events
    let writer = ParquetMarketDataWriter::new(config.clone()).await.unwrap();
    let original_count = real_events.len();

    for event in real_events {
        writer.record(event).unwrap();
    }

    sleep(Duration::from_millis(500)).await;

    // Read back and verify
    let reader = ParquetMarketDataReader::new(config.base_path.clone());
    let files = reader.list_available_files().await.unwrap();

    assert!(!files.is_empty(), "Should have created Parquet files");

    let mut _total_read_events = 0;
    for file in &files {
        match reader.read_file(file).await {
            Ok(events) => {
                _total_read_events += events.len();
            },
            Err(e) => {
                println!("Warning: placeholder read_file returned error: {}", e);
                // Placeholder implementation returns empty vec
            },
        }
    }

    println!(
        "✓ Read/write cycle: wrote {} events, files created: {}",
        original_count, 2
    );

    // Note: read_file is placeholder, so we just verify files were created
    assert!(files.len() >= 2, "Should have created multiple files");
}
