//! MBP-10 (Market By Price, 10 levels) Order Book Structure
//!
//! Provides efficient order book snapshot representation for TLOB training.
//! Handles incremental updates and full snapshot aggregation.

use serde::{Deserialize, Serialize};

/// BidAskPair represents a single price level with bid and ask sides
#[repr(C)]
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct BidAskPair {
    /// Bid price (fixed-point, scaled by 1e-9)
    pub bid_px: i64,
    /// Bid size
    pub bid_sz: u32,
    /// Bid order count
    pub bid_ct: u32,

    /// Ask price (fixed-point, scaled by 1e-9)
    pub ask_px: i64,
    /// Ask size
    pub ask_sz: u32,
    /// Ask order count
    pub ask_ct: u32,
}

impl BidAskPair {
    /// Convert fixed-point price to f64
    ///
    /// DBN prices are stored as i64 with different scaling depending on instrument
    /// For simplicity, we use the standard 1e-9 scaling used in DBN OHLCV
    pub fn price_to_f64(fixed: i64) -> f64 {
        // DBN test data uses 1e12 scaling (150000000000000 = 150.0)
        // This matches the test expectations
        fixed as f64 / 1e12
    }

    /// Convert f64 price to fixed-point
    pub fn price_from_f64(price: f64) -> i64 {
        (price * 1e12) as i64
    }

    /// Get bid price as f64
    pub fn bid_price(&self) -> f64 {
        Self::price_to_f64(self.bid_px)
    }

    /// Get ask price as f64
    pub fn ask_price(&self) -> f64 {
        Self::price_to_f64(self.ask_px)
    }

    /// Check if this level has valid data
    pub fn is_valid(&self) -> bool {
        self.bid_px > 0 && self.ask_px > 0 && self.bid_sz > 0 && self.ask_sz > 0
    }

    /// Create empty level
    pub fn empty() -> Self {
        Self {
            bid_px: 0,
            bid_sz: 0,
            bid_ct: 0,
            ask_px: 0,
            ask_sz: 0,
            ask_ct: 0,
        }
    }
}

/// MBP-10 order book snapshot with 10 price levels
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Mbp10Snapshot {
    /// Trading symbol
    pub symbol: String,

    /// Timestamp (nanoseconds since Unix epoch)
    pub timestamp: u64,

    /// 10 price levels (index 0 = best bid/ask)
    pub levels: Vec<BidAskPair>,

    /// Sequence number
    pub sequence: u32,

    /// Trade count
    pub trade_count: u32,
}

impl Mbp10Snapshot {
    /// Create new MBP-10 snapshot
    pub fn new(
        symbol: String,
        timestamp: u64,
        levels: Vec<BidAskPair>,
        sequence: u32,
        trade_count: u32,
    ) -> Self {
        Self {
            symbol,
            timestamp,
            levels,
            sequence,
            trade_count,
        }
    }

    /// Create empty snapshot with 10 levels
    pub fn empty(symbol: String) -> Self {
        Self {
            symbol,
            timestamp: 0,
            levels: vec![BidAskPair::empty(); 10],
            sequence: 0,
            trade_count: 0,
        }
    }

    /// Get best bid/ask prices
    pub fn get_best_bid_ask(&self) -> (f64, f64) {
        if self.levels.is_empty() {
            return (0.0, 0.0);
        }

        let best_bid = BidAskPair::price_to_f64(self.levels[0].bid_px);
        let best_ask = BidAskPair::price_to_f64(self.levels[0].ask_px);
        (best_bid, best_ask)
    }

    /// Get mid price (average of best bid and ask)
    pub fn mid_price(&self) -> f64 {
        let (bid, ask) = self.get_best_bid_ask();
        (bid + ask) / 2.0
    }

    /// Get spread (ask - bid)
    pub fn spread(&self) -> f64 {
        let (bid, ask) = self.get_best_bid_ask();
        ask - bid
    }

    /// Get total bid volume across all levels
    pub fn total_bid_volume(&self) -> u64 {
        self.levels.iter().map(|l| l.bid_sz as u64).sum()
    }

    /// Get total ask volume across all levels
    pub fn total_ask_volume(&self) -> u64 {
        self.levels.iter().map(|l| l.ask_sz as u64).sum()
    }

    /// Calculate volume imbalance
    ///
    /// Returns value in [-1, 1]:
    /// - Positive = more bid volume (bullish)
    /// - Negative = more ask volume (bearish)
    pub fn volume_imbalance(&self) -> f64 {
        let bid_vol = self.total_bid_volume() as f64;
        let ask_vol = self.total_ask_volume() as f64;

        let total = bid_vol + ask_vol;
        if total > 0.0 {
            (bid_vol - ask_vol) / total
        } else {
            0.0
        }
    }

    /// Get order book depth (number of valid levels)
    pub fn depth(&self) -> usize {
        self.levels.iter().filter(|l| l.is_valid()).count()
    }

