use crate::types::{ContingentOrder, OrderRequest, OrderSide, OrderType, Side, TimeInForce, TriggerType};
use dashmap::DashMap;
use tracing::debug;

/// Manages contingent orders (stop-loss, take-profit) with OCO linking.
pub struct ContingentManager {
    orders: DashMap<String, ContingentOrder>,
    /// OCO links: id -> partner_id
    oco_links: DashMap<String, String>,
    next_id: std::sync::atomic::AtomicU64,
}

impl ContingentManager {
    pub fn new() -> Self {
        Self {
            orders: DashMap::new(),
            oco_links: DashMap::new(),
            next_id: std::sync::atomic::AtomicU64::new(1),
        }
    }

    fn next_id(&self) -> String {
        let id = self
            .next_id
            .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
        format!("contingent_{}", id)
    }

    /// Add a stop-loss contingent order.
    pub fn add_stop_loss(
        &self,
        market_id: &str,
        side: Side,
        order_side: OrderSide,
        size: f64,
        trigger_price: f64,
        exchange: &str,
    ) -> String {
        let id = self.next_id();
        let order = ContingentOrder {
            id: id.clone(),
            trigger_type: TriggerType::StopLoss,
            market_id: market_id.to_string(),
            side,
            order_side,
            size,
            trigger_price,
            trigger_pnl: None,
            linked_order_id: None,
            exchange: exchange.to_string(),
            triggered: false,
            child_order_id: None,
            token_id: None,
            neg_risk: false,
        };
        self.orders.insert(id.clone(), order);
        debug!(id = %id, market = %market_id, trigger = trigger_price, "stop-loss added");
        id
    }

    /// Add a take-profit contingent order.
    pub fn add_take_profit(
        &self,
        market_id: &str,
        side: Side,
        order_side: OrderSide,
        size: f64,
        trigger_price: f64,
        trigger_pnl: Option<f64>,
        exchange: &str,
    ) -> String {
        let id = self.next_id();
        let order = ContingentOrder {
            id: id.clone(),
            trigger_type: TriggerType::TakeProfit,
            market_id: market_id.to_string(),
            side,
            order_side,
            size,
            trigger_price,
            trigger_pnl,
            linked_order_id: None,
            exchange: exchange.to_string(),
            triggered: false,
            child_order_id: None,
            token_id: None,
            neg_risk: false,
        };
        self.orders.insert(id.clone(), order);
        debug!(id = %id, market = %market_id, trigger = trigger_price, "take-profit added");
        id
    }

    /// Link two contingent orders as OCO (one-cancels-other).
    pub fn link_oco(&self, id_a: &str, id_b: &str) {
        if let Some(mut a) = self.orders.get_mut(id_a) {
            a.linked_order_id = Some(id_b.to_string());
        }
        if let Some(mut b) = self.orders.get_mut(id_b) {
            b.linked_order_id = Some(id_a.to_string());
        }
        self.oco_links
            .insert(id_a.to_string(), id_b.to_string());
        self.oco_links
            .insert(id_b.to_string(), id_a.to_string());
        debug!(id_a = %id_a, id_b = %id_b, "OCO link created");
    }

    /// Check which contingent orders should trigger at the given price.
    /// Marks orders as triggered atomically to prevent double-fire.
    /// Returns list of (contingent_id, OrderRequest, exchange_name).
    pub fn check_triggers(
        &self,
        market_id: &str,
        current_price: f64,
        unrealized_pnl: f64,
    ) -> Vec<(String, OrderRequest, String)> {
        let mut triggered = Vec::new();

        // Collect candidates first (read-only pass)
        let candidates: Vec<String> = self
            .orders
            .iter()
            .filter(|entry| {
                let order = entry.value();
                !order.triggered && order.market_id == market_id
            })
            .filter(|entry| {
                let order = entry.value();
                match order.trigger_type {
                    TriggerType::StopLoss => match order.order_side {
                        OrderSide::Sell => current_price <= order.trigger_price,
                        OrderSide::Buy => current_price >= order.trigger_price,
                    },
                    TriggerType::TakeProfit => {
                        let price_trigger = match order.order_side {
                            OrderSide::Sell => current_price >= order.trigger_price,
                            OrderSide::Buy => current_price <= order.trigger_price,
                        };
                        let pnl_trigger = order
                            .trigger_pnl
                            .map(|tp| unrealized_pnl >= tp)
                            .unwrap_or(false);
                        price_trigger || pnl_trigger
                    }
                }
            })
            .map(|entry| entry.key().clone())
            .collect();

        // Atomically mark each candidate as triggered via get_mut (prevents double-fire)
        for id in candidates {
            if let Some(mut entry) = self.orders.get_mut(&id) {
                if entry.triggered {
                    continue; // Already triggered by concurrent call
                }
                entry.triggered = true;

                let req = OrderRequest {
                    market_id: entry.market_id.clone(),
                    side: entry.side,
                    order_side: entry.order_side,
                    size: entry.size,
                    price: current_price,
                    order_type: OrderType::Market,
                    time_in_force: TimeInForce::FOK,
                    post_only: false,
                    token_id: entry.token_id.clone(),
                    neg_risk: entry.neg_risk,
                };
                triggered.push((entry.id.clone(), req, entry.exchange.clone()));
            }
        }

        triggered
    }

    /// Unmark a contingent order as triggered (for recovery after failed submission).
    pub fn unmark_triggered(&self, id: &str) {
        if let Some(mut entry) = self.orders.get_mut(id) {
            entry.triggered = false;
            entry.child_order_id = None;
        }
    }

