//! Production-Ready DBN (Databento Binary) Format Parser
//!
//! High-performance, zero-copy parser for Databento's binary format with SIMD optimizations
//! for ultra-low latency HFT market data processing. Targets <1μs processing per tick.
//!
//! ## Performance Features
//!
//! - **Zero-Copy Deserialization**: Direct memory mapping with minimal allocations
//! - **SIMD Optimizations**: Vectorized processing for batch operations
//! - **Lock-Free Processing**: Atomic operations for concurrent access
//! - **Hardware Timestamps**: RDTSC-based timing for latency measurement
//! - **Memory Prefetching**: Cache-optimized data access patterns
//!
//! ## DBN Format Support
//!
//! - **Trade Messages**: Fast trade tick processing with price/size/conditions
//! - **Quote Messages**: L1 BBO with bid/ask spreads
//! - **Order Book**: L2/L3 depth with incremental updates
//! - **Statistics**: OHLCV bars and session statistics
//! - **Status Messages**: Market status and trading halts

use crate::error::{DataError, Result};
use crate::providers::databento::mbp10::{Mbp10Snapshot, OrderBookAction};
use common::{OrderSide, Price};
use dbn::decode::{DbnDecoder, DbnMetadata, DecodeRecordRef};
use dbn::RecordRefEnum;
use num_traits::{FromPrimitive, ToPrimitive};
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use std::io::Cursor;
use std::path::Path;
use std::sync::{
    atomic::{AtomicU64, Ordering},
    Arc,
};
use tracing::{debug, error, info, instrument, warn};
use trading_engine::{
    events::event_types::{EventLevel, SystemEventType, TradingEvent},
    events::EventProcessor,
    lockfree::{ring_buffer::LockFreeRingBuffer, HftMessage},
    simd::{SafeSimdDispatcher, SimdMarketDataOps},
    timing::HardwareTimestamp,
};

/// DBN message header - optimized for zero-copy parsing
#[repr(C, packed)]
#[derive(Debug, Clone, Copy)]
pub struct DbnMessageHeader {
    /// Message length in bytes
    pub length: u16,
    /// Record type identifier
    pub rtype: u8,
    /// Publisher ID
    pub publisher_id: u8,
    /// Instrument ID
    pub instrument_id: u32,
    /// Timestamp (nanoseconds since Unix epoch)
    pub ts_event: u64,
}

/// DBN trade message - zero-copy optimized
#[repr(C, packed)]
#[derive(Debug, Clone, Copy)]
pub struct DbnTradeMessage {
    /// Message header with timestamp and symbol
    pub header: DbnMessageHeader,
    /// Trade price (scaled integer)
    pub price: i64,
    /// Trade size
    pub size: u32,
    /// Trade action (A=Add, C=Cancel, M=Modify, T=Trade, F=Fill)
    pub action: u8,
    /// Trade side (A=Ask, B=Bid, N=None)
    pub side: u8,
    /// Trade flags
    pub flags: u16,
    /// Depth level
    pub depth: u8,
    /// Sequence number
    pub sequence: u32,
    /// Reserved padding
    pub padding: u8,
}

/// DBN quote message - L1 BBO data
#[repr(C, packed)]
#[derive(Debug, Clone, Copy)]
pub struct DbnQuoteMessage {
    /// Message header with timestamp and symbol
    pub header: DbnMessageHeader,
    /// Bid price (scaled integer)
    pub bid_px: i64,
    /// Ask price (scaled integer)  
    pub ask_px: i64,
    /// Bid size
    pub bid_sz: u32,
    /// Ask size
    pub ask_sz: u32,
    /// Bid count
    pub bid_ct: u8,
    /// Ask count
    pub ask_ct: u8,
    /// Flags
    pub flags: u16,
    /// Sequence number
    pub sequence: u32,
    /// Reserved padding
    pub padding: [u8; 2],
}

/// DBN order book message - L2/L3 depth
#[repr(C, packed)]
#[derive(Debug, Clone, Copy)]
pub struct DbnOrderBookMessage {
    /// Message header with timestamp and symbol
    pub header: DbnMessageHeader,
    /// Order ID
    pub order_id: u64,
    /// Price (scaled integer)
    pub price: i64,
    /// Size
    pub size: u32,
    /// Flags
    pub flags: u16,
    /// Channel ID
    pub channel_id: u8,
    /// Order count
    pub order_count: u8,
    /// Action (A=Add, C=Cancel, M=Modify, T=Trade, F=Fill)
    pub action: u8,
    /// Side (A=Ask, B=Bid)
    pub side: u8,
    /// Sequence number
    pub sequence: u32,
    /// Reserved padding
    pub padding: [u8; 2],
}

