use crate::portfolio_tracker::{PortfolioTracker, TradeAction};
use rand::rngs::StdRng;
use rand::{Rng, SeedableRng};

/// Result of a trade execution attempt
#[derive(Debug, Clone, PartialEq)]
pub enum ExecutionResult {
    /// Trade executed successfully with actual fill price and quantity
    Executed {
        action: TradeAction,
        fill_price: f64,
        fill_quantity: f64,
        slippage: f64,
        latency_ms: f64,
    },
    /// Trade rejected with reason
    Rejected {
        action: TradeAction,
        reason: RejectionReason,
    },
}

/// Reasons for trade rejection
#[derive(Debug, Clone, PartialEq)]
pub enum RejectionReason {
    /// Position limit exceeded
    PositionLimit {
        current: f64,
        limit: f64,
        attempted: TradeAction,
    },
    /// Insufficient margin
    InsufficientMargin { required: f64, available: f64 },
    /// Maximum drawdown reached
    MaxDrawdown { current_dd: f64, max_dd: f64 },
    /// Maximum loss per trade exceeded
    MaxLossPerTrade { potential_loss: f64, max_loss: f64 },
}

/// Risk control configuration
#[derive(Debug, Clone)]
pub struct RiskControlConfig {
    /// Maximum position size (contracts)
    pub max_position: f64,
    /// Margin requirement per contract
    pub margin_per_contract: f64,
    /// Maximum drawdown allowed (fraction of initial capital)
    pub max_drawdown: f64,
    /// Maximum loss per trade (fraction of account value)
    pub max_loss_per_trade: f64,
}

impl Default for RiskControlConfig {
    fn default() -> Self {
        Self {
            max_position: 10.0,
            margin_per_contract: 100.0,
            max_drawdown: 0.20,       // 20% max drawdown
            max_loss_per_trade: 0.02, // 2% per trade
        }
    }
}

/// Execution cost configuration
#[derive(Debug, Clone)]
pub struct ExecutionCostConfig {
    /// Slippage range (min, max) in price units
    pub slippage_range: (f64, f64),
    /// Latency range (min, max) in milliseconds
    pub latency_range: (f64, f64),
    /// Partial fill probability (0.0 - 1.0)
    pub partial_fill_prob: f64,
    /// Partial fill ratio when triggered (min, max)
    pub partial_fill_ratio: (f64, f64),
}

impl Default for ExecutionCostConfig {
    fn default() -> Self {
        Self {
            slippage_range: (0.0, 0.05),    // 0-0.05 price units
            latency_range: (0.5, 2.0),      // 0.5-2.0 ms
            partial_fill_prob: 0.1,         // 10% chance of partial fill
            partial_fill_ratio: (0.5, 0.9), // 50-90% fill
        }
    }
}

/// Trade executor with risk controls and execution simulation
#[derive(Debug)]
pub struct TradeExecutor {
    /// Wrapped portfolio tracker
    portfolio: PortfolioTracker,
    /// Risk control configuration
    risk_config: RiskControlConfig,
    /// Execution cost configuration
    cost_config: ExecutionCostConfig,
    /// Random number generator
    rng: StdRng,
    /// Initial account value for drawdown calculation
    initial_value: f64,
    /// Peak account value for drawdown calculation
    peak_value: f64,
}

impl TradeExecutor {
    /// Create a new trade executor with default configurations
    pub fn new(initial_capital: f64) -> Self {
        Self::with_configs(
            initial_capital,
            RiskControlConfig::default(),
            ExecutionCostConfig::default(),
        )
    }

    /// Create a new trade executor with custom configurations
    pub fn with_configs(
        initial_capital: f64,
        risk_config: RiskControlConfig,
        cost_config: ExecutionCostConfig,
    ) -> Self {
        Self {
            portfolio: PortfolioTracker::with_default_spread(initial_capital as f32),
            risk_config,
            cost_config,
            rng: StdRng::from_entropy(),
            initial_value: initial_capital,
            peak_value: initial_capital,
        }
    }

