//! V10 Block W — Order arrival / cancel / modify rates + trade-to-quote ratio.
//!
//! ## Motivation
//!
//! The full MBP-10 stream contains 4 main event types (per Databento spec):
//! - `'A'` Add (new limit order posted)
//! - `'C'` Cancel (order removed)
//! - `'M'` Modify (order size/price changed)
//! - `'T'` Trade (execution)
//!
//! Production `Mbp10Snapshot` only retains the resulting book state — the
//! per-snapshot count of A/C/M events is dropped at parse time. This module
//! gives the v10 pipeline binary a place to ACCUMULATE those events as it
//! iterates raw `Mbp10Msg` records, producing 4 bar-frequency features.
//!
//! ## Features (4 dims)
//!
//! 1. `add_rate` — limit orders posted per second (rolling window)
//! 2. `cancel_rate` — cancellations per second
//! 3. `modify_rate` — modifications per second
//! 4. `trade_to_quote_ratio` (TQR) — `trades / (adds + cancels + modifies)`
//!
//! ## Why these are useful
//!
//! - **Cancel-to-add ratio** is a classic algo-trader signature (high cancel
//!   rates indicate aggressive market-making or quote-stuffing).
//! - **TQR** captures "is the book moving but nothing's executing?" (low TQR)
//!   versus "lots of execution per quote update" (high TQR).
//! - Per Hasbrouck 1995 + Cartea 2015, order arrival intensity tracked separately
//!   from trade intensity captures **liquidity provision dynamics**, which is
//!   independent of (and complementary to) the trade-side OFI/VPIN features.
//!
//! ## Usage
//!
//! ```ignore
//! let mut counters = OrderEventCounters::new(60_000_000_000); // 60-sec window
//! for msg in mbp10_msgs {
//!     counters.record(msg.hd.ts_event, msg.action as u8);
//! }
//! let features = counters.features(); // at bar boundary
//! ```

use std::collections::VecDeque;

/// Categorized event type (only the four we care about; everything else dropped).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EventType {
    Add,
    Cancel,
    Modify,
    Trade,
}

impl EventType {
    /// Map a DBN action byte to our enum. Returns None for unknown actions
    /// ('F' Fill, 'R' Clear, etc.) so we don't double-count.
    pub fn from_action_byte(action: u8) -> Option<Self> {
        match action {
            b'A' => Some(Self::Add),
            b'C' => Some(Self::Cancel),
            b'M' => Some(Self::Modify),
            b'T' => Some(Self::Trade),
            _ => None,
        }
    }
}

/// V10 Block W — streaming order-event counter with rolling window.
#[derive(Debug, Clone)]
pub struct OrderEventCounters {
    /// Rolling-window size in nanoseconds. Events older than this are evicted.
    window_ns: u64,
    /// Ring buffer of recent events: (timestamp_ns, event_type).
    events: VecDeque<(u64, EventType)>,
}

impl OrderEventCounters {
    pub fn new(window_ns: u64) -> Self {
        Self {
            window_ns: window_ns.max(1_000_000), // floor at 1ms to avoid divide-by-zero
            events: VecDeque::with_capacity(1024),
        }
    }

    /// V10 default: 60-second rolling window.
    pub fn with_defaults() -> Self {
        Self::new(60_000_000_000)
    }

    /// Record one event. Unknown action bytes are silently dropped.
    pub fn record(&mut self, timestamp_ns: u64, action: u8) {
        if let Some(ev) = EventType::from_action_byte(action) {
            self.events.push_back((timestamp_ns, ev));
            self.evict_old(timestamp_ns);
        }
    }

    /// Drop events older than `window_ns` before `current_ts`.
    fn evict_old(&mut self, current_ts: u64) {
        while let Some(&(ts, _)) = self.events.front() {
            if current_ts.saturating_sub(ts) > self.window_ns {
                self.events.pop_front();
            } else {
                break;
            }
        }
    }

    /// Compute Block W features: `[add_rate, cancel_rate, modify_rate, tqr]`.
    ///
    /// Rates are in events-per-second. TQR is clamped to [0, 100] for
    /// numerical safety (during quiescent quote periods, low non-trade counts
    /// can make the raw ratio enormous).
    pub fn features(&self) -> [f64; 4] {
        let mut adds = 0_u64;
        let mut cancels = 0_u64;
        let mut modifies = 0_u64;
        let mut trades = 0_u64;
        for &(_, ev) in &self.events {
            match ev {
                EventType::Add => adds += 1,
                EventType::Cancel => cancels += 1,
                EventType::Modify => modifies += 1,
                EventType::Trade => trades += 1,
            }
        }
        let window_secs = (self.window_ns as f64) / 1.0e9;
        let add_rate = adds as f64 / window_secs.max(1e-9);
        let cancel_rate = cancels as f64 / window_secs.max(1e-9);
        let modify_rate = modifies as f64 / window_secs.max(1e-9);

        let non_trade = adds + cancels + modifies;
        let tqr = if non_trade > 0 {
            (trades as f64 / non_trade as f64).clamp(0.0, 100.0)
        } else if trades > 0 {
            100.0 // all trades, no quotes → max TQR
        } else {
            0.0
        };

        [add_rate, cancel_rate, modify_rate, tqr]
    }

    pub fn reset(&mut self) {
        self.events.clear();
    }

    pub fn n_events_in_window(&self) -> usize {
        self.events.len()
    }
}

