//! Continuous Transaction Cost Model for PPO
//!
//! Provides realistic transaction cost modeling for continuous position sizing.
//! Unlike discrete action costs (fixed per action), continuous costs scale with
//! the magnitude of position changes.
//!
//! # Cost Components
//!
//! 1. **Base Cost**: Proportional to trade value (bps of notional)
//! 2. **Slippage**: Increases with trade size (market impact)
//! 3. **Order Type**: Different execution strategies have different costs
//!
//! # Fee Structure (Aligned with DQN)
//!
//! - Market: 15 bps (0.15%) - Immediate execution, high cost
//! - `LimitMaker`: 5 bps (0.05%) - Passive order, maker rebate
//! - `IoC`: 10 bps (0.10%) - Immediate-or-cancel, medium cost

pub use ml_core::action_space::OrderType;
use serde::{Deserialize, Serialize};

/// Transaction cost model for continuous position sizing
///
/// Combines base costs, slippage, and order type to compute total cost
/// for a position change. Costs are proportional to trade magnitude.
///
/// # Example
///
/// ```rust
/// use ml::ppo::continuous_transaction_costs::{
///     ContinuousTransactionCosts, SlippageModel, OrderType
/// };
///
/// let costs = ContinuousTransactionCosts::default_hft_cost_model();
///
/// // Small position change: 0.5 contracts @ $5000/contract = $2500 trade
/// let small_cost = costs.compute_cost(0.5, 0.0, 0.5);
/// assert!(small_cost < 20.0); // < $20 cost
///
/// // Large position change: 5.0 contracts = $25,000 trade
/// let large_cost = costs.compute_cost(5.0, 0.0, 5.0);
/// assert!(large_cost > small_cost * 10.0); // Slippage increases cost
/// ```
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContinuousTransactionCosts {
    /// Base cost in basis points (bps)
    /// 1 bp = 0.01% = 0.0001
    pub base_cost_bps: f32,

    /// Slippage model (market impact)
    pub slippage_model: SlippageModel,

    /// Order type preference
    pub order_type: OrderType,

    /// Contract value for cost computation (e.g., $5000 for ES futures)
    pub contract_value: f64,
}

/// Slippage model for market impact
///
/// Slippage increases with trade size due to order book depth limits.
/// Larger trades walk up the order book, getting worse fills.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SlippageModel {
    /// No slippage (theoretical)
    None,

    /// Linear slippage: cost = slope × |`position_change`|
    /// slope in bps per contract (e.g., 0.1 bps per contract)
    Linear { slope: f32 },

    /// Quadratic slippage: cost = coefficient × |`position_change|²`
    /// coefficient in bps (e.g., 0.5 → 0.5 bps for 1 contract, 2 bps for 2 contracts)
    Quadratic { coefficient: f32 },
}

impl ContinuousTransactionCosts {
    /// Create new transaction cost model
    pub const fn new(
        base_cost_bps: f32,
        slippage_model: SlippageModel,
        order_type: OrderType,
        contract_value: f64,
    ) -> Self {
        Self {
            base_cost_bps,
            slippage_model,
            order_type,
            contract_value,
        }
    }