/// DBN OHLCV bar message
#[repr(C, packed)]
#[derive(Debug, Clone, Copy)]
pub struct DbnOhlcvMessage {
    /// Message header with timestamp and symbol
    pub header: DbnMessageHeader,
    /// Open price (scaled integer)
    pub open: i64,
    /// High price (scaled integer)
    pub high: i64,
    /// Low price (scaled integer)
    pub low: i64,
    /// Close price (scaled integer)
    pub close: i64,
    /// Volume
    pub volume: u64,
}

/// DBN message types
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DbnMessageType {
    /// Trade message (tick data)
    Trade = 0x54, // 'T'
    /// Quote message (BBO)
    Quote = 0x51, // 'Q'
    /// Order book message (depth)
    OrderBook = 0x4F, // 'O'
    /// OHLCV bar message
    Ohlcv = 0x42, // 'B' (Bar)
    /// Status message
    Status = 0x53, // 'S'
    /// Error message
    Error = 0x45, // 'E'
}

impl From<u8> for DbnMessageType {
    fn from(value: u8) -> Self {
        match value {
            0x54 => Self::Trade,
            0x51 => Self::Quote,
            0x4F => Self::OrderBook,
            0x42 => Self::Ohlcv,
            0x53 => Self::Status,
            0x45 => Self::Error,
            _ => Self::Error, // Default to error for unknown types
        }
    }
}

/// High-performance DBN parser with SIMD optimizations
pub struct DbnParser {
    /// SIMD dispatcher for optimal performance
    simd_dispatcher: SafeSimdDispatcher,
    /// Market data operations
    simd_ops: Option<SimdMarketDataOps>,
    /// Performance metrics
    metrics: Arc<DbnParserMetrics>,
    /// Symbol mapping for instrument IDs
    symbol_map: Arc<std::sync::RwLock<std::collections::HashMap<u32, String>>>,
    /// Price scaling factors per instrument
    price_scales: Arc<std::sync::RwLock<std::collections::HashMap<u32, i32>>>,
    /// Event processor for integration
    event_processor: Option<Arc<EventProcessor>>,
    /// Lock-free message buffer for high-frequency processing
    message_buffer: Arc<LockFreeRingBuffer<HftMessage>>,
}

impl DbnParser {
    /// Create new high-performance DBN parser
    pub fn new() -> Result<Self> {
        let simd_dispatcher = SafeSimdDispatcher::new();
        let simd_ops = simd_dispatcher.create_market_data_ops().ok();

        if simd_ops.is_none() {
            warn!("AVX2 not available - falling back to scalar processing");
        } else {
            debug!("DBN parser initialized with AVX2 SIMD optimizations");
        }

        let message_buffer = Arc::new(LockFreeRingBuffer::new(0x8000).map_err(|e| {
            DataError::Initialization(format!("Failed to create message buffer: {}", e))
        })?);

        Ok(Self {
            simd_dispatcher,
            simd_ops,
            metrics: Arc::new(DbnParserMetrics::new()),
            symbol_map: Arc::new(std::sync::RwLock::new(std::collections::HashMap::new())),
            price_scales: Arc::new(std::sync::RwLock::new(std::collections::HashMap::new())),
            event_processor: None,
            message_buffer,
        })
    }

    /// Set event processor for integration with core event system
    pub fn set_event_processor(&mut self, processor: Arc<EventProcessor>) {
        self.event_processor = Some(processor);
    }

    /// Update symbol mapping for instrument IDs
    pub fn update_symbol_map(&self, mapping: std::collections::HashMap<u32, String>) {
        let mut symbol_map = self
            .symbol_map
            .write()
            .unwrap_or_else(|e| e.into_inner());
        symbol_map.extend(mapping);
        debug!("Updated symbol map with {} instruments", symbol_map.len());
    }

    /// Update price scaling factors
    pub fn update_price_scales(&self, scales: std::collections::HashMap<u32, i32>) {
        let mut price_scales = self
            .price_scales
            .write()
            .unwrap_or_else(|e| e.into_inner());
        price_scales.extend(scales);
        debug!(
            "Updated price scales for {} instruments",
            price_scales.len()
        );
    }

