use crate::types::{Order, OrderRequest, OrderSide, OrderStatus};
use dashmap::DashMap;
use std::sync::atomic::{AtomicU32, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};
use tracing::debug;

/// Tracks all orders and their state transitions.
pub struct OrderManager {
    orders: DashMap<String, Order>,
    /// Cached count of open orders (O(1) instead of O(N) iteration).
    cached_open_count: AtomicU32,
}

impl OrderManager {
    pub fn new() -> Self {
        Self {
            orders: DashMap::new(),
            cached_open_count: AtomicU32::new(0),
        }
    }

    /// Check if an order status is considered "open".
    #[inline]
    fn is_open(status: &OrderStatus) -> bool {
        matches!(
            status,
            OrderStatus::New | OrderStatus::Submitted | OrderStatus::Accepted | OrderStatus::PartiallyFilled
        )
    }

    /// Register a new order from a request, tagged with the target exchange.
    /// Returns the tracking ID.
    #[inline]
    pub fn register_new(&self, request: &OrderRequest, exchange: &str) -> String {
        let id = uuid::Uuid::new_v4().to_string();
        let now = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap_or_default()
            .as_secs_f64();

        let order = Order {
            id: id.clone(),
            market_id: request.market_id.clone(),
            side: request.side,
            order_side: request.order_side,
            price: request.price,
            size: request.size,
            filled_size: 0.0,
            remaining_size: request.size,
            order_type: request.order_type,
            time_in_force: request.time_in_force,
            status: OrderStatus::New,
            created_at: now,
            status_reason: None,
            exchange: exchange.to_string(),
            amendment_count: 0,
            token_id: request.token_id.clone(),
            neg_risk: request.neg_risk,
        };
        self.orders.insert(id.clone(), order);
        self.cached_open_count.fetch_add(1, Ordering::Release);
        id
    }

    /// Transition to Submitted with an exchange-assigned order ID.
    /// Uses remove+mutate+insert to avoid cloning the entire Order.
    ///
    /// SAFETY: The brief window between remove and insert is safe because all
    /// Engine methods holding &self are serialized through Python's GIL —
    /// no concurrent Rust thread reads from OrderManager during this window.
    #[inline]
    pub fn mark_submitted(&self, tracking_id: &str, exchange_id: &str) {
        if let Some((_, mut order)) = self.orders.remove(tracking_id) {
            order.id = exchange_id.to_string();
            order.status = OrderStatus::Submitted;
            self.orders.insert(exchange_id.to_string(), order);
        }
    }

    /// Mark order as accepted.
    pub fn mark_accepted(&self, order_id: &str) {
        if let Some(mut entry) = self.orders.get_mut(order_id) {
            entry.status = OrderStatus::Accepted;
        }
    }

    /// Apply a partial fill.
    pub fn mark_partial_fill(&self, order_id: &str, filled_size: f64, remaining: f64) {
        if let Some(mut entry) = self.orders.get_mut(order_id) {
            entry.filled_size = filled_size;
            entry.remaining_size = remaining;
            entry.status = OrderStatus::PartiallyFilled;
        }
    }

    /// Mark order as fully filled.
    pub fn mark_filled(&self, order_id: &str) {
        if let Some(mut entry) = self.orders.get_mut(order_id) {
            let was_open = Self::is_open(&entry.status);
            entry.filled_size = entry.size;
            entry.remaining_size = 0.0;
            entry.status = OrderStatus::Filled;
            if was_open {
                self.cached_open_count.fetch_sub(1, Ordering::Release);
            }
        }
    }

    /// Apply a fill of a given size to an order.
    /// Automatically determines Filled vs PartiallyFilled status.
    /// For canceled/rejected orders, updates fill accounting but preserves terminal status.
    #[inline]
    pub fn apply_fill_size(&self, order_id: &str, fill_size: f64) {
        if let Some(mut entry) = self.orders.get_mut(order_id) {
            let was_open = Self::is_open(&entry.status);
            entry.filled_size += fill_size;
            entry.remaining_size = (entry.size - entry.filled_size).max(0.0);
            // Don't overwrite terminal states (fill arrived after local cancel)
            if matches!(entry.status, OrderStatus::Canceled | OrderStatus::Rejected) {
                return;
            }
            if entry.remaining_size <= f64::EPSILON {
                entry.status = OrderStatus::Filled;
                if was_open {
                    self.cached_open_count.fetch_sub(1, Ordering::Release);
                }
            } else {
                entry.status = OrderStatus::PartiallyFilled;
            }
        }
    }

