use chrono::{DateTime, Utc};
use ordered_float::OrderedFloat;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt;

// ─── Asset ───────────────────────────────────────────────────────────────────

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Asset {
    Btc,
    Eth,
}

impl Asset {
    pub fn symbol(&self, venue: Venue) -> &'static str {
        match (self, venue) {
            (Asset::Btc, Venue::BinanceSpot) => "btcusdt",
            (Asset::Eth, Venue::BinanceSpot) => "ethusdt",
            (Asset::Btc, Venue::BinanceFutures) => "btcusdt",
            (Asset::Eth, Venue::BinanceFutures) => "ethusdt",
            (Asset::Btc, Venue::BybitSpot) => "BTCUSDT",
            (Asset::Eth, Venue::BybitSpot) => "ETHUSDT",
            (Asset::Btc, Venue::BybitPerps) => "BTCUSDT",
            (Asset::Eth, Venue::BybitPerps) => "ETHUSDT",
            (Asset::Btc, Venue::Deribit) => "BTC",
            (Asset::Eth, Venue::Deribit) => "ETH",
        }
    }

    pub fn all() -> &'static [Asset] {
        &[Asset::Btc, Asset::Eth]
    }
}

impl fmt::Display for Asset {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Asset::Btc => write!(f, "btc"),
            Asset::Eth => write!(f, "eth"),
        }
    }
}

// ─── Venue ───────────────────────────────────────────────────────────────────

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Venue {
    BinanceSpot,
    BinanceFutures,
    BybitSpot,
    BybitPerps,
    Deribit,
}

impl Venue {
    pub fn all() -> &'static [Venue] {
        &[
            Venue::BinanceSpot,
            Venue::BinanceFutures,
            Venue::BybitSpot,
            Venue::BybitPerps,
            Venue::Deribit,
        ]
    }
}

impl fmt::Display for Venue {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Venue::BinanceSpot => write!(f, "binance_spot"),
            Venue::BinanceFutures => write!(f, "binance_futures"),
            Venue::BybitSpot => write!(f, "bybit_spot"),
            Venue::BybitPerps => write!(f, "bybit_perps"),
            Venue::Deribit => write!(f, "deribit"),
        }
    }
}

// ─── Horizon ─────────────────────────────────────────────────────────────────

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
pub enum Horizon {
    #[serde(rename = "1m")]
    M1,
    #[serde(rename = "5m")]
    M5,
    #[serde(rename = "15m")]
    M15,
    #[serde(rename = "30m")]
    M30,
}

impl Horizon {
    pub fn minutes(&self) -> u32 {
        match self {
            Horizon::M1 => 1,
            Horizon::M5 => 5,
            Horizon::M15 => 15,
            Horizon::M30 => 30,
        }
    }

    pub fn all() -> &'static [Horizon] {
        &[Horizon::M1, Horizon::M5, Horizon::M15, Horizon::M30]
    }

    pub fn seconds(&self) -> u64 {
        self.minutes() as u64 * 60
    }
}

impl fmt::Display for Horizon {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Horizon::M1 => write!(f, "1m"),
            Horizon::M5 => write!(f, "5m"),
            Horizon::M15 => write!(f, "15m"),
            Horizon::M30 => write!(f, "30m"),
        }
    }
}

// ─── Side ────────────────────────────────────────────────────────────────────

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Side {
    Buy,
    Sell,
}

// ─── Trade ───────────────────────────────────────────────────────────────────

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Trade {
    pub timestamp: DateTime<Utc>,
    pub asset: Asset,
    pub venue: Venue,
    pub price: f64,
    pub size: f64,
    pub side: Side,
    pub trade_id: Option<String>,
}