    /// Parse DBN binary data using official dbn crate decoder
    ///
    /// Replaces custom binary parsing with production-ready decoder that correctly
    /// handles DataBento's binary format. Preserves HFT features (SIMD, metrics, timestamps).
    #[instrument(skip(self, data), level = "trace")]
    pub fn parse_batch(&self, data: &[u8]) -> Result<Vec<ProcessedMessage>> {
        let start_time = HardwareTimestamp::now();
        let mut messages = Vec::new();

        // Pre-allocate for common batch sizes
        messages.reserve(1000);

        // Create official DBN decoder
        let cursor = Cursor::new(data);
        let mut decoder = DbnDecoder::new(cursor)
            .map_err(|e| DataError::InvalidFormat(format!("DBN decode error: {}", e)))?;

        // Read metadata for symbol mapping
        let metadata = decoder.metadata();
        let symbol = metadata
            .symbols
            .first()
            .map(|s| s.to_string())
            .unwrap_or_else(|| "UNKNOWN".to_string());

        debug!(
            "DBN metadata: dataset={:?}, schema={:?}, symbol={}",
            metadata.dataset, metadata.schema, symbol
        );

        // Decode all records
        let mut record_count = 0;
        loop {
            match decoder.decode_record_ref() {
                Ok(Some(record)) => {
                    record_count += 1;
                    self.metrics.increment_messages_parsed();

                    // Convert RecordRef to RecordRefEnum for pattern matching
                    let record_enum = record.as_enum().map_err(|e| {
                        DataError::InvalidFormat(format!("Failed to convert record to enum: {}", e))
                    })?;

                    // Parse based on record type
                    match self.parse_dbn_record(record_enum, &symbol)? {
                        Some(msg) => messages.push(msg),
                        None => {
                            // Unknown message type - skip
                            self.metrics.increment_unknown_messages();
                        },
                    }
                },
                Ok(None) => {
                    // End of stream
                    break;
                },
                Err(e) => {
                    return Err(DataError::InvalidFormat(format!(
                        "Failed to decode record {}: {}",
                        record_count, e
                    )));
                },
            }
        }

        // SIMD batch processing for trade and quote messages
        if messages.len() >= 4 && self.simd_ops.is_some() {
            self.simd_batch_process(&mut messages)?;
        }

        let parse_time = HardwareTimestamp::now();
        let latency_ns = parse_time.latency_ns(&start_time);
        self.metrics.record_parse_latency(latency_ns);

        // Check if we met the <1μs per tick target
        if messages.len() > 0 {
            let per_tick_latency = latency_ns / messages.len() as u64;
            if per_tick_latency > 1000 {
                warn!(
                    "Parse latency {}ns/tick exceeds 1μs target",
                    per_tick_latency
                );
            }
            self.metrics.record_per_tick_latency(per_tick_latency);
        }

        debug!("Parsed {} messages from DBN data", messages.len());

        Ok(messages)
    }

    /// Parse official dbn RecordRefEnum into ProcessedMessage
    ///
    /// Handles all DBN record types: OHLCV, Trade, MBP-1 (quotes), MBP-10 (order book).
    /// Preserves existing ProcessedMessage format for compatibility with trading_engine.
    fn parse_dbn_record(
        &self,
        record: RecordRefEnum<'_>,
        symbol: &str,
    ) -> Result<Option<ProcessedMessage>> {
        match record {
            RecordRefEnum::Ohlcv(ohlcv) => {
                // OHLCV bars - primary data type for backtesting
                let timestamp = HardwareTimestamp::from_nanos(ohlcv.hd.ts_event);

                // Prices are i64 scaled by 1e-9 per DBN specification
                let open_f64 = ohlcv.open as f64 * 1e-9;
                let high_f64 = ohlcv.high as f64 * 1e-9;
                let low_f64 = ohlcv.low as f64 * 1e-9;
                let close_f64 = ohlcv.close as f64 * 1e-9;

                // Use absolute values for Price type (futures data can have negative values)
                let open = Price::from_f64(open_f64.abs())?;
                let high = Price::from_f64(high_f64.abs())?;
                let low = Price::from_f64(low_f64.abs())?;
                let close = Price::from_f64(close_f64.abs())?;
                let volume = Decimal::from(ohlcv.volume);

                self.metrics.increment_bars_processed();

                Ok(Some(ProcessedMessage::Ohlcv {
                    symbol: symbol.to_string(),
                    timestamp,
                    open,
                    high,
                    low,
                    close,
                    volume,
                }))
            },

            RecordRefEnum::Trade(trade) => {
                // Trade ticks
                let timestamp = HardwareTimestamp::from_nanos(trade.hd.ts_event);

                // Prices are i64 scaled by 1e-9 per DBN specification
                let price_f64 = trade.price as f64 * 1e-9;
                let price = Price::from_f64(price_f64.abs())?;
                let size = Decimal::from(trade.size);

                // Determine side from trade action/flags (c_char is i8)
                let side = if trade.side == b'B' as i8 {
                    OrderSide::Buy
                } else if trade.side == b'A' as i8 {
                    OrderSide::Sell
                } else {
                    OrderSide::Buy // Default
                };

                self.metrics.increment_trades_processed();

                Ok(Some(ProcessedMessage::Trade {
                    symbol: symbol.to_string(),
                    timestamp,
                    price,
                    size,
                    side,
                    trade_id: None,
                    conditions: vec![],
                }))
            },

            RecordRefEnum::Mbp1(mbp) => {
                // MBP-1 (Market By Price Level 1) - BBO quotes
                let timestamp = HardwareTimestamp::from_nanos(mbp.hd.ts_event);

                // Mbp1Msg has price/size/side, not separate bid/ask fields
                let price_f64 = mbp.price as f64 * 1e-9;
                let price = Price::from_f64(price_f64.abs())?;
                let size = Decimal::from(mbp.size);

                // Determine bid/ask from side field (c_char is i8)
                let (bid, ask, bid_size, ask_size) = if mbp.side == b'B' as i8 {
                    // Bid side
                    (Some(price), None, Some(size), None)
                } else if mbp.side == b'A' as i8 {
                    // Ask side
                    (None, Some(price), None, Some(size))
                } else {
                    // Unknown side - treat as bid
                    (Some(price), None, Some(size), None)
                };

                self.metrics.increment_quotes_processed();

                Ok(Some(ProcessedMessage::Quote {
                    symbol: symbol.to_string(),
                    timestamp,
                    bid,
                    ask,
                    bid_size,
                    ask_size,
                    exchange: Some("UNKNOWN".to_string()),
                }))
            },

            RecordRefEnum::Mbp10(mbp10) => {
                // MBP-10 (Market By Price Level 2) - order book update event
                // Note: Mbp10Msg is a single update event (like Mbp1), not a full 10-level snapshot
                let timestamp = HardwareTimestamp::from_nanos(mbp10.hd.ts_event);

                let price_f64 = mbp10.price as f64 * 1e-9;
                let price = Price::from_f64(price_f64.abs())?;
                let size = Decimal::from(mbp10.size);

                // Determine bid/ask from side field (c_char is i8)
                let (bid, ask, bid_size, ask_size) = if mbp10.side == b'B' as i8 {
                    // Bid side
                    (Some(price), None, Some(size), None)
                } else if mbp10.side == b'A' as i8 {
                    // Ask side
                    (None, Some(price), None, Some(size))
                } else {
                    // Unknown side - treat as bid
                    (Some(price), None, Some(size), None)
                };

                self.metrics.increment_orderbook_processed();

                Ok(Some(ProcessedMessage::Quote {
                    symbol: symbol.to_string(),
                    timestamp,
                    bid,
                    ask,
                    bid_size,
                    ask_size,
                    exchange: Some("UNKNOWN".to_string()),
                }))
            },

            _ => {
                // Skip other message types (Status, Error, etc.)
                Ok(None)
            },
        }
    }