    /// Mark a contingent order as triggered and record the child order ID.
    pub fn mark_triggered(&self, id: &str, child_order_id: &str) {
        if let Some(mut entry) = self.orders.get_mut(id) {
            entry.triggered = true;
            entry.child_order_id = Some(child_order_id.to_string());
            debug!(id = %id, child = %child_order_id, "contingent order triggered");
        }
    }

    /// Cancel a contingent order. Returns true if it was pending and got canceled.
    pub fn cancel(&self, id: &str) -> bool {
        if let Some((_, order)) = self.orders.remove(id) {
            if !order.triggered {
                // Also remove OCO links
                if let Some((_, partner_id)) = self.oco_links.remove(id) {
                    // Remove reverse link from partner
                    self.oco_links.remove(&partner_id);
                }
                debug!(id = %id, "contingent order canceled");
                return true;
            }
            // Re-insert if already triggered
            self.orders.insert(id.to_string(), order);
        }
        false
    }

    /// Cancel the OCO partner of a contingent order. Returns the partner ID if canceled.
    pub fn cancel_oco_partner(&self, id: &str) -> Option<String> {
        let partner_id = self.oco_links.get(id)?.value().clone();
        if self.cancel(&partner_id) {
            Some(partner_id)
        } else {
            None
        }
    }

    /// Get all pending (not yet triggered) contingent orders.
    pub fn pending_orders(&self) -> Vec<ContingentOrder> {
        self.orders
            .iter()
            .filter(|e| !e.value().triggered)
            .map(|e| e.value().clone())
            .collect()
    }
}

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

    #[test]
    fn stop_loss_sell_triggers_below_price() {
        let mgr = ContingentManager::new();
        let id = mgr.add_stop_loss("mkt_1", Side::Yes, OrderSide::Sell, 10.0, 0.45, "paper");

        // Price above trigger — no trigger
        let triggered = mgr.check_triggers("mkt_1", 0.50, 0.0);
        assert!(triggered.is_empty());

        // Price at trigger — triggers
        let triggered = mgr.check_triggers("mkt_1", 0.45, 0.0);
        assert_eq!(triggered.len(), 1);
        assert_eq!(triggered[0].0, id);
    }

    #[test]
    fn stop_loss_buy_triggers_above_price() {
        let mgr = ContingentManager::new();
        let id = mgr.add_stop_loss("mkt_1", Side::Yes, OrderSide::Buy, 10.0, 0.60, "paper");

        let triggered = mgr.check_triggers("mkt_1", 0.55, 0.0);
        assert!(triggered.is_empty());

        let triggered = mgr.check_triggers("mkt_1", 0.60, 0.0);
        assert_eq!(triggered.len(), 1);
        assert_eq!(triggered[0].0, id);
    }

    #[test]
    fn take_profit_sell_triggers_above_price() {
        let mgr = ContingentManager::new();
        let id = mgr.add_take_profit("mkt_1", Side::Yes, OrderSide::Sell, 10.0, 0.65, None, "paper");

        let triggered = mgr.check_triggers("mkt_1", 0.60, 0.0);
        assert!(triggered.is_empty());

        let triggered = mgr.check_triggers("mkt_1", 0.65, 0.0);
        assert_eq!(triggered.len(), 1);
        assert_eq!(triggered[0].0, id);
    }

    #[test]
    fn take_profit_triggers_on_pnl() {
        let mgr = ContingentManager::new();
        let id = mgr.add_take_profit("mkt_1", Side::Yes, OrderSide::Sell, 10.0, 0.90, Some(5.0), "paper");

        // Price not reached, PnL not reached
        let triggered = mgr.check_triggers("mkt_1", 0.50, 3.0);
        assert!(triggered.is_empty());

        // PnL threshold reached even though price hasn't
        let triggered = mgr.check_triggers("mkt_1", 0.50, 5.0);
        assert_eq!(triggered.len(), 1);
        assert_eq!(triggered[0].0, id);
    }

    #[test]
    fn oco_cancel() {
        let mgr = ContingentManager::new();
        let sl = mgr.add_stop_loss("mkt_1", Side::Yes, OrderSide::Sell, 10.0, 0.45, "paper");
        let tp = mgr.add_take_profit("mkt_1", Side::Yes, OrderSide::Sell, 10.0, 0.65, None, "paper");
        mgr.link_oco(&sl, &tp);

        // Cancel OCO partner of SL → should cancel TP
        let canceled = mgr.cancel_oco_partner(&sl);
        assert_eq!(canceled, Some(tp.clone()));

        // TP should be gone
        assert_eq!(mgr.pending_orders().len(), 1);
    }

    #[test]
    fn triggered_order_not_retriggered() {
        let mgr = ContingentManager::new();
        let id = mgr.add_stop_loss("mkt_1", Side::Yes, OrderSide::Sell, 10.0, 0.45, "paper");

        let triggered = mgr.check_triggers("mkt_1", 0.40, 0.0);
        assert_eq!(triggered.len(), 1);

        mgr.mark_triggered(&id, "child_123");

        // Should not trigger again
        let triggered = mgr.check_triggers("mkt_1", 0.35, 0.0);
        assert!(triggered.is_empty());
    }

    #[test]
    fn different_market_not_triggered() {
        let mgr = ContingentManager::new();
        mgr.add_stop_loss("mkt_1", Side::Yes, OrderSide::Sell, 10.0, 0.45, "paper");

        let triggered = mgr.check_triggers("mkt_2", 0.40, 0.0);
        assert!(triggered.is_empty());
    }
}