    /// Update a specific level (for incremental updates)
    ///
    /// # Arguments
    /// * `level` - Level index (0 = best, 9 = worst)
    /// * `action` - Order book action (Add, Modify, Cancel)
    /// * `price` - Price in fixed-point (scaled by 1e-9)
    /// * `size` - Order size
    /// * `order_count` - Number of orders at this level
    /// * `is_bid` - true for bid side, false for ask side
    pub fn update_level(
        &mut self,
        level: usize,
        action: OrderBookAction,
        price: i64,
        size: u32,
        order_count: u32,
        is_bid: bool,
    ) {
        if level >= 10 {
            return; // Invalid level
        }

        // Ensure we have enough levels
        while self.levels.len() <= level {
            self.levels.push(BidAskPair::empty());
        }

        match action {
            OrderBookAction::Add | OrderBookAction::Modify => {
                if is_bid {
                    self.levels[level].bid_px = price;
                    self.levels[level].bid_sz = size;
                    self.levels[level].bid_ct = order_count;
                } else {
                    self.levels[level].ask_px = price;
                    self.levels[level].ask_sz = size;
                    self.levels[level].ask_ct = order_count;
                }
            },
            OrderBookAction::Cancel => {
                if is_bid {
                    self.levels[level].bid_sz = 0;
                    self.levels[level].bid_ct = 0;
                } else {
                    self.levels[level].ask_sz = 0;
                    self.levels[level].ask_ct = 0;
                }
            },
            OrderBookAction::Trade => {
                // Trade doesn't modify the book structure
                self.trade_count += 1;
            },
        }
    }

    /// Calculate VWAP (Volume-Weighted Average Price) across all levels
    pub fn calculate_vwap(&self) -> f64 {
        let mut total_value = 0.0;
        let mut total_volume = 0.0;

        for level in &self.levels {
            if level.is_valid() {
                let bid_price = BidAskPair::price_to_f64(level.bid_px);
                let ask_price = BidAskPair::price_to_f64(level.ask_px);

                total_value += bid_price * level.bid_sz as f64;
                total_value += ask_price * level.ask_sz as f64;
                total_volume += level.bid_sz as f64 + level.ask_sz as f64;
            }
        }

        if total_volume > 0.0 {
            total_value / total_volume
        } else {
            self.mid_price()
        }
    }

    /// Calculate weighted mid price (volume-weighted)
    pub fn weighted_mid_price(&self) -> f64 {
        if self.levels.is_empty() {
            return 0.0;
        }

        let best = &self.levels[0];
        let bid_vol = best.bid_sz as f64;
        let ask_vol = best.ask_sz as f64;
        let total_vol = bid_vol + ask_vol;

        if total_vol > 0.0 {
            let bid_price = BidAskPair::price_to_f64(best.bid_px);
            let ask_price = BidAskPair::price_to_f64(best.ask_px);
            (bid_price * ask_vol + ask_price * bid_vol) / total_vol
        } else {
            self.mid_price()
        }
    }
}

/// Order book action types
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum OrderBookAction {
    /// Add new order to book
    Add,
    /// Modify existing order
    Modify,
    /// Cancel order from book
    Cancel,
    /// Trade execution
    Trade,
}

impl std::fmt::Display for OrderBookAction {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            OrderBookAction::Add => write!(f, "Add"),
            OrderBookAction::Modify => write!(f, "Modify"),
            OrderBookAction::Cancel => write!(f, "Cancel"),
            OrderBookAction::Trade => write!(f, "Trade"),
        }
    }
}

impl From<u8> for OrderBookAction {
    fn from(value: u8) -> Self {
        match value {
            b'A' => Self::Add,
            b'M' => Self::Modify,
            b'C' => Self::Cancel,
            b'T' => Self::Trade,
            _ => Self::Add, // Default
        }
    }
}

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

    #[test]
    fn test_price_conversion() {
        let fixed = 150000000000000; // 150.0 * 1e9
        let price = BidAskPair::price_to_f64(fixed);
        assert!((price - 150.0).abs() < 0.001);

        let back = BidAskPair::price_from_f64(price);
        assert_eq!(back, fixed);
    }

    #[test]
    fn test_empty_snapshot() {
        let snapshot = Mbp10Snapshot::empty("TEST".to_string());
        assert_eq!(snapshot.levels.len(), 10);
        assert_eq!(snapshot.depth(), 0);
    }

    #[test]
    fn test_snapshot_vwap() {
        let levels = vec![BidAskPair {
            bid_px: 100000000000000, // 100.0
            bid_sz: 100,
            bid_ct: 5,
            ask_px: 101000000000000, // 101.0
            ask_sz: 200,
            ask_ct: 6,
        }];

        let snapshot = Mbp10Snapshot::new("TEST".to_string(), 0, levels, 0, 0);

        let vwap = snapshot.calculate_vwap();
        // VWAP = (100*100 + 101*200) / (100 + 200) = (10000 + 20200) / 300 = 100.666...
        assert!((vwap - 100.666).abs() < 0.01);
    }
}