    /// Mark order as canceled.
    pub fn mark_canceled(&self, order_id: &str, reason: &str) {
        if let Some(mut entry) = self.orders.get_mut(order_id) {
            let was_open = Self::is_open(&entry.status);
            entry.status = OrderStatus::Canceled;
            entry.status_reason = Some(reason.to_string());
            if was_open {
                self.cached_open_count.fetch_sub(1, Ordering::Release);
            }
        }
    }

    /// Mark order as rejected.
    pub fn mark_rejected(&self, tracking_id: &str, reason: &str) {
        if let Some(mut entry) = self.orders.get_mut(tracking_id) {
            let was_open = Self::is_open(&entry.status);
            entry.status = OrderStatus::Rejected;
            entry.status_reason = Some(reason.to_string());
            if was_open {
                self.cached_open_count.fetch_sub(1, Ordering::Release);
            }
        }
    }

    /// Get all open orders.
    pub fn open_orders(&self) -> Vec<Order> {
        self.orders
            .iter()
            .filter(|e| {
                matches!(
                    e.status,
                    OrderStatus::New
                        | OrderStatus::Submitted
                        | OrderStatus::Accepted
                        | OrderStatus::PartiallyFilled
                )
            })
            .map(|e| e.value().clone())
            .collect()
    }

    /// Get all open orders for a specific market.
    pub fn open_orders_for_market(&self, market_id: &str) -> Vec<Order> {
        self.orders
            .iter()
            .filter(|e| {
                e.market_id == market_id
                    && matches!(
                        e.status,
                        OrderStatus::New
                            | OrderStatus::Submitted
                            | OrderStatus::Accepted
                            | OrderStatus::PartiallyFilled
                    )
            })
            .map(|e| e.value().clone())
            .collect()
    }

    /// Get order by ID.
    pub fn get(&self, order_id: &str) -> Option<Order> {
        self.orders.get(order_id).map(|e| e.value().clone())
    }

    /// Count of open orders (O(1) from cache).
    pub fn open_count(&self) -> u32 {
        self.cached_open_count.load(Ordering::Acquire)
    }

    /// Get all orders for a given market matching a side and order_side (for fill matching).
    pub fn find_order(&self, order_id: &str) -> Option<(OrderSide, String)> {
        self.orders
            .get(order_id)
            .map(|e| (e.order_side, e.market_id.clone()))
    }

    /// Amend an order. Handles both in-place (same ID) and cancel+resubmit (new ID) cases.
    /// Returns the final order ID.
    pub fn amend_order(
        &self,
        old_id: &str,
        new_id: &str,
        new_price: Option<f64>,
        new_size: Option<f64>,
    ) {
        if old_id == new_id {
            // In-place amendment (paper exchange)
            if let Some(mut entry) = self.orders.get_mut(old_id) {
                if let Some(p) = new_price {
                    entry.price = p;
                }
                if let Some(s) = new_size {
                    entry.size = s;
                    entry.remaining_size = (s - entry.filled_size).max(0.0);
                }
                entry.amendment_count += 1;
                debug!(order_id = %old_id, "order amended in-place");
            }
        } else {
            // Cancel + resubmit (live exchanges): old order canceled, new order tracked
            if let Some(old_entry) = self.orders.get(old_id) {
                let mut new_order = old_entry.clone();
                new_order.id = new_id.to_string();
                new_order.amendment_count += 1;
                if let Some(p) = new_price {
                    new_order.price = p;
                }
                if let Some(s) = new_size {
                    new_order.size = s;
                    new_order.remaining_size = s;
                    new_order.filled_size = 0.0;
                }
                drop(old_entry);
                self.orders.insert(new_id.to_string(), new_order);
                // Mark old order as canceled and update count
                if let Some(mut entry) = self.orders.get_mut(old_id) {
                    let was_open = Self::is_open(&entry.status);
                    entry.status = OrderStatus::Canceled;
                    entry.status_reason = Some("amended".to_string());
                    if !was_open {
                        // Old was already terminal but new order is open — net +1
                        self.cached_open_count.fetch_add(1, Ordering::Release);
                    }
                    // If old was open: old -1 + new +1 = net 0 (no change needed)
                }
                debug!(old_id = %old_id, new_id = %new_id, "order amended via cancel+resubmit");
            }
        }
    }

