use crate::error::HorizonError;
use crate::exchanges::Exchange;
use crate::types::{Fill, Order, OrderRequest, OrderSide, OrderStatus, OrderType, TimeInForce};
use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Mutex;
use std::time::{SystemTime, UNIX_EPOCH};

/// Paper trading exchange — simulated matching engine.
///
/// Orders rest in the book until `tick(market_id, mid_price)` is called.
/// Buy limit orders fill when mid_price <= order_price.
/// Sell limit orders fill when mid_price >= order_price.
pub struct PaperExchange {
    inner: Mutex<PaperInner>,
    maker_fee_rate: f64,
    taker_fee_rate: f64,
    partial_fill_ratio: f64,
    next_order_id: AtomicU64,
}

struct PaperInner {
    orders: Vec<Order>,
    fills: Vec<Fill>,
    next_fill_id: u64,
    /// Index: market_id -> vec of indices into `orders`.
    /// Maintained on insert; rebuilt after eviction.
    market_index: HashMap<String, Vec<usize>>,
}

impl PaperInner {
    /// Rebuild the market_index from scratch (needed after evict_terminal shifts indices).
    fn rebuild_market_index(&mut self) {
        self.market_index.clear();
        for (i, order) in self.orders.iter().enumerate() {
            self.market_index
                .entry(order.market_id.clone())
                .or_default()
                .push(i);
        }
    }
}

#[inline]
fn is_open(status: OrderStatus) -> bool {
    matches!(
        status,
        OrderStatus::New
            | OrderStatus::Submitted
            | OrderStatus::Accepted
            | OrderStatus::PartiallyFilled
    )
}

impl PaperExchange {
    pub fn new(fee_rate: f64) -> Self {
        Self::with_maker_taker_fees(fee_rate, fee_rate, 1.0)
    }

    pub fn with_partial_fill_ratio(fee_rate: f64, partial_fill_ratio: f64) -> Self {
        Self::with_maker_taker_fees(fee_rate, fee_rate, partial_fill_ratio)
    }

    pub fn with_maker_taker_fees(
        maker_fee_rate: f64,
        taker_fee_rate: f64,
        partial_fill_ratio: f64,
    ) -> Self {
        assert!(
            maker_fee_rate >= 0.0 && maker_fee_rate.is_finite(),
            "maker_fee_rate must be non-negative and finite, got {}",
            maker_fee_rate
        );
        assert!(
            taker_fee_rate >= 0.0 && taker_fee_rate.is_finite(),
            "taker_fee_rate must be non-negative and finite, got {}",
            taker_fee_rate
        );
        Self {
            inner: Mutex::new(PaperInner {
                orders: Vec::new(),
                fills: Vec::new(),
                next_fill_id: 1,
                market_index: HashMap::new(),
            }),
            maker_fee_rate,
            taker_fee_rate,
            partial_fill_ratio: partial_fill_ratio.clamp(0.0, 1.0),
            next_order_id: AtomicU64::new(1),
        }
    }