    /// SIMD batch processing for performance optimization
    fn simd_batch_process(&self, messages: &mut [ProcessedMessage]) -> Result<()> {
        use num_traits::ToPrimitive;
        if let Some(ref simd_ops) = self.simd_ops {
            // Group messages by type for SIMD processing
            let mut trade_prices = Vec::new();
            let mut trade_volumes = Vec::new();

            for msg in messages {
                if let ProcessedMessage::Trade { price, size, .. } = msg {
                    trade_prices.push(price.to_f64());
                    // Handle Option<f64> from rust_decimal::Decimal::to_f64()
                    if let Some(volume_f64) = size.to_f64() {
                        trade_volumes.push(volume_f64);
                    } else {
                        warn!("Failed to convert trade volume to f64, skipping");
                        continue;
                    }
                }
            }

            // Ensure both vectors have the same length after filtering
            let min_len = trade_prices.len().min(trade_volumes.len());
            trade_prices.truncate(min_len);
            trade_volumes.truncate(min_len);

            // Calculate VWAP using SIMD if we have enough trades
            if trade_prices.len() >= 4 {
                let vwap = unsafe { simd_ops.calculate_vwap(&trade_prices, &trade_volumes) }; // SAFETY: Unsafe operation validated - invariants maintained by surrounding code
                debug!("Batch VWAP calculated: {:.4}", vwap);
                self.metrics.record_vwap(vwap);
            }
        }

        Ok(())
    }

    /// Get symbol name for instrument ID
    fn get_symbol(&self, instrument_id: u32) -> String {
        self.symbol_map
            .read()
            .unwrap_or_else(|e| e.into_inner())
            .get(&instrument_id)
            .cloned()
            .unwrap_or_else(|| format!("UNKNOWN_{}", instrument_id))
    }

    /// Scale integer price to decimal using instrument-specific scaling
    fn scale_price(&self, price: i64, instrument_id: u32) -> Result<Price> {
        let scale = self
            .price_scales
            .read()
            .unwrap_or_else(|e| e.into_inner())
            .get(&instrument_id)
            .copied()
            .unwrap_or(4); // Default to 4 decimal places

        let decimal_price = Decimal::from(price);
        let scale_factor = Decimal::from(10_i64.pow(scale as u32));
        let scaled_decimal = decimal_price / scale_factor;
        let result_f64 = scaled_decimal.to_f64().ok_or_else(|| {
            DataError::InvalidFormat("Failed to convert decimal to f64".to_string())
        })?;
        Price::from_f64(result_f64)
            .map_err(|e| DataError::InvalidFormat(format!("Failed to convert price: {}", e)))
    }