    /// Evict terminal orders (Filled/Canceled/Rejected) older than `max_age_secs`.
    /// Returns the number of orders evicted.
    pub fn evict_terminal(&self, max_age_secs: f64) -> u32 {
        let now = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap_or_default()
            .as_secs_f64();
        let cutoff = now - max_age_secs;

        let to_remove: Vec<String> = self
            .orders
            .iter()
            .filter(|e| {
                matches!(
                    e.status,
                    OrderStatus::Filled | OrderStatus::Canceled | OrderStatus::Rejected
                ) && e.created_at < cutoff
            })
            .map(|e| e.key().clone())
            .collect();

        let count = to_remove.len() as u32;
        for key in to_remove {
            self.orders.remove(&key);
        }
        count
    }
}

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

    fn test_request() -> OrderRequest {
        OrderRequest {
            market_id: "mkt_1".into(),
            side: Side::Yes,
            order_side: OrderSide::Buy,
            size: 10.0,
            price: 0.55,
            order_type: OrderType::Limit,
            time_in_force: TimeInForce::GTC,
            post_only: true,
            token_id: None,
            neg_risk: false,
        }
    }

    #[test]
    fn order_lifecycle() {
        let mgr = OrderManager::new();
        let tracking_id = mgr.register_new(&test_request(), "paper");
        assert_eq!(mgr.open_count(), 1);

        mgr.mark_submitted(&tracking_id, "exch_123");
        let order = mgr.get("exch_123").unwrap();
        assert_eq!(order.status, OrderStatus::Submitted);
        assert_eq!(order.exchange, "paper");

        mgr.mark_filled("exch_123");
        let order = mgr.get("exch_123").unwrap();
        assert_eq!(order.status, OrderStatus::Filled);
        assert_eq!(mgr.open_count(), 0);
    }

    #[test]
    fn cancel_order() {
        let mgr = OrderManager::new();
        let tracking_id = mgr.register_new(&test_request(), "paper");
        mgr.mark_submitted(&tracking_id, "exch_456");
        mgr.mark_canceled("exch_456", "user requested");

        let order = mgr.get("exch_456").unwrap();
        assert_eq!(order.status, OrderStatus::Canceled);
        assert_eq!(mgr.open_count(), 0);
    }

    #[test]
    fn apply_fill_size_partial() {
        let mgr = OrderManager::new();
        let tracking_id = mgr.register_new(&test_request(), "paper");
        mgr.mark_submitted(&tracking_id, "exch_789");

        // Partially fill 5 of 10
        mgr.apply_fill_size("exch_789", 5.0);
        let order = mgr.get("exch_789").unwrap();
        assert_eq!(order.status, OrderStatus::PartiallyFilled);
        assert!((order.filled_size - 5.0).abs() < 1e-10);
        assert!((order.remaining_size - 5.0).abs() < 1e-10);
        assert_eq!(mgr.open_count(), 1); // still open
    }

    #[test]
    fn apply_fill_size_full() {
        let mgr = OrderManager::new();
        let tracking_id = mgr.register_new(&test_request(), "paper");
        mgr.mark_submitted(&tracking_id, "exch_full");

        // Partially fill 5 of 10
        mgr.apply_fill_size("exch_full", 5.0);
        assert_eq!(mgr.get("exch_full").unwrap().status, OrderStatus::PartiallyFilled);

        // Fill remaining 5
        mgr.apply_fill_size("exch_full", 5.0);
        let order = mgr.get("exch_full").unwrap();
        assert_eq!(order.status, OrderStatus::Filled);
        assert!((order.filled_size - 10.0).abs() < 1e-10);
        assert!(order.remaining_size < 1e-10);
        assert_eq!(mgr.open_count(), 0); // fully filled
    }
}