    /// Tick the matching engine with a new price for a market.
    /// Returns the number of fills generated.
    ///
    /// Matching rules:
    /// - Market orders: always match regardless of price, fill at mid_price.
    /// - Limit orders: buy fills when mid <= price, sell fills when mid >= price.
    ///
    /// TIF rules (after matching):
    /// - GTC: unfilled orders remain in the book.
    /// - FOK: only fills if entire order can be filled; otherwise canceled.
    /// - FAK/IOC: fill what matches, cancel the remainder.
    ///
    /// Partial fill simulation: when partial_fill_ratio < 1.0, only a fraction
    /// of the order's remaining size is filled per tick.
    #[inline]
    pub fn tick(&self, market_id: &str, mid_price: f64) -> usize {
        assert!(
            mid_price.is_finite() && mid_price > 0.0,
            "mid_price must be finite and positive, got {}",
            mid_price
        );
        let mut inner = self.inner.lock().unwrap_or_else(|e| e.into_inner());
        let now = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap_or_default()
            .as_secs_f64();

        // Phase 1: identify which orders should fill (using market_index for O(1) lookup)
        let candidate_indices: Vec<usize> = inner
            .market_index
            .get(market_id)
            .cloned()
            .unwrap_or_default();
        let to_fill: Vec<usize> = candidate_indices
            .into_iter()
            .filter(|&i| {
                let order = &inner.orders[i];
                is_open(order.status)
                    && match order.order_type {
                        OrderType::Market => true, // Market orders always match
                        OrderType::Limit => match order.order_side {
                            OrderSide::Buy => mid_price <= order.price,
                            OrderSide::Sell => mid_price >= order.price,
                        },
                    }
            })
            .collect();

        // Phase 2: create fills with partial fill support
        let mut next_id = inner.next_fill_id;
        let maker_fee = self.maker_fee_rate;
        let taker_fee = self.taker_fee_rate;
        let partial_ratio = self.partial_fill_ratio;
        let mut new_fills = Vec::with_capacity(to_fill.len());
        let mut fill_sizes: Vec<f64> = Vec::with_capacity(to_fill.len());

        for &i in &to_fill {
            let order = &inner.orders[i];

            // FOK orders must fill completely — ignore partial_fill_ratio
            let fill_size = if order.time_in_force == TimeInForce::FOK {
                order.remaining_size
            } else if partial_ratio < 1.0 {
                (order.remaining_size * partial_ratio).max(0.01).min(order.remaining_size)
            } else {
                order.remaining_size
            };

            // Market orders fill at mid_price, limit orders at order.price
            let fill_price = match order.order_type {
                OrderType::Market => mid_price,
                OrderType::Limit => order.price,
            };

            // Determine maker vs taker:
            // - Market orders are always takers (they cross the spread immediately)
            // - Limit buy resting below mid = maker; crossing at/above mid = taker
            // - Limit sell resting above mid = maker; crossing at/below mid = taker
            let is_maker = match order.order_type {
                OrderType::Market => false,
                OrderType::Limit => match order.order_side {
                    OrderSide::Buy => order.price < mid_price,
                    OrderSide::Sell => order.price > mid_price,
                },
            };

            let fee_rate = if is_maker { maker_fee } else { taker_fee };
            let fee = fill_size * fill_price * fee_rate;

            let fill_id = format!("paper_fill_{}", next_id);
            next_id += 1;

            new_fills.push(Fill {
                fill_id,
                order_id: order.id.clone(),
                market_id: order.market_id.clone(),
                side: order.side,
                order_side: order.order_side,
                price: fill_price,
                size: fill_size,
                fee,
                timestamp: now,
                token_id: None,
                exchange: "paper".to_string(),
                is_maker,
            });
            fill_sizes.push(fill_size);
        }

        // Phase 3: mutate order states based on actual fill sizes
        for (idx, &i) in to_fill.iter().enumerate() {
            let order = &mut inner.orders[i];
            let fill_size = fill_sizes[idx];
            order.filled_size += fill_size;
            order.remaining_size = (order.size - order.filled_size).max(0.0);
            if order.remaining_size <= f64::EPSILON {
                order.status = OrderStatus::Filled;
            } else {
                order.status = OrderStatus::PartiallyFilled;
            }
        }

        // Phase 4: cancel unfilled FOK/FAK orders and unfilled market orders (using index)
        let cancel_indices: Vec<usize> = inner
            .market_index
            .get(market_id)
            .cloned()
            .unwrap_or_default();
        for i in cancel_indices {
            let order = &mut inner.orders[i];
            if is_open(order.status) {
                let should_cancel = match order.time_in_force {
                    TimeInForce::FOK | TimeInForce::FAK => true,
                    _ => false,
                } || order.order_type == OrderType::Market;

                if should_cancel {
                    order.status = OrderStatus::Canceled;
                    order.status_reason = Some("tif_expired".into());
                }
            }
        }

        inner.next_fill_id = next_id;
        let fill_count = new_fills.len();
        inner.fills.extend(new_fills);

        fill_count
    }

    /// Get all resting (open) orders.
    pub fn resting_orders(&self) -> Vec<Order> {
        let inner = self.inner.lock().unwrap_or_else(|e| e.into_inner());
        inner
            .orders
            .iter()
            .filter(|o| is_open(o.status))
            .cloned()
            .collect()
    }

    /// Evict terminal (non-open) orders from the internal vec to bound memory.
    pub fn evict_terminal(&self) {
        let mut inner = self.inner.lock().unwrap_or_else(|e| e.into_inner());
        inner.orders.retain(|o| is_open(o.status));
        // Rebuild market_index after indices shifted due to retain
        inner.rebuild_market_index();
    }
}