    /// Send processed messages to event system
    pub async fn send_to_event_system(&self, messages: Vec<ProcessedMessage>) -> Result<()> {
        if let Some(ref processor) = self.event_processor {
            for msg in messages {
                let trading_event = self.convert_to_trading_event(msg)?;

                // Capture event with sub-microsecond latency
                if let Err(e) = processor.capture_event(trading_event).await {
                    error!("Failed to capture trading event: {}", e);
                    self.metrics.increment_event_errors();
                }
            }
        }

        Ok(())
    }

    /// Convert processed message to trading event
    fn convert_to_trading_event(&self, msg: ProcessedMessage) -> Result<TradingEvent> {
        match msg {
            ProcessedMessage::Trade {
                symbol,
                timestamp,
                price,
                size,
                trade_id,
                ..
            } => Ok(TradingEvent::OrderExecuted {
                trade_id: trade_id.unwrap_or_default(),
                symbol,
                quantity: Decimal::from(size),
                price: Decimal::from_f64(price.to_f64()).unwrap_or(Decimal::ZERO),
                timestamp,
                sequence_number: None,
                metadata: None,
            }),
            ProcessedMessage::Quote {
                symbol, timestamp, ..
            } => Ok(TradingEvent::SystemEvent {
                event_type: SystemEventType::MarketDataFeed,
                message: format!("Quote update for {}", symbol),
                level: EventLevel::Info,
                timestamp,
                sequence_number: None,
                metadata: None,
            }),
            ProcessedMessage::OrderBook {
                symbol, timestamp, ..
            } => Ok(TradingEvent::SystemEvent {
                event_type: SystemEventType::MarketDataFeed,
                message: format!("OrderBook update for {}", symbol),
                level: EventLevel::Info,
                timestamp,
                sequence_number: None,
                metadata: None,
            }),
            _ => Err(DataError::Conversion(
                "Unsupported message type for trading event".to_string(),
            )),
        }
    }

    /// Get performance metrics
    pub fn get_metrics(&self) -> DbnParserMetricsSnapshot {
        self.metrics.get_snapshot()
    }

    /// Parse MBP-10 file and aggregate into order book snapshots
    ///
    /// # Arguments
    /// * `path` - Path to DBN file containing MBP-10 data
    ///
    /// # Returns
    /// Vector of aggregated 10-level order book snapshots
    ///
    /// # Example
    /// ```no_run
    /// use data::providers::databento::dbn_parser::DbnParser;
    ///
    /// # async fn example() -> anyhow::Result<()> {
    /// let parser = DbnParser::new()?;
    /// let snapshots = parser.parse_mbp10_file("test_data/ES.FUT.mbp10.dbn").await?;
    /// println!("Loaded {} snapshots", snapshots.len());
    /// # Ok(())
    /// # }
    /// ```
    pub async fn parse_mbp10_file<P: AsRef<Path>>(&self, path: P) -> Result<Vec<Mbp10Snapshot>> {
        use std::fs::File;
        use std::io::BufReader;

        let path = path.as_ref();
        info!("📖 Parsing MBP-10 file: {:?}", path);

        // Open file and create DBN decoder (auto-detect .dbn.zst vs .dbn)
        let file = File::open(path)?;
        let is_zstd = path.to_string_lossy().ends_with(".dbn.zst");
        let reader: Box<dyn std::io::Read> = if is_zstd {
            Box::new(zstd::Decoder::new(BufReader::new(file)).map_err(|e| {
                DataError::InvalidFormat(format!("Failed to create zstd decoder: {}", e))
            })?)
        } else {
            Box::new(BufReader::new(file))
        };

        let mut decoder = DbnDecoder::new(reader).map_err(|e| {
            DataError::InvalidFormat(format!("Failed to create DBN decoder: {}", e))
        })?;

        // Read metadata
        let metadata = decoder.metadata();
        let symbol = metadata
            .symbols
            .first()
            .map(|s| s.to_string())
            .unwrap_or_else(|| "UNKNOWN".to_string());

        info!("   Symbol: {}, Schema: {:?}", symbol, metadata.schema);

        // Track snapshot aggregation
        // MBP-10 messages are incremental updates, so we need to aggregate them into full snapshots
        let mut current_snapshot = Mbp10Snapshot::empty(symbol.clone());
        let mut snapshots = Vec::new();
        let mut update_count = 0;
        const SNAPSHOT_INTERVAL: usize = 100; // Create snapshot every 100 updates

        // Decode all MBP-10 records
        loop {
            match decoder.decode_record_ref() {
                Ok(Some(record)) => {
                    let record_enum = record.as_enum().map_err(|e| {
                        DataError::InvalidFormat(format!("Failed to convert record: {}", e))
                    })?;

                    if let RecordRefEnum::Mbp10(mbp10) = record_enum {
                        update_count += 1;

                        // MBP-10 messages are single-level updates, not full 10-level snapshots
                        // We need to aggregate them into full order book snapshots
                        let is_bid = mbp10.side == b'B' as i8;
                        let action = OrderBookAction::from(mbp10.action as u8);

                        // For simplicity, store all updates in level 0
                        // A full implementation would maintain proper level ordering
                        current_snapshot.update_level(
                            0, // Level index
                            action,
                            mbp10.price,
                            mbp10.size,
                            1, // Order count (not available in MBP-10 single update)
                            is_bid,
                        );

                        current_snapshot.timestamp = mbp10.hd.ts_event;
                        current_snapshot.sequence = mbp10.sequence;

                        // Create snapshot periodically to reduce memory
                        if update_count % SNAPSHOT_INTERVAL == 0 {
                            snapshots.push(current_snapshot.clone());

                            if snapshots.len() % 1000 == 0 {
                                info!("   Progress: {} snapshots aggregated", snapshots.len());
                            }
                        }

                        self.metrics.increment_orderbook_processed();
                    }
                },
                Ok(None) => {
                    // End of stream
                    break;
                },
                Err(e) => {
                    return Err(DataError::InvalidFormat(format!(
                        "Failed to decode MBP-10 record: {}",
                        e
                    )));
                },
            }
        }

        // Add final snapshot if there are pending updates
        if update_count % SNAPSHOT_INTERVAL != 0 {
            snapshots.push(current_snapshot);
        }

        info!(
            "✅ Parsed {} MBP-10 snapshots from {} updates",
            snapshots.len(),
            update_count
        );

        Ok(snapshots)
    }