    /// Compute total transaction cost for position change
    ///
    /// # Arguments
    ///
    /// * `position_change` - Absolute change in position (contracts or units)
    /// * `current_position` - Current position (for context, not currently used)
    /// * `target_position` - Target position (for context, not currently used)
    ///
    /// # Returns
    ///
    /// Total cost in dollars
    ///
    /// # Formula
    ///
    /// ```text
    /// trade_value = |position_change| × contract_value
    /// base_cost = trade_value × (base_cost_bps / 10000)
    /// slippage_cost = compute_slippage(|position_change|)
    /// total_cost = base_cost + slippage_cost
    /// ```
    ///
    /// # Example
    ///
    /// ```rust
    /// use ml::ppo::continuous_transaction_costs::{
    ///     ContinuousTransactionCosts, SlippageModel, OrderType
    /// };
    ///
    /// let costs = ContinuousTransactionCosts::new(
    ///     5.0,  // 5 bps base
    ///     SlippageModel::Linear { slope: 0.1 },
    ///     OrderType::LimitMaker,
    ///     5000.0,  // $5000 per contract
    /// );
    ///
    /// // 1 contract change @ $5000 = $5000 trade
    /// // Base: $5000 × 0.0005 = $2.50
    /// // Slippage: $5000 × 0.00001 × 1 = $0.50
    /// // Total: $3.00
    /// let cost = costs.compute_cost(1.0, 0.0, 1.0);
    /// assert!((cost - 3.0).abs() < 0.1);
    /// ```
    pub fn compute_cost(
        &self,
        position_change: f32,
        _current_position: f32,
        _target_position: f32,
    ) -> f32 {
        let abs_change = position_change.abs();

        // Zero change = zero cost
        if abs_change < 1e-6 {
            return 0.0;
        }

        // Compute trade value
        let trade_value = (abs_change as f64) * self.contract_value;

        // Base cost (in bps)
        let base_cost_dollars = trade_value * (self.base_cost_bps as f64 / 10000.0);

        // Slippage cost
        let slippage_bps = self.compute_slippage_bps(abs_change);
        let slippage_dollars = trade_value * (slippage_bps as f64 / 10000.0);

        (base_cost_dollars + slippage_dollars) as f32
    }

    /// Compute cost gradient for backpropagation
    ///
    /// Gradient of total cost w.r.t. `position_change`.
    /// Useful for incorporating transaction costs into policy gradient.
    ///
    /// # Formula
    ///
    /// For Linear slippage: d(cost)/d(pos) = `contract_value` × (`base_bps` + slope) / 10000
    /// For Quadratic slippage: d(cost)/d(pos) = `contract_value` × (`base_bps` + 2 × coeff × |pos|) / 10000
    ///
    /// # Returns
    ///
    /// Gradient as f32 (dollars per unit position change)
    ///
    /// # Example
    ///
    /// ```rust
    /// use ml::ppo::continuous_transaction_costs::{
    ///     ContinuousTransactionCosts, SlippageModel, OrderType
    /// };
    ///
    /// let costs = ContinuousTransactionCosts::new(
    ///     5.0,
    ///     SlippageModel::Linear { slope: 0.1 },
    ///     OrderType::LimitMaker,
    ///     5000.0,
    /// );
    ///
    /// // Gradient = 5000 × (5.0 + 0.1) / 10000 = 2.55
    /// let grad = costs.compute_cost_gradient(1.0);
    /// assert!((grad - 2.55).abs() < 0.01);
    /// ```
    pub fn compute_cost_gradient(&self, position_change: f32) -> f32 {
        let abs_change = position_change.abs();

        let slippage_gradient_bps = match &self.slippage_model {
            SlippageModel::None => 0.0,
            SlippageModel::Linear { slope } => *slope,
            SlippageModel::Quadratic { coefficient } => 2.0 * coefficient * abs_change,
        };

        let total_gradient_bps = self.base_cost_bps + slippage_gradient_bps;

        (self.contract_value as f32) * total_gradient_bps / 10000.0
    }

    /// Compute slippage cost in basis points
    fn compute_slippage_bps(&self, abs_position_change: f32) -> f32 {
        match &self.slippage_model {
            SlippageModel::None => 0.0,
            SlippageModel::Linear { slope } => slope * abs_position_change,
            SlippageModel::Quadratic { coefficient } => {
                coefficient * abs_position_change * abs_position_change
            }
        }
    }

    /// Get total cost in bps (for analysis)
    pub fn total_cost_bps(&self, position_change: f32) -> f32 {
        let abs_change = position_change.abs();
        self.base_cost_bps + self.compute_slippage_bps(abs_change)
    }
}

