//! Simulated broker for paper trading.

use ml_ensemble::signal::{TradeAction, TradeSignal};

/// A simulated fill from the paper broker.
#[derive(Debug, Clone)]
pub struct Fill {
    pub symbol: String,
    pub side: TradeAction,
    pub quantity: i64,
    pub fill_price: f64,
    pub commission: f64,
    pub timestamp_us: i64,
}

/// Paper broker that simulates order fills against market prices.
#[derive(Debug)]
pub struct PaperBroker {
    cash: f64,
    initial_cash: f64,
    position: i64, // signed: positive = long, negative = short
    avg_entry_price: f64,
    realized_pnl: f64,
    slippage_bps: f64,
    commission_per_contract: f64,
    fills: Vec<Fill>,
}

impl PaperBroker {
    pub fn new(initial_cash: f64, slippage_bps: f64) -> Self {
        Self {
            cash: initial_cash,
            initial_cash,
            position: 0,
            avg_entry_price: 0.0,
            realized_pnl: 0.0,
            slippage_bps,
            commission_per_contract: 2.50,
            fills: Vec::new(),
        }
    }

    /// Execute a trade signal at the given market price.
    /// Returns a Fill if the signal is actionable (Buy or Sell).
    pub fn execute_signal(&mut self, signal: &TradeSignal, market_price: f64) -> Option<Fill> {
        match signal.action {
            TradeAction::Hold => None,
            TradeAction::Buy => {
                let slippage = market_price * self.slippage_bps;
                let fill_price = market_price + slippage;
                let quantity = 1_i64;

                // Close short or open long
                if self.position < 0 {
                    // Closing short: realized P&L
                    let pnl = (self.avg_entry_price - fill_price) * (-self.position) as f64;
                    self.realized_pnl += pnl;
                    self.position = 0;
                    self.avg_entry_price = 0.0;
                } else {
                    // Opening/adding long
                    let total_cost =
                        self.avg_entry_price * self.position as f64 + fill_price;
                    self.position += quantity;
                    if self.position > 0 {
                        self.avg_entry_price = total_cost / self.position as f64;
                    }
                }

                self.cash -= self.commission_per_contract;

                let fill = Fill {
                    symbol: signal.symbol.clone(),
                    side: TradeAction::Buy,
                    quantity,
                    fill_price,
                    commission: self.commission_per_contract,
                    timestamp_us: signal.timestamp_us,
                };
                self.fills.push(fill.clone());
                Some(fill)
            }
            TradeAction::Sell => {
                let slippage = market_price * self.slippage_bps;
                let fill_price = market_price - slippage;
                let quantity = 1_i64;

                // Close long or open short
                if self.position > 0 {
                    let pnl = (fill_price - self.avg_entry_price) * self.position as f64;
                    self.realized_pnl += pnl;
                    self.position = 0;
                    self.avg_entry_price = 0.0;
                } else {
                    let total_cost =
                        self.avg_entry_price * (-self.position) as f64 + fill_price;
                    self.position -= quantity;
                    if self.position < 0 {
                        self.avg_entry_price = total_cost / (-self.position) as f64;
                    }
                }

                self.cash -= self.commission_per_contract;

                let fill = Fill {
                    symbol: signal.symbol.clone(),
                    side: TradeAction::Sell,
                    quantity,
                    fill_price,
                    commission: self.commission_per_contract,
                    timestamp_us: signal.timestamp_us,
                };
                self.fills.push(fill.clone());
                Some(fill)
            }
        }
    }

    pub fn position_size(&self) -> i64 {
        self.position
    }

    pub fn unrealized_pnl(&self, current_price: f64) -> f64 {
        if self.position > 0 {
            (current_price - self.avg_entry_price) * self.position as f64
        } else if self.position < 0 {
            (self.avg_entry_price - current_price) * (-self.position) as f64
        } else {
            0.0
        }
    }

    pub fn realized_pnl(&self) -> f64 {
        self.realized_pnl
    }

    pub fn total_pnl(&self, current_price: f64) -> f64 {
        self.realized_pnl + self.unrealized_pnl(current_price)
            - (self.fills.len() as f64 * self.commission_per_contract)
            + self.cash
            - self.initial_cash
            + (self.fills.len() as f64 * self.commission_per_contract)
    }

    pub fn equity(&self, current_price: f64) -> f64 {
        self.cash + self.unrealized_pnl(current_price)
    }

    pub fn num_trades(&self) -> usize {
        self.fills.len()
    }
}

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

    fn make_signal(action: TradeAction, symbol: &str) -> TradeSignal {
        TradeSignal {
            action,
            symbol: symbol.to_string(),
            confidence: 0.9,
            direction: match action {
                TradeAction::Buy => 0.5,
                TradeAction::Sell => -0.5,
                TradeAction::Hold => 0.0,
            },
            timestamp_us: 0,
        }
    }

    #[test]
    fn test_buy_and_position() {
        let mut broker = PaperBroker::new(100_000.0, 0.0001);
        let signal = make_signal(TradeAction::Buy, "6E.FUT");
        let fill = broker.execute_signal(&signal, 1.0850);
        assert!(fill.is_some());
        assert_eq!(broker.position_size(), 1);
        let f = fill.as_ref().map(|f| f.fill_price > 1.0850);
        assert_eq!(f, Some(true)); // slippage applied
    }

    #[test]
    fn test_hold_no_fill() {
        let mut broker = PaperBroker::new(100_000.0, 0.0001);
        let signal = make_signal(TradeAction::Hold, "6E.FUT");
        let fill = broker.execute_signal(&signal, 1.0850);
        assert!(fill.is_none());
        assert_eq!(broker.position_size(), 0);
    }

    #[test]
    fn test_buy_then_sell_roundtrip() {
        let mut broker = PaperBroker::new(100_000.0, 0.0001);
        let buy = make_signal(TradeAction::Buy, "6E.FUT");
        broker.execute_signal(&buy, 1.0850);
        assert_eq!(broker.position_size(), 1);

        let sell = make_signal(TradeAction::Sell, "6E.FUT");
        broker.execute_signal(&sell, 1.0860);
        assert_eq!(broker.position_size(), 0);
        // Should have some realized P&L (positive since we bought low, sold high)
        assert!(broker.realized_pnl() > 0.0);
    }

    #[test]
    fn test_unrealized_pnl() {
        let mut broker = PaperBroker::new(100_000.0, 0.0001);
        let buy = make_signal(TradeAction::Buy, "6E.FUT");
        broker.execute_signal(&buy, 1.0850);
        // Price went up
        let upnl = broker.unrealized_pnl(1.0900);
        assert!(upnl > 0.0);
    }
}