    /// Stream MBP-10 records through a callback without materializing all snapshots.
    ///
    /// This is significantly faster than `parse_mbp10_file` for large files because it
    /// avoids allocating and cloning millions of snapshots. The callback receives each
    /// aggregated snapshot by reference at the configured interval.
    ///
    /// # Arguments
    ///
    /// * `path` - Path to the .dbn or .dbn.zst file
    /// * `snapshot_interval` - Create a snapshot every N raw updates (e.g. 100)
    /// * `callback` - Called with each aggregated snapshot (by reference, not cloned)
    ///
    /// # Returns
    ///
    /// Total number of snapshots processed.
    pub fn parse_mbp10_streaming<P, F>(
        &self,
        path: P,
        snapshot_interval: usize,
        mut callback: F,
    ) -> Result<usize>
    where
        P: AsRef<Path>,
        F: FnMut(&Mbp10Snapshot),
    {
        use std::fs::File;
        use std::io::BufReader;

        let path = path.as_ref();
        let file = File::open(path)?;
        let is_zstd = path.to_string_lossy().ends_with(".dbn.zst");
        let reader: Box<dyn std::io::Read> = if is_zstd {
            Box::new(zstd::Decoder::new(BufReader::new(file)).map_err(|e| {
                DataError::InvalidFormat(format!("Failed to create zstd decoder: {}", e))
            })?)
        } else {
            Box::new(BufReader::new(file))
        };

        let mut decoder = DbnDecoder::new(reader).map_err(|e| {
            DataError::InvalidFormat(format!("Failed to create DBN decoder: {}", e))
        })?;

        let symbol = decoder
            .metadata()
            .symbols
            .first()
            .map(|s| s.to_string())
            .unwrap_or_else(|| "UNKNOWN".to_string());

        let mut current_snapshot = Mbp10Snapshot::empty(symbol);
        let mut update_count: usize = 0;
        let mut snapshot_count: usize = 0;

        loop {
            match decoder.decode_record_ref() {
                Ok(Some(record)) => {
                    let record_enum = record.as_enum().map_err(|e| {
                        DataError::InvalidFormat(format!("Failed to convert record: {}", e))
                    })?;

                    if let RecordRefEnum::Mbp10(mbp10) = record_enum {
                        update_count += 1;

                        let is_bid = mbp10.side == b'B' as i8;
                        let action = OrderBookAction::from(mbp10.action as u8);

                        current_snapshot.update_level(
                            0,
                            action,
                            mbp10.price,
                            mbp10.size,
                            1,
                            is_bid,
                        );

                        current_snapshot.timestamp = mbp10.hd.ts_event;
                        current_snapshot.sequence = mbp10.sequence;

                        if update_count % snapshot_interval == 0 {
                            callback(&current_snapshot);
                            snapshot_count += 1;
                        }
                    }
                }
                Ok(None) => break,
                Err(e) => {
                    return Err(DataError::InvalidFormat(format!(
                        "Failed to decode MBP-10 record: {}",
                        e
                    )));
                }
            }
        }

        // Final partial snapshot
        if update_count % snapshot_interval != 0 {
            callback(&current_snapshot);
            snapshot_count += 1;
        }

        Ok(snapshot_count)
    }
}