/// Default HFT cost model (aligned with DQN)
///
/// Uses `LimitMaker` orders (5 bps) with linear slippage (0.1 bps/contract).
/// Contract value: $5000 (ES futures).
///
/// # Example
///
/// ```rust
/// use ml::ppo::continuous_transaction_costs::default_hft_cost_model;
///
/// let costs = default_hft_cost_model();
///
/// // 1 contract @ $5000 = $5000 trade
/// // Base: 5 bps = $2.50
/// // Slippage: 0.1 bps = $0.50
/// // Total: $3.00
/// let cost = costs.compute_cost(1.0, 0.0, 1.0);
/// assert!((cost - 3.0).abs() < 0.1);
/// ```
pub const fn default_hft_cost_model() -> ContinuousTransactionCosts {
    ContinuousTransactionCosts {
        base_cost_bps: 5.0, // LimitMaker (aligned with DQN)
        slippage_model: SlippageModel::Linear { slope: 0.1 },
        order_type: OrderType::LimitMaker,
        contract_value: 5000.0, // ES futures ~$5000/contract
    }
}

/// Conservative cost model (Market orders, quadratic slippage)
///
/// Higher costs for realistic simulation of aggressive execution.
pub const fn conservative_cost_model() -> ContinuousTransactionCosts {
    ContinuousTransactionCosts {
        base_cost_bps: 15.0, // Market orders
        slippage_model: SlippageModel::Quadratic { coefficient: 0.5 },
        order_type: OrderType::Market,
        contract_value: 5000.0,
    }
}