    /// Create a new trade executor with a specific RNG seed for deterministic testing
    pub fn with_seed(
        initial_capital: f64,
        risk_config: RiskControlConfig,
        cost_config: ExecutionCostConfig,
        seed: u64,
    ) -> Self {
        Self {
            portfolio: PortfolioTracker::with_default_spread(initial_capital as f32),
            risk_config,
            cost_config,
            rng: StdRng::seed_from_u64(seed),
            initial_value: initial_capital,
            peak_value: initial_capital,
        }
    }

    /// Execute a trade with risk controls and execution simulation
    pub fn execute_trade(&mut self, action: TradeAction, current_price: f64) -> ExecutionResult {
        // Risk control checks
        if let Some(rejection) = self.check_risk_controls(&action, current_price) {
            return rejection;
        }

        // Simulate execution costs
        let (fill_price, fill_quantity, slippage, latency_ms) =
            self.simulate_execution(&action, current_price);

        // Execute the trade on the portfolio tracker
        let actual_action = match action {
            TradeAction::Buy(_qty) => TradeAction::Buy(fill_quantity),
            TradeAction::Sell(_qty) => TradeAction::Sell(fill_quantity),
            TradeAction::Hold => TradeAction::Hold,
        };

        self.portfolio.execute_trade(actual_action, fill_price);

        // Update peak value for drawdown tracking
        let current_value = self.portfolio.total_value_cached() as f64;
        if current_value > self.peak_value {
            self.peak_value = current_value;
        }

        ExecutionResult::Executed {
            action: actual_action,
            fill_price,
            fill_quantity,
            slippage,
            latency_ms,
        }
    }

    /// Check all risk control rules
    fn check_risk_controls(
        &self,
        action: &TradeAction,
        current_price: f64,
    ) -> Option<ExecutionResult> {
        // Check position limit
        if let Some(rejection) = self.check_position_limit(action) {
            return Some(rejection);
        }

        // Check margin requirement
        if let Some(rejection) = self.check_margin_requirement(action, current_price) {
            return Some(rejection);
        }

        // Check drawdown limit
        if let Some(rejection) = self.check_drawdown_limit() {
            return Some(rejection);
        }

        // Check max loss per trade
        if let Some(rejection) = self.check_max_loss_per_trade(action, current_price) {
            return Some(rejection);
        }

        None
    }

    /// Check position limit constraint
    fn check_position_limit(&self, action: &TradeAction) -> Option<ExecutionResult> {
        let current_position = self.portfolio.current_position() as f64;
        let new_position = match action {
            TradeAction::Buy(qty) => current_position + qty,
            TradeAction::Sell(qty) => current_position - qty,
            TradeAction::Hold => return None,
        };

        if new_position.abs() > self.risk_config.max_position {
            return Some(ExecutionResult::Rejected {
                action: *action,
                reason: RejectionReason::PositionLimit {
                    current: current_position,
                    limit: self.risk_config.max_position,
                    attempted: *action,
                },
            });
        }

        None
    }

    /// Check margin requirement constraint
    fn check_margin_requirement(
        &self,
        action: &TradeAction,
        _current_price: f64,
    ) -> Option<ExecutionResult> {
        let quantity = match action {
            TradeAction::Buy(qty) | TradeAction::Sell(qty) => *qty,
            TradeAction::Hold => return None,
        };

        let required_margin = quantity * self.risk_config.margin_per_contract;
        let available_cash = self.portfolio.cash_balance() as f64;

        if required_margin > available_cash {
            return Some(ExecutionResult::Rejected {
                action: *action,
                reason: RejectionReason::InsufficientMargin {
                    required: required_margin,
                    available: available_cash,
                },
            });
        }

        None
    }

    /// Check drawdown limit constraint
    fn check_drawdown_limit(&self) -> Option<ExecutionResult> {
        let current_value = self.portfolio.total_value_cached() as f64;
        let drawdown = (self.peak_value - current_value) / self.peak_value;

        if drawdown > self.risk_config.max_drawdown {
            return Some(ExecutionResult::Rejected {
                action: TradeAction::Hold,
                reason: RejectionReason::MaxDrawdown {
                    current_dd: drawdown,
                    max_dd: self.risk_config.max_drawdown,
                },
            });
        }

        None
    }