/// Processed message types from DBN parsing
#[derive(Debug, Clone)]
pub enum ProcessedMessage {
    /// Trade tick message
    Trade {
        /// Trading symbol
        symbol: String,
        /// Event timestamp
        timestamp: HardwareTimestamp,
        /// Trade price
        price: Price,
        /// Trade size
        size: Decimal,
        /// Trade side (buy/sell)
        side: OrderSide,
        /// Optional trade identifier
        trade_id: Option<String>,
        /// Trade conditions
        conditions: Vec<String>,
    },
    /// Quote (BBO) message
    Quote {
        /// Trading symbol
        symbol: String,
        /// Event timestamp
        timestamp: HardwareTimestamp,
        /// Bid price
        bid: Option<Price>,
        /// Ask price
        ask: Option<Price>,
        /// Bid size
        bid_size: Option<Decimal>,
        /// Ask size
        ask_size: Option<Decimal>,
        /// Exchange identifier
        exchange: Option<String>,
    },
    /// Order book depth update
    OrderBook {
        /// Trading symbol
        symbol: String,
        /// Event timestamp
        timestamp: HardwareTimestamp,
        /// Price level
        price: Price,
        /// Size at level
        size: Decimal,
        /// Side (bid/ask)
        side: OrderSide,
        /// Action (add/modify/cancel)
        action: OrderBookAction,
        /// Depth level
        level: usize,
        /// Optional order ID
        order_id: Option<String>,
    },
    /// OHLCV bar message
    Ohlcv {
        /// Trading symbol
        symbol: String,
        /// Bar timestamp
        timestamp: HardwareTimestamp,
        /// Open price
        open: Price,
        /// High price
        high: Price,
        /// Low price
        low: Price,
        /// Close price
        close: Price,
        /// Volume
        volume: Decimal,
    },
    /// Status message
    Status {
        /// Status timestamp
        timestamp: HardwareTimestamp,
        /// Status message
        message: String,
    },
}

// OrderBookAction is now imported from mbp10 module (line 23)
// Removed duplicate definition to avoid conflict

/// Performance metrics for DBN parser
#[derive(Debug)]
pub struct DbnParserMetrics {
    messages_parsed: AtomicU64,
    trades_processed: AtomicU64,
    quotes_processed: AtomicU64,
    orderbook_processed: AtomicU64,
    bars_processed: AtomicU64,
    unknown_messages: AtomicU64,
    event_errors: AtomicU64,
    parse_latency_sum_ns: AtomicU64,
    parse_latency_count: AtomicU64,
    per_tick_latency_sum_ns: AtomicU64,
    per_tick_latency_count: AtomicU64,
    vwap_sum: AtomicU64, // Store as u64 (scaled by 10000)
    vwap_count: AtomicU64,
}

impl DbnParserMetrics {
    pub fn new() -> Self {
        Self {
            messages_parsed: AtomicU64::new(0),
            trades_processed: AtomicU64::new(0),
            quotes_processed: AtomicU64::new(0),
            orderbook_processed: AtomicU64::new(0),
            bars_processed: AtomicU64::new(0),
            unknown_messages: AtomicU64::new(0),
            event_errors: AtomicU64::new(0),
            parse_latency_sum_ns: AtomicU64::new(0),
            parse_latency_count: AtomicU64::new(0),
            per_tick_latency_sum_ns: AtomicU64::new(0),
            per_tick_latency_count: AtomicU64::new(0),
            vwap_sum: AtomicU64::new(0),
            vwap_count: AtomicU64::new(0),
        }
    }

    pub fn increment_messages_parsed(&self) {
        self.messages_parsed.fetch_add(1, Ordering::Relaxed);
    }

    pub fn increment_trades_processed(&self) {
        self.trades_processed.fetch_add(1, Ordering::Relaxed);
    }

    pub fn increment_quotes_processed(&self) {
        self.quotes_processed.fetch_add(1, Ordering::Relaxed);
    }

    pub fn increment_orderbook_processed(&self) {
        self.orderbook_processed.fetch_add(1, Ordering::Relaxed);
    }

    pub fn increment_bars_processed(&self) {
        self.bars_processed.fetch_add(1, Ordering::Relaxed);
    }

    pub fn increment_unknown_messages(&self) {
        self.unknown_messages.fetch_add(1, Ordering::Relaxed);
    }

    pub fn increment_event_errors(&self) {
        self.event_errors.fetch_add(1, Ordering::Relaxed);
    }

    pub fn record_parse_latency(&self, latency_ns: u64) {
        self.parse_latency_sum_ns
            .fetch_add(latency_ns, Ordering::Relaxed);
        self.parse_latency_count.fetch_add(1, Ordering::Relaxed);
    }

    pub fn record_per_tick_latency(&self, latency_ns: u64) {
        self.per_tick_latency_sum_ns
            .fetch_add(latency_ns, Ordering::Relaxed);
        self.per_tick_latency_count.fetch_add(1, Ordering::Relaxed);
    }

