//! DBN to MarketEvent converter
//!
//! Converts `ProcessedMessage` values from the Databento DBN parser
//! into the canonical `MarketEvent` type used throughout the trading engine.

use chrono::{DateTime, Utc};
use common::{Quantity, Symbol};
use data::providers::databento::dbn_parser::ProcessedMessage;
use trading_engine::timing::HardwareTimestamp;
use trading_engine::types::events::MarketEvent;

/// Convert a `HardwareTimestamp` to `DateTime<Utc>`.
///
/// Uses the nanoseconds-since-epoch stored in the timestamp.
/// Returns `None` if the nanoseconds value cannot represent a valid datetime
/// (only possible for values outside the i64 range, which is practically
/// impossible for real timestamps).
fn hw_ts_to_datetime(ts: &HardwareTimestamp) -> Option<DateTime<Utc>> {
    let nanos_i64 = i64::try_from(ts.as_nanos()).ok()?;
    Some(DateTime::from_timestamp_nanos(nanos_i64))
}

/// Convert a `ProcessedMessage` from the DBN parser into a `MarketEvent`.
///
/// Returns `Some(MarketEvent)` for `Trade`, `Ohlcv`, and `Quote` variants.
/// Returns `None` for `OrderBook` and `Status` variants, which do not have
/// a direct one-to-one mapping to the `MarketEvent` enum (order book updates
/// would need aggregation into a full snapshot, and status messages are
/// system-level events).
///
/// # Arguments
///
/// * `msg` - A reference to the `ProcessedMessage` to convert.
///
/// # Returns
///
/// `Some(MarketEvent)` if the message can be converted, `None` otherwise.
pub fn processed_to_market_event(msg: &ProcessedMessage) -> Option<MarketEvent> {
    match msg {
        ProcessedMessage::Trade {
            symbol,
            timestamp,
            price,
            size,
            side,
            trade_id,
            conditions: _,
        } => {
            let dt = hw_ts_to_datetime(timestamp)?;
            let qty = Quantity::from_decimal(*size).ok()?;
            Some(MarketEvent::Trade {
                symbol: Symbol::new(symbol.clone()),
                price: *price,
                size: qty,
                timestamp: dt,
                side: Some(*side),
                venue: None,
                trade_id: trade_id.clone(),
            })
        }
        ProcessedMessage::Ohlcv {
            symbol,
            timestamp,
            open,
            high,
            low,
            close,
            volume,
        } => {
            let dt = hw_ts_to_datetime(timestamp)?;
            let vol = Quantity::from_decimal(*volume).ok()?;
            Some(MarketEvent::Bar {
                symbol: Symbol::new(symbol.clone()),
                open: *open,
                high: *high,
                low: *low,
                close: *close,
                volume: vol,
                timestamp: dt,
                interval: String::new(),
                venue: None,
            })
        }
        ProcessedMessage::Quote {
            symbol,
            timestamp,
            bid,
            ask,
            bid_size,
            ask_size,
            exchange,
        } => {
            let dt = hw_ts_to_datetime(timestamp)?;
            let bid_price = (*bid)?;
            let ask_price = (*ask)?;
            let bq = Quantity::from_decimal((*bid_size)?).ok()?;
            let aq = Quantity::from_decimal((*ask_size)?).ok()?;
            Some(MarketEvent::Quote {
                symbol: Symbol::new(symbol.clone()),
                bid_price,
                bid_size: bq,
                ask_price,
                ask_size: aq,
                timestamp: dt,
                venue: exchange.clone(),
            })
        }
        ProcessedMessage::OrderBook { .. } | ProcessedMessage::Status { .. } => None,
    }
}

#[cfg(test)]
#[allow(clippy::float_cmp)]
mod tests {
    use super::*;
    use common::{OrderSide, Price};
    use rust_decimal::Decimal;
    use trading_engine::timing::{HardwareTimestamp, TimingSource};

    /// Helper: build a `HardwareTimestamp` from a `DateTime<Utc>`.
    fn ts_from_dt(dt: DateTime<Utc>) -> HardwareTimestamp {
        #[allow(clippy::cast_sign_loss)]
        let nanos = dt.timestamp_nanos_opt().unwrap_or(0) as u64;
        HardwareTimestamp {
            cycles: 0,
            nanos,
            source: TimingSource::SystemClock,
            validation_passed: true,
        }
    }