    /// Check maximum loss per trade constraint
    fn check_max_loss_per_trade(
        &self,
        action: &TradeAction,
        current_price: f64,
    ) -> Option<ExecutionResult> {
        let quantity = match action {
            TradeAction::Buy(qty) | TradeAction::Sell(qty) => *qty,
            TradeAction::Hold => return None,
        };

        let potential_loss = quantity * current_price * self.risk_config.max_loss_per_trade;
        let max_loss =
            (self.portfolio.total_value_cached() as f64) * self.risk_config.max_loss_per_trade;

        if potential_loss > max_loss {
            return Some(ExecutionResult::Rejected {
                action: *action,
                reason: RejectionReason::MaxLossPerTrade {
                    potential_loss,
                    max_loss,
                },
            });
        }

        None
    }

    /// Simulate execution costs (slippage, latency, partial fills)
    fn simulate_execution(
        &mut self,
        action: &TradeAction,
        current_price: f64,
    ) -> (f64, f64, f64, f64) {
        let quantity = match action {
            TradeAction::Buy(qty) | TradeAction::Sell(qty) => *qty,
            TradeAction::Hold => return (current_price, 0.0, 0.0, 0.0),
        };

        // Simulate slippage
        let slippage = self
            .rng
            .gen_range(self.cost_config.slippage_range.0..=self.cost_config.slippage_range.1);
        let fill_price = match action {
            TradeAction::Buy(_) => current_price + slippage, // Pay more when buying
            TradeAction::Sell(_) => current_price - slippage, // Receive less when selling
            TradeAction::Hold => current_price,
        };

        // Simulate latency
        let latency_ms = self
            .rng
            .gen_range(self.cost_config.latency_range.0..=self.cost_config.latency_range.1);

        // Simulate partial fills
        let fill_quantity = if self.rng.gen::<f64>() < self.cost_config.partial_fill_prob {
            let fill_ratio = self.rng.gen_range(
                self.cost_config.partial_fill_ratio.0..=self.cost_config.partial_fill_ratio.1,
            );
            quantity * fill_ratio
        } else {
            quantity
        };

        (fill_price, fill_quantity, slippage, latency_ms)
    }

    /// Get current portfolio position
    pub fn current_position(&self) -> f64 {
        self.portfolio.current_position() as f64
    }

    /// Get current cash balance
    pub fn cash_balance(&self) -> f64 {
        self.portfolio.cash_balance() as f64
    }

    /// Get total portfolio value
    pub fn total_value(&self) -> f64 {
        self.portfolio.total_value_cached() as f64
    }

    /// Get realized profit/loss
    pub fn realized_pnl(&self) -> f64 {
        self.portfolio.realized_pnl() as f64
    }

    /// Get unrealized profit/loss
    pub fn unrealized_pnl(&self) -> f64 {
        self.portfolio.unrealized_pnl_cached() as f64
    }

    /// Get average entry price
    pub fn average_entry_price(&self) -> f64 {
        self.portfolio.average_entry_price() as f64
    }

    /// Get current drawdown
    pub fn current_drawdown(&self) -> f64 {
        let current_value = self.portfolio.total_value_cached() as f64;
        (self.peak_value - current_value) / self.peak_value
    }

    /// Reset the executor to initial state
    pub fn reset(&mut self) {
        self.portfolio = PortfolioTracker::with_default_spread(self.initial_value as f32);
        self.peak_value = self.initial_value;
    }
}

#[cfg(test)]
#[allow(
    clippy::wildcard_enum_match_arm,
    clippy::manual_range_contains,
    clippy::redundant_clone
)]
mod tests {
    use super::*;