/// Zero-cost model (for testing/ablation studies)
pub const fn zero_cost_model() -> ContinuousTransactionCosts {
    ContinuousTransactionCosts {
        base_cost_bps: 0.0,
        slippage_model: SlippageModel::None,
        order_type: OrderType::LimitMaker,
        contract_value: 5000.0,
    }
}

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

    #[test]
    fn test_order_type_cost_bps() {
        assert_eq!(OrderType::Market.cost_bps(), 15.0);
        assert_eq!(OrderType::LimitMaker.cost_bps(), 5.0);
        assert_eq!(OrderType::IoC.cost_bps(), 10.0);
    }

    #[test]
    fn test_order_type_cost_decimal() {
        assert!((OrderType::Market.cost_decimal() - 0.0015).abs() < 1e-6);
        assert!((OrderType::LimitMaker.cost_decimal() - 0.0005).abs() < 1e-6);
        assert!((OrderType::IoC.cost_decimal() - 0.001).abs() < 1e-6);
    }

    #[test]
    fn test_compute_cost_no_slippage() {
        let costs = ContinuousTransactionCosts::new(
            5.0, // 5 bps
            SlippageModel::None,
            OrderType::LimitMaker,
            5000.0,
        );

        // 1 contract @ $5000 = $5000 trade
        // Cost: $5000 × 0.0005 = $2.50
        let cost = costs.compute_cost(1.0, 0.0, 1.0);
        assert!((cost - 2.5).abs() < 0.01);
    }

    #[test]
    fn test_compute_cost_linear_slippage() {
        let costs = ContinuousTransactionCosts::new(
            5.0, // 5 bps base
            SlippageModel::Linear { slope: 1.0 }, // 1.0 bps per contract (was 0.1)
            OrderType::LimitMaker,
            5000.0,
        );

        // 1 contract @ $5000 = $5000 trade
        // Base: $5000 × 0.0005 = $2.50
        // Slippage: $5000 × 0.0001 (1.0 bp) = $0.50
        // Total: $3.00
        let cost = costs.compute_cost(1.0, 0.0, 1.0);
        assert!((cost - 3.0).abs() < 0.1);

        // 2 contracts
        // Base: $10000 × 0.0005 = $5.00
        // Slippage: $10000 × 0.0002 (2.0 bp) = $2.00
        // Total: $7.00
        let cost_2 = costs.compute_cost(2.0, 0.0, 2.0);
        assert!((cost_2 - 7.0).abs() < 0.1);
    }

    #[test]
    fn test_compute_cost_quadratic_slippage() {
        let costs = ContinuousTransactionCosts::new(
            5.0, // 5 bps base
            SlippageModel::Quadratic { coefficient: 0.5 },
            OrderType::LimitMaker,
            5000.0,
        );

        // 1 contract
        // Base: $5000 × 0.0005 = $2.50
        // Slippage: $5000 × (0.5 × 1^2) / 10000 = $5000 × 0.00005 = $0.25
        // Total: $2.75
        let cost_1 = costs.compute_cost(1.0, 0.0, 1.0);
        assert!((cost_1 - 2.75).abs() < 0.1);

        // 2 contracts
        // Base: $10000 × 0.0005 = $5.00
        // Slippage: $10000 × (0.5 × 4) / 10000 = $10000 × 0.0002 = $2.00
        // Total: $7.00
        let cost_2 = costs.compute_cost(2.0, 0.0, 2.0);
        assert!((cost_2 - 7.0).abs() < 0.1);
    }

    #[test]
    fn test_compute_cost_zero_change() {
        let costs = default_hft_cost_model();

        let cost = costs.compute_cost(0.0, 0.0, 0.0);
        assert_eq!(cost, 0.0);

        let cost_tiny = costs.compute_cost(1e-8, 0.0, 1e-8);
        assert_eq!(cost_tiny, 0.0);
    }

    #[test]
    fn test_compute_cost_negative_change() {
        let costs = default_hft_cost_model();

        // Cost should be same for +1.0 and -1.0 (uses abs)
        let cost_pos = costs.compute_cost(1.0, 0.0, 1.0);
        let cost_neg = costs.compute_cost(-1.0, 0.0, -1.0);

        assert!((cost_pos - cost_neg).abs() < 0.01);
    }

    #[test]
    fn test_compute_cost_gradient_linear() {
        let costs = ContinuousTransactionCosts::new(
            5.0,
            SlippageModel::Linear { slope: 0.1 },
            OrderType::LimitMaker,
            5000.0,
        );

        // Gradient = 5000 × (5.0 + 0.1) / 10000 = 2.55
        let grad = costs.compute_cost_gradient(1.0);
        assert!((grad - 2.55).abs() < 0.01);

        // Linear model: gradient independent of position_change
        let grad_2 = costs.compute_cost_gradient(2.0);
        assert!((grad_2 - 2.55).abs() < 0.01);
    }

    #[test]
    fn test_compute_cost_gradient_quadratic() {
        let costs = ContinuousTransactionCosts::new(
            5.0,
            SlippageModel::Quadratic { coefficient: 0.5 },
            OrderType::LimitMaker,
            5000.0,
        );

        // Gradient at pos=1.0: 5000 × (5.0 + 2 × 0.5 × 1.0) / 10000 = 3.0
        let grad_1 = costs.compute_cost_gradient(1.0);
        assert!((grad_1 - 3.0).abs() < 0.01);

        // Gradient at pos=2.0: 5000 × (5.0 + 2 × 0.5 × 2.0) / 10000 = 3.5
        let grad_2 = costs.compute_cost_gradient(2.0);
        assert!((grad_2 - 3.5).abs() < 0.01);
    }

    #[test]
    fn test_compute_cost_gradient_no_slippage() {
        let costs = ContinuousTransactionCosts::new(
            5.0,
            SlippageModel::None,
            OrderType::LimitMaker,
            5000.0,
        );

        // Gradient = 5000 × 5.0 / 10000 = 2.5
        let grad = costs.compute_cost_gradient(1.0);
        assert!((grad - 2.5).abs() < 0.01);
    }

    #[test]
    fn test_total_cost_bps_linear() {
        let costs = ContinuousTransactionCosts::new(
            5.0,
            SlippageModel::Linear { slope: 0.1 },
            OrderType::LimitMaker,
            5000.0,
        );

        // 1 contract: 5.0 + 0.1 × 1.0 = 5.1 bps
        assert!((costs.total_cost_bps(1.0) - 5.1).abs() < 0.01);

        // 2 contracts: 5.0 + 0.1 × 2.0 = 5.2 bps
        assert!((costs.total_cost_bps(2.0) - 5.2).abs() < 0.01);
    }

    #[test]
    fn test_total_cost_bps_quadratic() {
        let costs = ContinuousTransactionCosts::new(
            5.0,
            SlippageModel::Quadratic { coefficient: 0.5 },
            OrderType::LimitMaker,
            5000.0,
        );

        // 1 contract: 5.0 + 0.5 × 1^2 = 5.5 bps
        assert!((costs.total_cost_bps(1.0) - 5.5).abs() < 0.01);

        // 2 contracts: 5.0 + 0.5 × 4 = 7.0 bps
        assert!((costs.total_cost_bps(2.0) - 7.0).abs() < 0.01);
    }

    #[test]
    fn test_default_hft_cost_model() {
        let costs = default_hft_cost_model();

        assert_eq!(costs.base_cost_bps, 5.0);
        assert_eq!(costs.order_type, OrderType::LimitMaker);
        assert_eq!(costs.contract_value, 5000.0);
        matches!(costs.slippage_model, SlippageModel::Linear { slope: _ });

        // 1 contract cost should be ~$3
        let cost = costs.compute_cost(1.0, 0.0, 1.0);
        assert!(cost > 2.5 && cost < 3.5);
    }

    #[test]
    fn test_conservative_cost_model() {
        let costs = conservative_cost_model();

        assert_eq!(costs.base_cost_bps, 15.0);
        assert_eq!(costs.order_type, OrderType::Market);
        matches!(costs.slippage_model, SlippageModel::Quadratic { .. });

        // Should be more expensive than default
        let default_costs = default_hft_cost_model();
        let conservative_cost = costs.compute_cost(1.0, 0.0, 1.0);
        let default_cost = default_costs.compute_cost(1.0, 0.0, 1.0);

        assert!(conservative_cost > default_cost);
    }

    #[test]
    fn test_zero_cost_model() {
        let costs = zero_cost_model();

        let cost = costs.compute_cost(1.0, 0.0, 1.0);
        assert_eq!(cost, 0.0);

        let cost_large = costs.compute_cost(100.0, 0.0, 100.0);
        assert_eq!(cost_large, 0.0);
    }

    #[test]
    fn test_cost_scaling_with_contract_value() {
        let cheap = ContinuousTransactionCosts::new(
            5.0,
            SlippageModel::None,
            OrderType::LimitMaker,
            1000.0, // $1000/contract
        );

        let expensive = ContinuousTransactionCosts::new(
            5.0,
            SlippageModel::None,
            OrderType::LimitMaker,
            10000.0, // $10000/contract
        );

        let cost_cheap = cheap.compute_cost(1.0, 0.0, 1.0);
        let cost_expensive = expensive.compute_cost(1.0, 0.0, 1.0);

        // 10x contract value → 10x cost
        assert!((cost_expensive / cost_cheap - 10.0).abs() < 0.1);
    }

    #[test]
    fn test_different_order_types_integration() {
        let market = ContinuousTransactionCosts::new(
            15.0,
            SlippageModel::None,
            OrderType::Market,
            5000.0,
        );

        let limit = ContinuousTransactionCosts::new(
            5.0,
            SlippageModel::None,
            OrderType::LimitMaker,
            5000.0,
        );

        let ioc = ContinuousTransactionCosts::new(
            10.0,
            SlippageModel::None,
            OrderType::IoC,
            5000.0,
        );

        let market_cost = market.compute_cost(1.0, 0.0, 1.0);
        let limit_cost = limit.compute_cost(1.0, 0.0, 1.0);
        let ioc_cost = ioc.compute_cost(1.0, 0.0, 1.0);

        // Market > IoC > LimitMaker
        assert!(market_cost > ioc_cost);
        assert!(ioc_cost > limit_cost);
    }
}