impl Exchange for PaperExchange {
    #[inline]
    fn submit_order(&self, req: &OrderRequest) -> Result<String, HorizonError> {
        // Validate price/size before accepting the order
        if !req.size.is_finite() || req.size <= 0.0 {
            return Err(HorizonError::Order(format!(
                "paper: invalid order size {}", req.size
            )));
        }
        if !req.price.is_finite() {
            return Err(HorizonError::Order(format!(
                "paper: invalid order price {}", req.price
            )));
        }
        let mut inner = self.inner.lock().unwrap_or_else(|e| e.into_inner());
        let seq = self.next_order_id.fetch_add(1, Ordering::Relaxed);
        let id = format!("p{}", seq);
        let now = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap_or_default()
            .as_secs_f64();

        let order = Order {
            id: id.clone(),
            market_id: req.market_id.clone(),
            side: req.side,
            order_side: req.order_side,
            price: req.price,
            size: req.size,
            filled_size: 0.0,
            remaining_size: req.size,
            order_type: req.order_type,
            time_in_force: req.time_in_force,
            status: OrderStatus::Accepted,
            created_at: now,
            status_reason: None,
            exchange: "paper".to_string(),
            amendment_count: 0,
            token_id: req.token_id.clone(),
            neg_risk: req.neg_risk,
        };

        let idx = inner.orders.len();
        inner.market_index
            .entry(req.market_id.clone())
            .or_default()
            .push(idx);
        inner.orders.push(order);
        Ok(id)
    }

    fn cancel_order(&self, order_id: &str) -> Result<(), HorizonError> {
        let mut inner = self.inner.lock().unwrap_or_else(|e| e.into_inner());
        for order in inner.orders.iter_mut() {
            if order.id == order_id && is_open(order.status) {
                order.status = OrderStatus::Canceled;
                order.status_reason = Some("canceled".to_string());
                return Ok(());
            }
        }
        Err(HorizonError::Order(format!(
            "order not found: {}",
            order_id
        )))
    }

    fn cancel_all(&self) -> Result<usize, HorizonError> {
        let mut inner = self.inner.lock().unwrap_or_else(|e| e.into_inner());
        let mut count = 0;
        for order in inner.orders.iter_mut() {
            if is_open(order.status) {
                order.status = OrderStatus::Canceled;
                order.status_reason = Some("cancel_all".to_string());
                count += 1;
            }
        }
        Ok(count)
    }

    fn cancel_for_market(&self, market_id: &str) -> Result<usize, HorizonError> {
        let mut inner = self.inner.lock().unwrap_or_else(|e| e.into_inner());
        let mut count = 0;
        let indices: Vec<usize> = inner
            .market_index
            .get(market_id)
            .cloned()
            .unwrap_or_default();
        for i in indices {
            let order = &mut inner.orders[i];
            if is_open(order.status) {
                order.status = OrderStatus::Canceled;
                order.status_reason = Some("cancel_market".to_string());
                count += 1;
            }
        }
        Ok(count)
    }

    fn drain_fills(&self) -> Vec<Fill> {
        let mut inner = self.inner.lock().unwrap_or_else(|e| e.into_inner());
        std::mem::take(&mut inner.fills)
    }

    fn name(&self) -> &str {
        "paper"
    }