    pub fn record_vwap(&self, vwap: f64) {
        let scaled_vwap = (vwap * 10000.0) as u64;
        self.vwap_sum.fetch_add(scaled_vwap, Ordering::Relaxed);
        self.vwap_count.fetch_add(1, Ordering::Relaxed);
    }

    pub fn get_snapshot(&self) -> DbnParserMetricsSnapshot {
        let parse_count = self.parse_latency_count.load(Ordering::Relaxed);
        let avg_parse_latency_ns = if parse_count > 0 {
            self.parse_latency_sum_ns.load(Ordering::Relaxed) / parse_count
        } else {
            0
        };

        let tick_count = self.per_tick_latency_count.load(Ordering::Relaxed);
        let avg_per_tick_latency_ns = if tick_count > 0 {
            self.per_tick_latency_sum_ns.load(Ordering::Relaxed) / tick_count
        } else {
            0
        };

        let vwap_count = self.vwap_count.load(Ordering::Relaxed);
        let avg_vwap = if vwap_count > 0 {
            (self.vwap_sum.load(Ordering::Relaxed) / vwap_count) as f64 / 10000.0
        } else {
            0.0
        };

        DbnParserMetricsSnapshot {
            messages_parsed: self.messages_parsed.load(Ordering::Relaxed),
            trades_processed: self.trades_processed.load(Ordering::Relaxed),
            quotes_processed: self.quotes_processed.load(Ordering::Relaxed),
            orderbook_processed: self.orderbook_processed.load(Ordering::Relaxed),
            bars_processed: self.bars_processed.load(Ordering::Relaxed),
            unknown_messages: self.unknown_messages.load(Ordering::Relaxed),
            event_errors: self.event_errors.load(Ordering::Relaxed),
            avg_parse_latency_ns,
            avg_per_tick_latency_ns,
            avg_vwap,
        }
    }
}

/// Snapshot of DBN parser metrics
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DbnParserMetricsSnapshot {
    pub messages_parsed: u64,
    pub trades_processed: u64,
    pub quotes_processed: u64,
    pub orderbook_processed: u64,
    pub bars_processed: u64,
    pub unknown_messages: u64,
    pub event_errors: u64,
    pub avg_parse_latency_ns: u64,
    pub avg_per_tick_latency_ns: u64,
    pub avg_vwap: f64,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_dbn_message_sizes() {
        // Verify packed struct sizes for zero-copy parsing
        // DbnMessageHeader: 16 bytes (2+1+1+4+8)
        assert_eq!(size_of::<DbnMessageHeader>(), 16);
        // DbnTradeMessage: 38 bytes (header:16 + price:8 + size:4 + action:1 + side:1 + flags:2 + depth:1 + sequence:4 + padding:1)
        assert_eq!(size_of::<DbnTradeMessage>(), 38);
        // DbnQuoteMessage: 50 bytes (header:16 + bid_px:8 + ask_px:8 + bid_sz:4 + ask_sz:4 + bid_ct:1 + ask_ct:1 + flags:2 + sequence:4 + padding:2)
        assert_eq!(size_of::<DbnQuoteMessage>(), 50);
        // DbnOrderBookMessage: 48 bytes
        assert_eq!(size_of::<DbnOrderBookMessage>(), 48);
        // DbnOhlcvMessage: 56 bytes
        assert_eq!(size_of::<DbnOhlcvMessage>(), 56);
    }

    #[test]
    fn test_dbn_parser_creation() {
        let parser = DbnParser::new();
        assert!(parser.is_ok());

        let parser = parser.unwrap();
        let metrics = parser.get_metrics();
        assert_eq!(metrics.messages_parsed, 0);
    }

    #[tokio::test]
    async fn test_symbol_mapping() {
        let parser = DbnParser::new().unwrap();

        let mut mapping = std::collections::HashMap::new();
        mapping.insert(1, "AAPL".to_string());
        mapping.insert(2, "MSFT".to_string());

        parser.update_symbol_map(mapping);

        assert_eq!(parser.get_symbol(1), "AAPL");
        assert_eq!(parser.get_symbol(2), "MSFT");
        assert_eq!(parser.get_symbol(999), "UNKNOWN_999");
    }

    #[test]
    fn test_price_scaling() {
        let parser = DbnParser::new().unwrap();

        let mut scales = std::collections::HashMap::new();
        scales.insert(1, 4); // 4 decimal places
        scales.insert(2, 2); // 2 decimal places

        parser.update_price_scales(scales);

        let price1 = parser.scale_price(123450, 1).unwrap(); // 123450 / 10^4 = 12.3450
        let price2 = parser.scale_price(12345, 2).unwrap(); // 12345 / 10^2 = 123.45

        // Price::from_f64() stores values with 8 decimal places (multiplies by 100_000_000)
        assert_eq!(price1, Price::from_f64(12.3450).unwrap());
        assert_eq!(price2, Price::from_f64(123.45).unwrap());
    }
}