    #[test]
    fn test_successful_execution() {
        let mut executor = TradeExecutor::with_seed(
            10000.0,
            RiskControlConfig::default(),
            ExecutionCostConfig {
                slippage_range: (0.01, 0.02),
                latency_range: (1.0, 1.5),
                partial_fill_prob: 0.0,
                partial_fill_ratio: (1.0, 1.0),
            },
            42,
        );

        let result = executor.execute_trade(TradeAction::Buy(1.0), 100.0);
        match result {
            ExecutionResult::Executed {
                fill_price,
                fill_quantity,
                slippage,
                latency_ms,
                ..
            } => {
                assert!(fill_price > 100.0); // Should have positive slippage for buy
                assert_eq!(fill_quantity, 1.0);
                assert!(slippage >= 0.01 && slippage <= 0.02);
                assert!(latency_ms >= 1.0 && latency_ms <= 1.5);
            },
            ExecutionResult::Rejected { .. } => panic!("Trade should not be rejected"),
        }
    }

    #[test]
    fn test_position_limit_rejection() {
        let risk_config = RiskControlConfig {
            max_position: 5.0,
            ..Default::default()
        };
        let mut executor =
            TradeExecutor::with_seed(10000.0, risk_config, ExecutionCostConfig::default(), 42);

        // Execute trades up to position limit
        executor.execute_trade(TradeAction::Buy(3.0), 100.0);

        // This should be rejected (3 + 3 = 6 > 5)
        let result = executor.execute_trade(TradeAction::Buy(3.0), 100.0);
        match result {
            ExecutionResult::Rejected {
                reason: RejectionReason::PositionLimit { current, limit, .. },
                ..
            } => {
                assert_eq!(current, 3.0);
                assert_eq!(limit, 5.0);
            },
            _ => panic!("Trade should be rejected due to position limit"),
        }
    }

    #[test]
    fn test_margin_requirement_rejection() {
        let risk_config = RiskControlConfig {
            margin_per_contract: 1000.0,
            ..Default::default()
        };
        let mut executor =
            TradeExecutor::with_seed(2000.0, risk_config, ExecutionCostConfig::default(), 42);

        // This should be rejected (5 * 1000 = 5000 > 2000)
        let result = executor.execute_trade(TradeAction::Buy(5.0), 100.0);
        match result {
            ExecutionResult::Rejected {
                reason:
                    RejectionReason::InsufficientMargin {
                        required,
                        available,
                    },
                ..
            } => {
                assert_eq!(required, 5000.0);
                assert_eq!(available, 2000.0);
            },
            _ => panic!("Trade should be rejected due to insufficient margin"),
        }
    }

    #[test]
    fn test_drawdown_limit_rejection() {
        let risk_config = RiskControlConfig {
            max_drawdown: 0.20,
            margin_per_contract: 10.0,
            max_position: 100.0,
            ..Default::default()
        };
        let mut executor = TradeExecutor::with_seed(
            10000.0,
            risk_config,
            ExecutionCostConfig {
                slippage_range: (0.0, 0.0),
                latency_range: (1.0, 1.0),
                partial_fill_prob: 0.0,
                partial_fill_ratio: (1.0, 1.0),
            },
            42,
        );

        // Create a large loss to trigger drawdown limit (> 20%)
        // Strategy: Buy smaller position, then close at lower price to create loss
        // Buy 50 contracts at 100.0 = spend 5,000 (keep 5,000 cash)
        // Sell 50 contracts (close) at 50.0 = loss of 2,500 (25% of initial capital)
        // Portfolio value: 10,000 - 2,500 = 7,500 (25% drawdown)
        executor.execute_trade(TradeAction::Buy(50.0), 100.0);
        executor.execute_trade(TradeAction::Sell(50.0), 50.0);

        // Check that drawdown exceeds limit
        let drawdown = executor.current_drawdown();
        assert!(
            drawdown > 0.20,
            "Expected drawdown > 0.20, got {}",
            drawdown
        );

        // Next trade should be rejected due to max drawdown
        let result = executor.execute_trade(TradeAction::Buy(1.0), 50.0);
        match result {
            ExecutionResult::Rejected {
                reason: RejectionReason::MaxDrawdown { current_dd, max_dd },
                ..
            } => {
                assert!(
                    current_dd > 0.20,
                    "Expected current_dd > 0.20, got {}",
                    current_dd
                );
                assert_eq!(max_dd, 0.20);
            },
            _ => panic!("Trade should be rejected due to max drawdown"),
        }
    }