// ─── Book types ──────────────────────────────────────────────────────────────

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BookLevel {
    pub price: OrderedFloat<f64>,
    pub size: f64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct L2Snapshot {
    pub timestamp: DateTime<Utc>,
    pub asset: Asset,
    pub venue: Venue,
    pub bids: Vec<BookLevel>,
    pub asks: Vec<BookLevel>,
    pub last_update_id: Option<u64>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct L2Update {
    pub timestamp: DateTime<Utc>,
    pub asset: Asset,
    pub venue: Venue,
    pub bids: Vec<BookLevel>,
    pub asks: Vec<BookLevel>,
    pub first_update_id: Option<u64>,
    pub last_update_id: Option<u64>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BboQuote {
    pub timestamp: DateTime<Utc>,
    pub asset: Asset,
    pub venue: Venue,
    pub bid_price: f64,
    pub bid_size: f64,
    pub ask_price: f64,
    pub ask_size: f64,
}

impl BboQuote {
    pub fn mid(&self) -> f64 {
        (self.bid_price + self.ask_price) / 2.0
    }

    pub fn spread(&self) -> f64 {
        self.ask_price - self.bid_price
    }

    pub fn spread_rel(&self) -> f64 {
        let mid = self.mid();
        if mid == 0.0 {
            return 0.0;
        }
        self.spread() / mid
    }

    pub fn microprice(&self) -> f64 {
        let total = self.bid_size + self.ask_size;
        if total == 0.0 {
            return self.mid();
        }
        (self.bid_price * self.ask_size + self.ask_price * self.bid_size) / total
    }
}

// ─── Bar ─────────────────────────────────────────────────────────────────────

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Bar {
    pub timestamp: DateTime<Utc>,
    pub asset: Asset,
    pub venue: Venue,
    pub open: f64,
    pub high: f64,
    pub low: f64,
    pub close: f64,
    pub volume: f64,
    pub buy_volume: f64,
    pub sell_volume: f64,
    pub trade_count: u64,
    pub vwap: f64,
    pub microprice: f64,
    pub spread: f64,
    pub depth_bid_5: f64,
    pub depth_ask_5: f64,
    pub is_gap: bool,
}

// ─── Funding, OI, Liquidation ────────────────────────────────────────────────

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FundingRate {
    pub timestamp: DateTime<Utc>,
    pub asset: Asset,
    pub venue: Venue,
    pub rate: f64,
    pub next_funding_time: Option<DateTime<Utc>>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OpenInterest {
    pub timestamp: DateTime<Utc>,
    pub asset: Asset,
    pub venue: Venue,
    pub oi: f64,
    pub oi_value: f64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Liquidation {
    pub timestamp: DateTime<Utc>,
    pub asset: Asset,
    pub venue: Venue,
    pub side: Side,
    pub price: f64,
    pub size: f64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MarkIndex {
    pub timestamp: DateTime<Utc>,
    pub asset: Asset,
    pub venue: Venue,
    pub mark_price: f64,
    pub index_price: f64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BookStatus {
    pub timestamp: DateTime<Utc>,
    pub asset: Asset,
    pub venue: Venue,
    pub is_seeded: bool,
    pub is_sequence_valid: bool,
    pub is_gap_detected: bool,
    pub reseed_count: u64,
}

// ─── Options / IV ────────────────────────────────────────────────────────────

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IvQuote {
    pub timestamp: DateTime<Utc>,
    pub asset: Asset,
    pub atm_iv: f64,
    pub skew_25d: f64,
    pub term_slope: f64,
}

// ─── Regime ──────────────────────────────────────────────────────────────────

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Regime {
    #[serde(rename = "calm_mr")]
    CalmMeanReversion,
    #[serde(rename = "calm_trend")]
    CalmTrend,
    #[serde(rename = "vol_expansion")]
    VolExpansion,
    #[serde(rename = "thin_liq")]
    ThinLiquidityStressed,
}

impl Default for Regime {
    fn default() -> Self {
        Regime::CalmMeanReversion
    }
}

impl fmt::Display for Regime {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Regime::CalmMeanReversion => write!(f, "calm_mean_reversion"),
            Regime::CalmTrend => write!(f, "calm_trend"),
            Regime::VolExpansion => write!(f, "vol_expansion"),
            Regime::ThinLiquidityStressed => write!(f, "thin_liquidity_stressed"),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RegimeState {
    pub regime: Regime,
    pub timestamp: DateTime<Utc>,
    pub confidence: f64,
    pub persistence_count: u32,
}

impl Default for RegimeState {
    fn default() -> Self {
        Self {
            regime: Regime::default(),
            timestamp: Utc::now(),
            confidence: 0.0,
            persistence_count: 0,
        }
    }
}

// ─── Prediction ──────────────────────────────────────────────────────────────

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Label {
    Up,
    Flat,
    Down,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Prediction {
    pub timestamp: DateTime<Utc>,
    pub asset: Asset,
    pub horizon: Horizon,
    pub p_up: f64,
    pub p_flat: f64,
    pub p_down: f64,
    pub sigma_hat: f64,
    pub p_jump: Option<f64>,
    pub regime: Regime,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum SignalDirection {
    Long,
    Short,
    Flat,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TradeSignal {
    pub timestamp: DateTime<Utc>,
    pub asset: Asset,
    pub horizon: Horizon,
    pub direction: SignalDirection,
    pub size: f64,
    pub entry_price: f64,
    pub stop_distance: f64,
    pub prediction: Prediction,
}

// ─── Market events (channel types) ──────────────────────────────────────────

#[derive(Debug, Clone)]
pub enum MarketEvent {
    Trade(Trade),
    L2Snapshot(L2Snapshot),
    L2Update(L2Update),
    BookStatus(BookStatus),
    Bbo(BboQuote),
    MarkIndex(MarkIndex),
}

#[derive(Debug, Clone)]
pub enum FundingEvent {
    Funding(FundingRate),
    Oi(OpenInterest),
    Liquidation(Liquidation),
}

#[derive(Debug, Clone)]
pub enum IvEvent {
    IvUpdate(IvQuote),
}

// ─── Feature row ─────────────────────────────────────────────────────────────

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FeatureRow {
    pub timestamp: DateTime<Utc>,
    pub asset: Asset,
    pub horizon: Horizon,
    pub features: HashMap<String, f64>,
}

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

    #[test]
    fn test_asset_symbols() {
        assert_eq!(Asset::Btc.symbol(Venue::BinanceSpot), "btcusdt");
        assert_eq!(Asset::Eth.symbol(Venue::BinanceSpot), "ethusdt");
        assert_eq!(Asset::Btc.symbol(Venue::BinanceFutures), "btcusdt");
        assert_eq!(Asset::Btc.symbol(Venue::BybitSpot), "BTCUSDT");
        assert_eq!(Asset::Eth.symbol(Venue::BybitPerps), "ETHUSDT");
        assert_eq!(Asset::Btc.symbol(Venue::Deribit), "BTC");
        assert_eq!(Asset::Eth.symbol(Venue::Deribit), "ETH");
    }

    #[test]
    fn test_horizon_minutes() {
        assert_eq!(Horizon::M1.minutes(), 1);
        assert_eq!(Horizon::M5.minutes(), 5);
        assert_eq!(Horizon::M15.minutes(), 15);
        assert_eq!(Horizon::M30.minutes(), 30);
    }

    #[test]
    fn test_horizon_seconds() {
        assert_eq!(Horizon::M1.seconds(), 60);
        assert_eq!(Horizon::M5.seconds(), 300);
        assert_eq!(Horizon::M15.seconds(), 900);
        assert_eq!(Horizon::M30.seconds(), 1800);
    }

    #[test]
    fn test_bbo_calculations() {
        let bbo = BboQuote {
            timestamp: Utc::now(),
            asset: Asset::Btc,
            venue: Venue::BinanceSpot,
            bid_price: 50000.0,
            bid_size: 1.0,
            ask_price: 50010.0,
            ask_size: 1.0,
        };
        assert!((bbo.mid() - 50005.0).abs() < 1e-10);
        assert!((bbo.spread() - 10.0).abs() < 1e-10);
        assert!((bbo.spread_rel() - 10.0 / 50005.0).abs() < 1e-10);
        // Equal sizes -> microprice == mid
        assert!((bbo.microprice() - 50005.0).abs() < 1e-10);
    }

    #[test]
    fn test_microprice_weighted() {
        let bbo = BboQuote {
            timestamp: Utc::now(),
            asset: Asset::Btc,
            venue: Venue::BinanceSpot,
            bid_price: 100.0,
            bid_size: 3.0,
            ask_price: 102.0,
            ask_size: 1.0,
        };
        // microprice = (100*1 + 102*3) / (3+1) = (100 + 306) / 4 = 101.5
        assert!((bbo.microprice() - 101.5).abs() < 1e-10);
    }

    #[test]
    fn test_serde_roundtrip_asset() {
        for asset in Asset::all() {
            let json = serde_json::to_string(asset).unwrap();
            let back: Asset = serde_json::from_str(&json).unwrap();
            assert_eq!(*asset, back);
        }
    }

    #[test]
    fn test_serde_roundtrip_venue() {
        for venue in Venue::all() {
            let json = serde_json::to_string(venue).unwrap();
            let back: Venue = serde_json::from_str(&json).unwrap();
            assert_eq!(*venue, back);
        }
    }

    #[test]
    fn test_serde_roundtrip_horizon() {
        for h in Horizon::all() {
            let json = serde_json::to_string(h).unwrap();
            let back: Horizon = serde_json::from_str(&json).unwrap();
            assert_eq!(*h, back);
        }
    }

    #[test]
    fn test_serde_roundtrip_trade() {
        let trade = Trade {
            timestamp: Utc::now(),
            asset: Asset::Btc,
            venue: Venue::BinanceSpot,
            price: 50000.0,
            size: 0.1,
            side: Side::Buy,
            trade_id: Some("12345".to_string()),
        };
        let json = serde_json::to_string(&trade).unwrap();
        let back: Trade = serde_json::from_str(&json).unwrap();
        assert_eq!(back.asset, Asset::Btc);
        assert!((back.price - 50000.0).abs() < 1e-10);
    }

    #[test]
    fn test_serde_roundtrip_bar() {
        let bar = Bar {
            timestamp: Utc::now(),
            asset: Asset::Eth,
            venue: Venue::BybitSpot,
            open: 3000.0,
            high: 3010.0,
            low: 2990.0,
            close: 3005.0,
            volume: 100.0,
            buy_volume: 60.0,
            sell_volume: 40.0,
            trade_count: 50,
            vwap: 3002.0,
            microprice: 3004.0,
            spread: 0.5,
            depth_bid_5: 10.0,
            depth_ask_5: 12.0,
            is_gap: false,
        };
        let json = serde_json::to_string(&bar).unwrap();
        let back: Bar = serde_json::from_str(&json).unwrap();
        assert_eq!(back.asset, Asset::Eth);
        assert!((back.vwap - 3002.0).abs() < 1e-10);
    }

    #[test]
    fn test_serde_roundtrip_prediction() {
        let pred = Prediction {
            timestamp: Utc::now(),
            asset: Asset::Btc,
            horizon: Horizon::M5,
            p_up: 0.4,
            p_flat: 0.3,
            p_down: 0.3,
            sigma_hat: 0.01,
            p_jump: Some(0.05),
            regime: Regime::CalmTrend,
        };
        let json = serde_json::to_string(&pred).unwrap();
        let back: Prediction = serde_json::from_str(&json).unwrap();
        assert!((back.p_up - 0.4).abs() < 1e-10);
        assert_eq!(back.regime, Regime::CalmTrend);
    }

    #[test]
    fn test_serde_roundtrip_regime() {
        let regimes = [
            Regime::CalmMeanReversion,
            Regime::CalmTrend,
            Regime::VolExpansion,
            Regime::ThinLiquidityStressed,
        ];
        for r in &regimes {
            let json = serde_json::to_string(r).unwrap();
            let back: Regime = serde_json::from_str(&json).unwrap();
            assert_eq!(*r, back);
        }
    }

    #[test]
    fn test_serde_roundtrip_label() {
        for label in [Label::Up, Label::Flat, Label::Down] {
            let json = serde_json::to_string(&label).unwrap();
            let back: Label = serde_json::from_str(&json).unwrap();
            assert_eq!(label, back);
        }
    }

    #[test]
    fn test_serde_roundtrip_iv_quote() {
        let iv = IvQuote {
            timestamp: Utc::now(),
            asset: Asset::Btc,
            atm_iv: 0.55,
            skew_25d: -0.03,
            term_slope: 0.02,
        };
        let json = serde_json::to_string(&iv).unwrap();
        let back: IvQuote = serde_json::from_str(&json).unwrap();
        assert!((back.atm_iv - 0.55).abs() < 1e-10);
    }

    #[test]
    fn test_serde_roundtrip_feature_row() {
        let mut features = HashMap::new();
        features.insert("micro_tilt".to_string(), 0.001);
        features.insert("imb_5".to_string(), 0.3);
        let row = FeatureRow {
            timestamp: Utc::now(),
            asset: Asset::Btc,
            horizon: Horizon::M1,
            features,
        };
        let json = serde_json::to_string(&row).unwrap();
        let back: FeatureRow = serde_json::from_str(&json).unwrap();
        assert!((back.features["micro_tilt"] - 0.001).abs() < 1e-10);
    }
}