impl Default for OrderEventCounters {
    fn default() -> Self {
        Self::with_defaults()
    }
}

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

    #[test]
    fn test_event_type_dispatch() {
        assert_eq!(EventType::from_action_byte(b'A'), Some(EventType::Add));
        assert_eq!(EventType::from_action_byte(b'C'), Some(EventType::Cancel));
        assert_eq!(EventType::from_action_byte(b'M'), Some(EventType::Modify));
        assert_eq!(EventType::from_action_byte(b'T'), Some(EventType::Trade));
        // 'F' Fill, 'R' Clear, etc. — silently dropped
        assert_eq!(EventType::from_action_byte(b'F'), None);
        assert_eq!(EventType::from_action_byte(b'R'), None);
    }

    #[test]
    fn test_cold_start_features_all_zero() {
        let oec = OrderEventCounters::with_defaults();
        assert_eq!(oec.features(), [0.0; 4]);
    }

    #[test]
    fn test_add_rate_matches_event_density() {
        // 60 Add events in 60-sec window → add_rate = 1.0 events/sec
        let mut oec = OrderEventCounters::new(60_000_000_000);
        for i in 0..60 {
            oec.record((i as u64) * 1_000_000_000, b'A');
        }
        let [add_rate, cancel, modify, tqr] = oec.features();
        assert!((add_rate - 1.0).abs() < 1e-9, "expected add_rate=1.0, got {add_rate}");
        assert_eq!(cancel, 0.0);
        assert_eq!(modify, 0.0);
        // No trades, all quotes → TQR = 0
        assert_eq!(tqr, 0.0);
    }

    #[test]
    fn test_tqr_pure_trades_caps_at_100() {
        // All trades, no quotes → TQR = 100 (cap)
        let mut oec = OrderEventCounters::new(60_000_000_000);
        for i in 0..50 {
            oec.record((i as u64) * 1_000_000_000, b'T');
        }
        let [_, _, _, tqr] = oec.features();
        assert_eq!(tqr, 100.0, "all-trades-no-quotes → max TQR");
    }

    #[test]
    fn test_tqr_typical_ratio() {
        // 100 Adds + 50 Cancels + 30 Trades → TQR = 30 / (100+50) = 0.2
        let mut oec = OrderEventCounters::new(60_000_000_000);
        let mut t = 0_u64;
        for _ in 0..100 {
            oec.record(t, b'A');
            t += 100_000_000;
        }
        for _ in 0..50 {
            oec.record(t, b'C');
            t += 100_000_000;
        }
        for _ in 0..30 {
            oec.record(t, b'T');
            t += 100_000_000;
        }
        let [_, _, _, tqr] = oec.features();
        assert!((tqr - 0.2).abs() < 1e-6, "expected TQR=0.2, got {tqr}");
    }

    #[test]
    fn test_old_events_evicted() {
        let mut oec = OrderEventCounters::new(10_000_000_000); // 10-sec window
        // Record 20 events spread over 30 seconds
        for i in 0..20 {
            oec.record((i as u64) * 1_500_000_000, b'A'); // every 1.5 sec
        }
        // Latest ts = 19 × 1.5e9 = 28.5 sec
        // Window: 28.5 - 10 = 18.5 sec → events with ts >= 18.5 sec
        // ts >= 19.5 sec (event 13 onwards) → 7 events
        let n = oec.n_events_in_window();
        assert!(
            n <= 8 && n >= 6,
            "expected ~7 events in window (10-sec / 1.5-sec spacing), got {n}"
        );
    }

    #[test]
    fn test_unknown_actions_dropped() {
        let mut oec = OrderEventCounters::with_defaults();
        oec.record(1_000_000_000, b'F'); // Fill (unknown to us)
        oec.record(2_000_000_000, b'R'); // Clear
        oec.record(3_000_000_000, b'X'); // garbage
        oec.record(4_000_000_000, b'A'); // valid Add
        assert_eq!(
            oec.n_events_in_window(),
            1,
            "only valid event types should be retained, got {} events",
            oec.n_events_in_window()
        );
    }

    #[test]
    fn test_reset_clears_features() {
        let mut oec = OrderEventCounters::with_defaults();
        for i in 0..10 {
            oec.record((i as u64) * 1_000_000_000, b'A');
        }
        assert_ne!(oec.features()[0], 0.0);
        oec.reset();
        assert_eq!(oec.features(), [0.0; 4]);
    }

    #[test]
    fn test_all_three_quote_event_types_counted_independently() {
        // 30 Adds, 20 Cancels, 10 Modifies, 5 Trades — over 50-sec window
        let mut oec = OrderEventCounters::new(50_000_000_000);
        let mut t = 0_u64;
        for _ in 0..30 {
            oec.record(t, b'A');
            t += 100_000_000;
        }
        for _ in 0..20 {
            oec.record(t, b'C');
            t += 100_000_000;
        }
        for _ in 0..10 {
            oec.record(t, b'M');
            t += 100_000_000;
        }
        for _ in 0..5 {
            oec.record(t, b'T');
            t += 100_000_000;
        }
        let [add_rate, cancel_rate, modify_rate, _] = oec.features();
        // Rates: 30/50 = 0.6, 20/50 = 0.4, 10/50 = 0.2
        assert!((add_rate - 0.6).abs() < 1e-6);
        assert!((cancel_rate - 0.4).abs() < 1e-6);
        assert!((modify_rate - 0.2).abs() < 1e-6);
    }
}