    #[test]
    fn test_max_loss_per_trade_rejection() {
        let risk_config = RiskControlConfig {
            max_loss_per_trade: 0.02, // 2% max loss per trade
            margin_per_contract: 10.0,
            max_position: 1000.0, // Increase position limit to allow test trade
            ..Default::default()
        };
        let mut executor =
            TradeExecutor::with_seed(10000.0, risk_config, ExecutionCostConfig::default(), 42);

        // Attempt a trade that violates max_loss_per_trade constraint
        // Portfolio value: 10,000
        // Max loss allowed: 10,000 * 0.02 = 200
        // Trade: Buy 200 contracts at 100.0
        // Potential loss: 200 * 100 * 0.02 = 400
        // Since 400 > 200, this should be rejected
        let result = executor.execute_trade(TradeAction::Buy(200.0), 100.0);
        match result {
            ExecutionResult::Rejected {
                reason:
                    RejectionReason::MaxLossPerTrade {
                        potential_loss,
                        max_loss,
                    },
                ..
            } => {
                assert_eq!(potential_loss, 400.0);
                assert_eq!(max_loss, 200.0);
            },
            other => panic!(
                "Trade should be rejected due to max loss per trade, got: {:?}",
                other
            ),
        }
    }

    #[test]
    fn test_partial_fill_simulation() {
        let mut executor = TradeExecutor::with_seed(
            10000.0,
            RiskControlConfig::default(),
            ExecutionCostConfig {
                slippage_range: (0.0, 0.0),
                latency_range: (1.0, 1.0),
                partial_fill_prob: 1.0,         // Always partial fill
                partial_fill_ratio: (0.5, 0.5), // Always 50% fill
            },
            42,
        );

        let result = executor.execute_trade(TradeAction::Buy(10.0), 100.0);
        match result {
            ExecutionResult::Executed { fill_quantity, .. } => {
                assert_eq!(fill_quantity, 5.0); // 50% of 10.0
            },
            ExecutionResult::Rejected { .. } => panic!("Trade should not be rejected"),
        }
    }

    #[test]
    fn test_slippage_direction() {
        let mut executor = TradeExecutor::with_seed(
            10000.0,
            RiskControlConfig::default(),
            ExecutionCostConfig {
                slippage_range: (0.05, 0.05),
                latency_range: (1.0, 1.0),
                partial_fill_prob: 0.0,
                partial_fill_ratio: (1.0, 1.0),
            },
            42,
        );

        // Buy should have positive slippage (pay more)
        let result = executor.execute_trade(TradeAction::Buy(1.0), 100.0);
        match result {
            ExecutionResult::Executed { fill_price, .. } => {
                assert_eq!(fill_price, 100.05);
            },
            _ => panic!("Trade should not be rejected"),
        }

        // Sell should have negative slippage (receive less)
        let result = executor.execute_trade(TradeAction::Sell(1.0), 100.0);
        match result {
            ExecutionResult::Executed { fill_price, .. } => {
                assert_eq!(fill_price, 99.95);
            },
            _ => panic!("Trade should not be rejected"),
        }
    }

    #[test]
    fn test_portfolio_delegation() {
        let mut executor = TradeExecutor::with_seed(
            10000.0,
            RiskControlConfig::default(),
            ExecutionCostConfig {
                slippage_range: (0.0, 0.0),
                latency_range: (1.0, 1.0),
                partial_fill_prob: 0.0,
                partial_fill_ratio: (1.0, 1.0),
            },
            42,
        );

        assert_eq!(executor.current_position(), 0.0);
        assert_eq!(executor.cash_balance(), 10000.0);
        assert_eq!(executor.total_value(), 10000.0);

        executor.execute_trade(TradeAction::Buy(5.0), 100.0);

        assert_eq!(executor.current_position(), 5.0);
        assert!(executor.cash_balance() < 10000.0);
    }