    fn amend_order(
        &self,
        order_id: &str,
        new_price: Option<f64>,
        new_size: Option<f64>,
        _original_request: &OrderRequest,
    ) -> Result<String, HorizonError> {
        // Validate before mutating
        if let Some(p) = new_price {
            if !p.is_finite() || p < 0.0 {
                return Err(HorizonError::Order(format!(
                    "paper amend: invalid price {}", p
                )));
            }
        }
        if let Some(s) = new_size {
            if !s.is_finite() || s <= 0.0 {
                return Err(HorizonError::Order(format!(
                    "paper amend: invalid size {}", s
                )));
            }
        }
        let mut inner = self.inner.lock().unwrap_or_else(|e| e.into_inner());
        for order in inner.orders.iter_mut() {
            if order.id == order_id && is_open(order.status) {
                if let Some(s) = new_size {
                    if s < order.filled_size {
                        return Err(HorizonError::Order(format!(
                            "paper amend: new size {} < filled_size {}",
                            s, order.filled_size
                        )));
                    }
                }
                if let Some(p) = new_price {
                    order.price = p;
                }
                if let Some(s) = new_size {
                    order.size = s;
                    order.remaining_size = (s - order.filled_size).max(0.0);
                }
                order.amendment_count += 1;
                return Ok(order_id.to_string());
            }
        }
        Err(HorizonError::Order(format!(
            "order not found for amendment: {}",
            order_id
        )))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::types::{OrderSide, OrderType, Side, TimeInForce};

    fn buy_request(market: &str, price: f64, size: f64) -> OrderRequest {
        OrderRequest {
            market_id: market.into(),
            side: Side::Yes,
            order_side: OrderSide::Buy,
            size,
            price,
            order_type: OrderType::Limit,
            time_in_force: TimeInForce::GTC,
            post_only: true,
            token_id: None,
            neg_risk: false,
        }
    }

    #[test]
    fn paper_submit_and_fill() {
        let exchange = PaperExchange::new(0.001);
        let id = exchange
            .submit_order(&buy_request("mkt_1", 0.55, 10.0))
            .unwrap();
        assert!(!id.is_empty());
        assert_eq!(exchange.resting_orders().len(), 1);

        // Price above order -> no fill
        assert_eq!(exchange.tick("mkt_1", 0.60), 0);
        assert_eq!(exchange.resting_orders().len(), 1);

        // Price at order -> fill
        assert_eq!(exchange.tick("mkt_1", 0.55), 1);
        assert_eq!(exchange.resting_orders().len(), 0);

        let fills = exchange.drain_fills();
        assert_eq!(fills.len(), 1);
        assert!((fills[0].price - 0.55).abs() < 1e-10);
    }

    #[test]
    fn paper_cancel() {
        let exchange = PaperExchange::new(0.0);
        let id = exchange
            .submit_order(&buy_request("mkt_1", 0.55, 10.0))
            .unwrap();
        assert_eq!(exchange.resting_orders().len(), 1);

        exchange.cancel_order(&id).unwrap();
        assert_eq!(exchange.resting_orders().len(), 0);
    }

    #[test]
    fn paper_cancel_all() {
        let exchange = PaperExchange::new(0.0);
        exchange
            .submit_order(&buy_request("mkt_1", 0.55, 10.0))
            .unwrap();
        exchange
            .submit_order(&buy_request("mkt_1", 0.50, 5.0))
            .unwrap();
        assert_eq!(exchange.resting_orders().len(), 2);

        let count = exchange.cancel_all().unwrap();
        assert_eq!(count, 2);
        assert_eq!(exchange.resting_orders().len(), 0);
    }

    fn market_buy_request(market: &str, size: f64) -> OrderRequest {
        OrderRequest {
            market_id: market.into(),
            side: Side::Yes,
            order_side: OrderSide::Buy,
            size,
            price: 0.0,
            order_type: OrderType::Market,
            time_in_force: TimeInForce::FOK,
            post_only: false,
            token_id: None,
            neg_risk: false,
        }
    }

    fn fok_buy_request(market: &str, price: f64, size: f64) -> OrderRequest {
        OrderRequest {
            market_id: market.into(),
            side: Side::Yes,
            order_side: OrderSide::Buy,
            size,
            price,
            order_type: OrderType::Limit,
            time_in_force: TimeInForce::FOK,
            post_only: false,
            token_id: None,
            neg_risk: false,
        }
    }

    fn fak_buy_request(market: &str, price: f64, size: f64) -> OrderRequest {
        OrderRequest {
            market_id: market.into(),
            side: Side::Yes,
            order_side: OrderSide::Buy,
            size,
            price,
            order_type: OrderType::Limit,
            time_in_force: TimeInForce::FAK,
            post_only: false,
            token_id: None,
            neg_risk: false,
        }
    }

    #[test]
    fn paper_market_order_fills_at_mid_price() {
        let exchange = PaperExchange::new(0.0);
        exchange
            .submit_order(&market_buy_request("mkt_1", 10.0))
            .unwrap();

        assert_eq!(exchange.tick("mkt_1", 0.60), 1);
        let fills = exchange.drain_fills();
        assert_eq!(fills.len(), 1);
        assert!((fills[0].price - 0.60).abs() < 1e-10, "market order should fill at mid_price");
        assert!((fills[0].size - 10.0).abs() < 1e-10);
    }

    #[test]
    fn paper_fok_cancels_if_no_match() {
        let exchange = PaperExchange::new(0.0);
        // FOK buy at 0.40, but mid_price is 0.60 — won't match
        exchange
            .submit_order(&fok_buy_request("mkt_1", 0.40, 10.0))
            .unwrap();

        assert_eq!(exchange.tick("mkt_1", 0.60), 0);
        assert_eq!(exchange.resting_orders().len(), 0, "FOK should be canceled after tick");
    }

    #[test]
    fn paper_fak_cancels_unfilled_remainder() {
        // With partial_fill_ratio=0.5, FAK should fill 50% then cancel the rest
        let exchange = PaperExchange::with_partial_fill_ratio(0.0, 0.5);
        exchange
            .submit_order(&fak_buy_request("mkt_1", 0.55, 10.0))
            .unwrap();

        // Price matches (0.50 <= 0.55)
        assert_eq!(exchange.tick("mkt_1", 0.50), 1);

        let fills = exchange.drain_fills();
        assert_eq!(fills.len(), 1);
        assert!((fills[0].size - 5.0).abs() < 1e-10, "should fill 50% of 10");

        // FAK remainder should be canceled
        assert_eq!(exchange.resting_orders().len(), 0);
    }

    #[test]
    fn paper_partial_fill_status() {
        let exchange = PaperExchange::with_partial_fill_ratio(0.0, 0.5);
        exchange
            .submit_order(&buy_request("mkt_1", 0.55, 10.0))
            .unwrap();

        // GTC order with partial ratio — fills 50%, stays as PartiallyFilled
        assert_eq!(exchange.tick("mkt_1", 0.50), 1);

        let fills = exchange.drain_fills();
        assert_eq!(fills.len(), 1);
        assert!((fills[0].size - 5.0).abs() < 1e-10);

        // GTC order should still be resting (PartiallyFilled)
        let resting = exchange.resting_orders();
        assert_eq!(resting.len(), 1);
        assert_eq!(resting[0].status, OrderStatus::PartiallyFilled);
        assert!((resting[0].filled_size - 5.0).abs() < 1e-10);
        assert!((resting[0].remaining_size - 5.0).abs() < 1e-10);
    }

    #[test]
    fn paper_partial_fill_completes_over_multiple_ticks() {
        let exchange = PaperExchange::with_partial_fill_ratio(0.0, 0.5);
        exchange
            .submit_order(&buy_request("mkt_1", 0.55, 10.0))
            .unwrap();

        // First tick: fills 50% of 10 = 5
        assert_eq!(exchange.tick("mkt_1", 0.50), 1);
        exchange.drain_fills();

        // Second tick: fills 50% of remaining 5 = 2.5
        assert_eq!(exchange.tick("mkt_1", 0.50), 1);
        let fills = exchange.drain_fills();
        assert_eq!(fills.len(), 1);
        assert!((fills[0].size - 2.5).abs() < 1e-10);

        // Order still partially filled (remaining=2.5)
        let resting = exchange.resting_orders();
        assert_eq!(resting.len(), 1);
        assert_eq!(resting[0].status, OrderStatus::PartiallyFilled);

        // Keep ticking until fully filled (min fill is 0.01)
        // Each tick fills max(remaining*0.5, 0.01), which converges geometrically.
        // Tick until the order is fully filled.
        for _ in 0..100 {
            if exchange.resting_orders().is_empty() {
                break;
            }
            assert_eq!(exchange.tick("mkt_1", 0.50), 1);
            exchange.drain_fills();
        }

        // Should be fully filled now
        assert_eq!(exchange.resting_orders().len(), 0);
    }

    #[test]
    fn paper_market_order_canceled_if_no_tick() {
        // Market order submitted, then tick for a DIFFERENT market — should cancel the market order
        let exchange = PaperExchange::new(0.0);
        exchange
            .submit_order(&market_buy_request("mkt_1", 10.0))
            .unwrap();

        // Tick fills it on matching market
        assert_eq!(exchange.tick("mkt_1", 0.50), 1);
        let fills = exchange.drain_fills();
        assert_eq!(fills.len(), 1);
        // After fill, market order goes Filled then phase 4 doesn't cancel because it's not open anymore
        assert_eq!(exchange.resting_orders().len(), 0);
    }
}
