use crate::{OrderBookSnapshot, PriceLevel};
use serde::{Serialize, Deserialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OrderBookAnalytics {
    pub spread: f64,
    pub spread_bps: f64,
    pub mid_price: f64,
    pub micro_price: f64, // Size-weighted mid price
    pub imbalance: f64,   // -1 to 1 (negative = bid pressure)
    pub depth_imbalance: OrderBookImbalance,
    pub liquidity_score: f64,
    pub effective_spread: f64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OrderBookImbalance {
    pub level_1: f64,
    pub level_5: f64,
    pub level_10: f64,
    pub weighted: f64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LiquidityProfile {
    pub bid_liquidity: Vec<LiquidityLevel>,
    pub ask_liquidity: Vec<LiquidityLevel>,
    pub total_bid_depth: f64,
    pub total_ask_depth: f64,
    pub bid_depth_weighted_price: f64,
    pub ask_depth_weighted_price: f64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LiquidityLevel {
    pub price: f64,
    pub size: f64,
    pub cumulative_size: f64,
    pub cost_to_execute: f64, // Cost to buy/sell up to this level
}

impl OrderBookAnalytics {
    pub fn calculate(snapshot: &OrderBookSnapshot) -> Option<Self> {
        if snapshot.bids.is_empty() || snapshot.asks.is_empty() {
            return None;
        }

        let best_bid = snapshot.bids[0].price;
        let best_ask = snapshot.asks[0].price;
        let spread = best_ask - best_bid;
        let mid_price = (best_bid + best_ask) / 2.0;
        let spread_bps = (spread / mid_price) * 10000.0;

        // Calculate micro price (size-weighted)
        let bid_size = snapshot.bids[0].size;
        let ask_size = snapshot.asks[0].size;
        let micro_price = (best_bid * ask_size + best_ask * bid_size) / (bid_size + ask_size);

        // Calculate imbalance
        let imbalance = (bid_size - ask_size) / (bid_size + ask_size);

        // Calculate depth imbalance at different levels
        let depth_imbalance = Self::calculate_depth_imbalance(snapshot);

        // Calculate liquidity score
        let liquidity_score = Self::calculate_liquidity_score(snapshot);

        // Effective spread (considers depth)
        let effective_spread = Self::calculate_effective_spread(snapshot, 1000.0); // $1000 order

        Some(OrderBookAnalytics {
            spread,
            spread_bps,
            mid_price,
            micro_price,
            imbalance,
            depth_imbalance,
            liquidity_score,
            effective_spread,
        })
    }

    fn calculate_depth_imbalance(snapshot: &OrderBookSnapshot) -> OrderBookImbalance {
        let calc_imbalance = |depth: usize| -> f64 {
            let bid_depth: f64 = snapshot.bids.iter()
                .take(depth)
                .map(|l| l.size)
                .sum();
            
            let ask_depth: f64 = snapshot.asks.iter()
                .take(depth)
                .map(|l| l.size)
                .sum();
            
            if bid_depth + ask_depth > 0.0 {
                (bid_depth - ask_depth) / (bid_depth + ask_depth)
            } else {
                0.0
            }
        };

        // Weighted imbalance (more weight on top levels)
        let mut weighted_bid = 0.0;
        let mut weighted_ask = 0.0;
        let mut weight_sum = 0.0;

        for (i, (bid, ask)) in snapshot.bids.iter().zip(snapshot.asks.iter()).enumerate().take(10) {
            let weight = 1.0 / (i + 1) as f64;
            weighted_bid += bid.size * weight;
            weighted_ask += ask.size * weight;
            weight_sum += weight;
        }

        let weighted = if weighted_bid + weighted_ask > 0.0 {
            (weighted_bid - weighted_ask) / (weighted_bid + weighted_ask)
        } else {
            0.0
        };

        OrderBookImbalance {
            level_1: calc_imbalance(1),
            level_5: calc_imbalance(5),
            level_10: calc_imbalance(10),
            weighted,
        }
    }

    fn calculate_liquidity_score(snapshot: &OrderBookSnapshot) -> f64 {
        // Liquidity score based on depth and tightness
        let depth_score = (snapshot.bids.len() + snapshot.asks.len()) as f64 / 20.0; // Normalize by 10 levels each side
        
        let volume_score = {
            let bid_volume: f64 = snapshot.bids.iter().take(5).map(|l| l.size).sum();
            let ask_volume: f64 = snapshot.asks.iter().take(5).map(|l| l.size).sum();
            ((bid_volume + ask_volume) / 10000.0).min(1.0) // Normalize by $10k
        };

        let spread_score = if let (Some(bid), Some(ask)) = (snapshot.bids.first(), snapshot.asks.first()) {
            let spread_bps = ((ask.price - bid.price) / ((ask.price + bid.price) / 2.0)) * 10000.0;
            (50.0 / (spread_bps + 1.0)).min(1.0) // Lower spread = higher score
        } else {
            0.0
        };

        (depth_score * 0.3 + volume_score * 0.4 + spread_score * 0.3).min(1.0)
    }

    fn calculate_effective_spread(snapshot: &OrderBookSnapshot, order_size_usd: f64) -> f64 {
        let avg_execution_price = |levels: &[PriceLevel], size_usd: f64, is_buy: bool| -> Option<f64> {
            let mut remaining = size_usd;
            let mut total_cost = 0.0;
            let mut total_shares = 0.0;

            for level in levels {
                let level_value = level.price * level.size;
                if remaining <= level_value {
                    let shares = remaining / level.price;
                    total_cost += remaining;
                    total_shares += shares;
                    break;
                } else {
                    total_cost += level_value;
                    total_shares += level.size;
                    remaining -= level_value;
                }
            }

            if total_shares > 0.0 {
                Some(total_cost / total_shares)
            } else {
                None
            }
        };

        if let (Some(bid_exec), Some(ask_exec)) = (
            avg_execution_price(&snapshot.bids, order_size_usd, false),
            avg_execution_price(&snapshot.asks, order_size_usd, true)
        ) {
            ask_exec - bid_exec
        } else if let (Some(bid), Some(ask)) = (snapshot.bids.first(), snapshot.asks.first()) {
            ask.price - bid.price
        } else {
            0.0
        }
    }
}

impl LiquidityProfile {
    pub fn from_snapshot(snapshot: &OrderBookSnapshot) -> Self {
        let mut bid_liquidity = Vec::new();
        let mut ask_liquidity = Vec::new();
        
        let mut cumulative_bid_size = 0.0;
        let mut cumulative_bid_cost = 0.0;
        
        for bid in &snapshot.bids {
            cumulative_bid_size += bid.size;
            cumulative_bid_cost += bid.price * bid.size;
            
            bid_liquidity.push(LiquidityLevel {
                price: bid.price,
                size: bid.size,
                cumulative_size: cumulative_bid_size,
                cost_to_execute: cumulative_bid_cost,
            });
        }
        
        let mut cumulative_ask_size = 0.0;
        let mut cumulative_ask_cost = 0.0;
        
        for ask in &snapshot.asks {
            cumulative_ask_size += ask.size;
            cumulative_ask_cost += ask.price * ask.size;
            
            ask_liquidity.push(LiquidityLevel {
                price: ask.price,
                size: ask.size,
                cumulative_size: cumulative_ask_size,
                cost_to_execute: cumulative_ask_cost,
            });
        }
        
        let total_bid_depth = cumulative_bid_cost;
        let total_ask_depth = cumulative_ask_cost;
        
        let bid_depth_weighted_price = if cumulative_bid_size > 0.0 {
            cumulative_bid_cost / cumulative_bid_size
        } else {
            0.0
        };
        
        let ask_depth_weighted_price = if cumulative_ask_size > 0.0 {
            cumulative_ask_cost / cumulative_ask_size
        } else {
            0.0
        };
        
        Self {
            bid_liquidity,
            ask_liquidity,
            total_bid_depth,
            total_ask_depth,
            bid_depth_weighted_price,
            ask_depth_weighted_price,
        }
    }

    /// Calculate the market impact of executing a given size
    pub fn calculate_market_impact(&self, size_usd: f64, is_buy: bool) -> MarketImpact {
        let levels = if is_buy { &self.ask_liquidity } else { &self.bid_liquidity };
        
        if levels.is_empty() {
            return MarketImpact::default();
        }
        
        let reference_price = levels[0].price;
        let mut remaining = size_usd;
        let mut total_cost = 0.0;
        let mut total_shares = 0.0;
        let mut levels_consumed = 0;
        
        for (i, level) in levels.iter().enumerate() {
            let level_value = level.price * level.size;
            
            if remaining <= level_value {
                let shares = remaining / level.price;
                total_cost += shares * level.price;
                total_shares += shares;
                levels_consumed = i + 1;
                break;
            } else {
                total_cost += level_value;
                total_shares += level.size;
                remaining -= level_value;
                levels_consumed = i + 1;
            }
        }
        
        let avg_execution_price = if total_shares > 0.0 {
            total_cost / total_shares
        } else {
            reference_price
        };
        
        let price_impact = if is_buy {
            (avg_execution_price - reference_price) / reference_price
        } else {
            (reference_price - avg_execution_price) / reference_price
        };
        
        let slippage = (avg_execution_price - reference_price).abs();
        
        MarketImpact {
            avg_execution_price,
            price_impact,
            slippage,
            levels_consumed,
            total_shares,
        }
    }
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct MarketImpact {
    pub avg_execution_price: f64,
    pub price_impact: f64, // As percentage
    pub slippage: f64,     // In price units
    pub levels_consumed: usize,
    pub total_shares: f64,
}

/// Track orderbook dynamics over time
pub struct OrderBookDynamics {
    snapshots: Vec<(chrono::DateTime<chrono::Utc>, OrderBookAnalytics)>,
    max_history: usize,
}

impl OrderBookDynamics {
    pub fn new(max_history: usize) -> Self {
        Self {
            snapshots: Vec::new(),
            max_history,
        }
    }

    pub fn add_snapshot(&mut self, timestamp: chrono::DateTime<chrono::Utc>, analytics: OrderBookAnalytics) {
        self.snapshots.push((timestamp, analytics));
        
        if self.snapshots.len() > self.max_history {
            self.snapshots.remove(0);
        }
    }

    pub fn get_volatility(&self, window: usize) -> Option<f64> {
        if self.snapshots.len() < window {
            return None;
        }

        let recent = &self.snapshots[self.snapshots.len() - window..];
        let mid_prices: Vec<f64> = recent.iter().map(|(_, a)| a.mid_price).collect();
        
        let mean = mid_prices.iter().sum::<f64>() / mid_prices.len() as f64;
        let variance = mid_prices.iter()
            .map(|p| (p - mean).powi(2))
            .sum::<f64>() / mid_prices.len() as f64;
        
        Some(variance.sqrt())
    }

    pub fn get_average_spread(&self, window: usize) -> Option<f64> {
        if self.snapshots.len() < window {
            return None;
        }

        let recent = &self.snapshots[self.snapshots.len() - window..];
        let total_spread: f64 = recent.iter().map(|(_, a)| a.spread).sum();
        
        Some(total_spread / window as f64)
    }

    pub fn detect_momentum(&self, window: usize) -> Option<f64> {
        if self.snapshots.len() < window {
            return None;
        }

        let recent = &self.snapshots[self.snapshots.len() - window..];
        let imbalances: Vec<f64> = recent.iter()
            .map(|(_, a)| a.depth_imbalance.weighted)
            .collect();
        
        // Average imbalance indicates momentum direction
        Some(imbalances.iter().sum::<f64>() / imbalances.len() as f64)
    }
}