    #[test]
    fn test_drawdown_tracking() {
        let mut executor = TradeExecutor::with_seed(
            10000.0,
            RiskControlConfig {
                margin_per_contract: 10.0,
                ..Default::default()
            },
            ExecutionCostConfig {
                slippage_range: (0.0, 0.0),
                latency_range: (1.0, 1.0),
                partial_fill_prob: 0.0,
                partial_fill_ratio: (1.0, 1.0),
            },
            42,
        );

        assert_eq!(executor.current_drawdown(), 0.0);

        // Create a profitable trade (peak should increase)
        executor.execute_trade(TradeAction::Buy(10.0), 100.0);
        executor.execute_trade(TradeAction::Sell(10.0), 110.0);

        assert_eq!(executor.current_drawdown(), 0.0);

        // Create a losing trade (drawdown should increase)
        executor.execute_trade(TradeAction::Buy(10.0), 110.0);
        executor.execute_trade(TradeAction::Sell(10.0), 100.0);

        assert!(executor.current_drawdown() > 0.0);
    }

    #[test]
    fn test_reset() {
        let mut executor = TradeExecutor::with_seed(
            10000.0,
            RiskControlConfig::default(),
            ExecutionCostConfig {
                slippage_range: (0.0, 0.0),
                latency_range: (1.0, 1.0),
                partial_fill_prob: 0.0,
                partial_fill_ratio: (1.0, 1.0),
            },
            42,
        );

        executor.execute_trade(TradeAction::Buy(5.0), 100.0);
        assert_ne!(executor.current_position(), 0.0);

        executor.reset();
        assert_eq!(executor.current_position(), 0.0);
        assert_eq!(executor.cash_balance(), 10000.0);
        assert_eq!(executor.total_value(), 10000.0);
        assert_eq!(executor.current_drawdown(), 0.0);
    }

    #[test]
    fn test_hold_action() {
        let mut executor = TradeExecutor::with_seed(
            10000.0,
            RiskControlConfig::default(),
            ExecutionCostConfig::default(),
            42,
        );

        let result = executor.execute_trade(TradeAction::Hold, 100.0);
        match result {
            ExecutionResult::Executed {
                fill_quantity,
                slippage,
                ..
            } => {
                assert_eq!(fill_quantity, 0.0);
                assert_eq!(slippage, 0.0);
            },
            _ => panic!("Hold should always execute"),
        }
    }

    #[test]
    fn test_default_configs() {
        let executor = TradeExecutor::new(10000.0);
        assert_eq!(executor.initial_value, 10000.0);
        assert_eq!(executor.peak_value, 10000.0);
    }

    #[test]
    fn test_execution_result_equality() {
        let result1 = ExecutionResult::Executed {
            action: TradeAction::Buy(1.0),
            fill_price: 100.0,
            fill_quantity: 1.0,
            slippage: 0.05,
            latency_ms: 1.5,
        };
        let result2 = ExecutionResult::Executed {
            action: TradeAction::Buy(1.0),
            fill_price: 100.0,
            fill_quantity: 1.0,
            slippage: 0.05,
            latency_ms: 1.5,
        };
        assert_eq!(result1, result2);
    }

    #[test]
    fn test_rejection_reason_equality() {
        let reason1 = RejectionReason::PositionLimit {
            current: 5.0,
            limit: 10.0,
            attempted: TradeAction::Buy(6.0),
        };
        let reason2 = RejectionReason::PositionLimit {
            current: 5.0,
            limit: 10.0,
            attempted: TradeAction::Buy(6.0),
        };
        assert_eq!(reason1, reason2);
    }

    #[test]
    fn test_configs_with_custom_values() {
        let risk_config = RiskControlConfig {
            max_position: 20.0,
            margin_per_contract: 200.0,
            max_drawdown: 0.15,
            max_loss_per_trade: 0.01,
        };
        let cost_config = ExecutionCostConfig {
            slippage_range: (0.01, 0.10),
            latency_range: (2.0, 5.0),
            partial_fill_prob: 0.2,
            partial_fill_ratio: (0.6, 0.95),
        };

        let executor =
            TradeExecutor::with_configs(15000.0, risk_config.clone(), cost_config.clone());
        assert_eq!(executor.risk_config.max_position, 20.0);
        assert_eq!(executor.cost_config.partial_fill_prob, 0.2);
    }
}