    #[test]
    fn test_convert_trade() {
        let now = Utc::now();
        let ts = ts_from_dt(now);
        let price = Price::from_f64(123.45).ok();
        let price = price.unwrap_or(Price::ZERO);

        let msg = ProcessedMessage::Trade {
            symbol: "AAPL".to_string(),
            timestamp: ts,
            price,
            size: Decimal::new(100, 0),
            side: OrderSide::Buy,
            trade_id: Some("t1".to_string()),
            conditions: vec![],
        };

        let event = processed_to_market_event(&msg);
        assert!(event.is_some(), "Trade should convert to MarketEvent");

        let event = event.unwrap_or_else(|| {
            MarketEvent::Control {
                command: String::new(),
                parameters: std::collections::HashMap::new(),
                timestamp: Utc::now(),
            }
        });

        if let MarketEvent::Trade {
            symbol,
            price: p,
            size,
            side,
            trade_id,
            ..
        } = &event
        {
            assert_eq!(symbol.as_str(), "AAPL");
            assert_eq!(*p, price);
            assert_eq!(size.to_f64(), 100.0);
            assert_eq!(*side, Some(OrderSide::Buy));
            assert_eq!(*trade_id, Some("t1".to_string()));
        } else {
            // Should not reach here
            assert!(matches!(event, MarketEvent::Trade { .. }), "Expected MarketEvent::Trade variant");
        }
    }

    #[test]
    fn test_convert_ohlcv_to_bar() {
        let now = Utc::now();
        let ts = ts_from_dt(now);
        let open = Price::from_f64(100.0).unwrap_or(Price::ZERO);
        let high = Price::from_f64(110.0).unwrap_or(Price::ZERO);
        let low = Price::from_f64(95.0).unwrap_or(Price::ZERO);
        let close = Price::from_f64(105.0).unwrap_or(Price::ZERO);

        let msg = ProcessedMessage::Ohlcv {
            symbol: "MSFT".to_string(),
            timestamp: ts,
            open,
            high,
            low,
            close,
            volume: Decimal::new(5000, 0),
        };

        let event = processed_to_market_event(&msg);
        assert!(event.is_some(), "OHLCV should convert to MarketEvent::Bar");

        let event = event.unwrap_or_else(|| {
            MarketEvent::Control {
                command: String::new(),
                parameters: std::collections::HashMap::new(),
                timestamp: Utc::now(),
            }
        });

        if let MarketEvent::Bar {
            symbol,
            open: o,
            high: h,
            low: l,
            close: c,
            volume,
            ..
        } = &event
        {
            assert_eq!(symbol.as_str(), "MSFT");
            assert_eq!(*o, open);
            assert_eq!(*h, high);
            assert_eq!(*l, low);
            assert_eq!(*c, close);
            assert_eq!(volume.to_f64(), 5000.0);
        } else {
            assert!(matches!(event, MarketEvent::Bar { .. }), "Expected MarketEvent::Bar variant");
        }
    }

    #[test]
    fn test_convert_quote() {
        let now = Utc::now();
        let ts = ts_from_dt(now);
        let bid = Price::from_f64(99.0).unwrap_or(Price::ZERO);
        let ask = Price::from_f64(101.0).unwrap_or(Price::ZERO);

        let msg = ProcessedMessage::Quote {
            symbol: "TSLA".to_string(),
            timestamp: ts,
            bid: Some(bid),
            ask: Some(ask),
            bid_size: Some(Decimal::new(200, 0)),
            ask_size: Some(Decimal::new(150, 0)),
            exchange: Some("NASDAQ".to_string()),
        };

        let event = processed_to_market_event(&msg);
        assert!(event.is_some(), "Quote should convert to MarketEvent::Quote");

        let event = event.unwrap_or_else(|| {
            MarketEvent::Control {
                command: String::new(),
                parameters: std::collections::HashMap::new(),
                timestamp: Utc::now(),
            }
        });

        if let MarketEvent::Quote {
            symbol,
            bid_price,
            ask_price,
            bid_size,
            ask_size,
            venue,
            ..
        } = &event
        {
            assert_eq!(symbol.as_str(), "TSLA");
            assert_eq!(*bid_price, bid);
            assert_eq!(*ask_price, ask);
            assert_eq!(bid_size.to_f64(), 200.0);
            assert_eq!(ask_size.to_f64(), 150.0);
            assert_eq!(*venue, Some("NASDAQ".to_string()));
        } else {
            assert!(matches!(event, MarketEvent::Quote { .. }), "Expected MarketEvent::Quote variant");
        }
    }
}
