//! Fill simulation for order type-dependent execution.
//!
//! Models the cost/benefit tradeoffs of different order types:
//! - **Market orders**: Always fill, but pay the full spread (taker fee)
//! - **Limit orders**: Save on spread, but may not fill (queue position risk)
//! - **IoC orders**: Aggressive limits — higher fill rate, moderate cost
//!
//! This creates a learnable signal for the execution dimension: the DQN can
//! eventually learn (via Branching DQN in Phase C+) that limit orders are
//! better in calm markets and market orders are better in volatile ones.

use crate::common::action::{OrderType, Urgency};

/// Splitmix64-style deterministic hash that maps (step, action_idx) to [0.0, 1.0).
///
/// Combines two inputs into a single u64 seed using a Weyl-sequence offset,
/// then applies the splitmix64 finalizer (two rounds of xor-shift + multiply)
/// for excellent avalanche and uniformity properties. Zero-allocation, inline-able,
/// and deterministic: same (step, action_idx) always produces the same output.
#[inline]
fn splitmix64_unit(step: usize, action_idx: usize) -> f64 {
    // Combine step and action_idx into a single seed.
    // Use a large odd constant (Weyl sequence increment from splitmix64)
    // to separate action indices within the same step.
    let mut z = (step as u64).wrapping_mul(0x9e37_79b9_7f4a_7c15)
        .wrapping_add((action_idx as u64).wrapping_mul(0x6a09_e667_f3bc_c908));

    // Splitmix64 finalizer — two rounds for full avalanche
    z = (z ^ (z >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
    z = (z ^ (z >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
    z ^= z >> 31;

    // Convert to [0.0, 1.0) by taking the upper 53 bits as a double mantissa.
    // This gives uniform distribution with the full precision of f64.
    (z >> 11) as f64 / ((1_u64 << 53) as f64)
}

/// Result of a simulated fill attempt.
#[derive(Debug, Clone, Copy)]
pub struct FillResult {
    /// Whether the order was filled (true) or missed (false).
    pub filled: bool,
    /// Net cost adjustment as a fraction of trade value.
    /// Positive = cost (market orders), negative = rebate (limit orders that fill).
    pub cost_adjustment: f64,
    /// The fill probability that was used for the random draw.
    pub fill_probability: f64,
}

/// Simulates order fills based on order type and market conditions.
///
/// Models three key effects:
/// 1. **Fill probability**: Market=100%, IoC=~85%, Limit=30-80% (vol-dependent)
/// 2. **Spread capture**: Limit orders that fill capture half the spread
/// 3. **Urgency modifier**: Aggressive increases fill prob but also cost
#[derive(Debug, Clone)]
pub struct FillSimulator {
    /// Base fill probability for IoC orders (default: 0.85)
    pub ioc_fill_prob: f64,
    /// Minimum fill probability for limit orders (default: 0.30)
    pub limit_fill_prob_min: f64,
    /// Maximum fill probability for limit orders (default: 0.80)
    pub limit_fill_prob_max: f64,
    /// Fraction of spread captured by filled limit orders (default: 0.50)
    pub spread_capture_fraction: f64,
    /// Fraction of spread paid by market orders (default: 0.50)
    pub spread_cost_fraction: f64,
}

impl Default for FillSimulator {
    fn default() -> Self {
        Self {
            ioc_fill_prob: 0.85,
            limit_fill_prob_min: 0.30,
            limit_fill_prob_max: 0.80,
            spread_capture_fraction: 0.50,
            spread_cost_fraction: 0.50,
        }
    }
}

impl FillSimulator {
    /// Compute the fill probability for a given order type and market conditions.
    ///
    /// # Arguments
    /// * `order_type` - Market, LimitMaker, or IoC
    /// * `urgency` - Patient (0.5x), Normal (1.0x), or Aggressive (1.5x)
    /// * `normalized_volatility` - Volatility / median_volatility (1.0 = average)
    ///
    /// # Returns
    /// Fill probability in [0.0, 1.0]
    pub fn fill_probability(
        &self,
        order_type: OrderType,
        urgency: Urgency,
        normalized_volatility: f32,
    ) -> f64 {
        let base_prob = match order_type {
            OrderType::Market => 1.0,
            OrderType::IoC => self.ioc_fill_prob,
            OrderType::LimitMaker => {
                // Higher volatility → more price movement → more likely to hit limit price
                let vol_factor = (normalized_volatility as f64).clamp(0.0, 3.0) / 3.0;
                self.limit_fill_prob_min
                    + vol_factor * (self.limit_fill_prob_max - self.limit_fill_prob_min)
            }
        };

        // Urgency modifier: Aggressive increases fill prob, Patient decreases
        let urgency_modifier = match urgency {
            Urgency::Patient => 0.85,    // More patient → less likely to fill
            Urgency::Normal => 1.0,
            Urgency::Aggressive => 1.10, // More aggressive → more likely to fill
        };

        (base_prob * urgency_modifier).clamp(0.0, 1.0)
    }

    /// Compute the spread-related cost adjustment for a given order type.
    ///
    /// # Arguments
    /// * `order_type` - Market, LimitMaker, or IoC
    /// * `spread_bps` - Current bid-ask spread in basis points
    /// * `filled` - Whether the order was filled
    ///
    /// # Returns
    /// Cost adjustment as a fraction (positive = cost, negative = rebate).
    /// For a non-filled limit order, returns 0.0 (no trade happened).
    pub fn spread_cost(&self, order_type: OrderType, spread_bps: f64, filled: bool) -> f64 {
        if !filled {
            return 0.0; // No fill → no cost
        }

        match order_type {
            OrderType::Market => {
                // Market orders cross the spread (pay taker cost)
                spread_bps * self.spread_cost_fraction / 10000.0
            }
            OrderType::IoC => {
                // IoC: partial spread cost (aggressive limit)
                spread_bps * self.spread_cost_fraction * 0.5 / 10000.0
            }
            OrderType::LimitMaker => {
                // Limit orders that fill capture half the spread (maker rebate)
                -(spread_bps * self.spread_capture_fraction / 10000.0)
            }
        }
    }

    /// Simulate a fill attempt using a deterministic hash (reproducible during training).
    ///
    /// Uses a simple hash of step + action index to produce a deterministic
    /// "random" value, ensuring reproducibility across training runs with the
    /// same seed, while still providing stochastic fill behavior.
    ///
    /// # Arguments
    /// * `order_type` - Market, LimitMaker, or IoC
    /// * `urgency` - Patient, Normal, or Aggressive
    /// * `normalized_volatility` - Volatility / median_volatility
    /// * `spread_bps` - Current bid-ask spread in basis points
    /// * `step` - Current training step (for deterministic hash)
    /// * `action_idx` - Action index (for deterministic hash)
    ///
    /// # Returns
    /// `FillResult` with fill status, cost adjustment, and fill probability.
    pub fn simulate_fill(
        &self,
        order_type: OrderType,
        urgency: Urgency,
        normalized_volatility: f32,
        spread_bps: f64,
        step: usize,
        action_idx: usize,
    ) -> FillResult {
        let fill_prob = self.fill_probability(order_type, urgency, normalized_volatility);

        // Deterministic pseudo-random using splitmix64-style finalizer.
        // Combines step and action_idx into a single seed, then applies
        // two rounds of xor-shift + multiply for excellent avalanche properties.
        let pseudo_random = splitmix64_unit(step, action_idx);

        let filled = pseudo_random < fill_prob;
        let cost_adjustment = self.spread_cost(order_type, spread_bps, filled);

        FillResult {
            filled,
            cost_adjustment,
            fill_probability: fill_prob,
        }
    }
}

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

    #[test]
    fn test_market_always_fills() {
        let sim = FillSimulator::default();
        for step in 0..100 {
            let result = sim.simulate_fill(
                OrderType::Market,
                Urgency::Normal,
                1.0,
                10.0,
                step,
                0,
            );
            assert!(result.filled, "Market orders should always fill");
            assert!(result.cost_adjustment > 0.0, "Market orders should have positive cost");
            assert!((result.fill_probability - 1.0).abs() < 1e-10);
        }
    }

    #[test]
    fn test_limit_sometimes_fills() {
        let sim = FillSimulator::default();
        let mut fill_count = 0;
        let total = 1000;
        for step in 0..total {
            let result = sim.simulate_fill(
                OrderType::LimitMaker,
                Urgency::Normal,
                1.0, // normal volatility
                10.0,
                step,
                0,
            );
            if result.filled {
                fill_count += 1;
                // Filled limit orders should have negative cost (rebate)
                assert!(
                    result.cost_adjustment < 0.0,
                    "Filled limit orders should get spread rebate"
                );
            }
        }
        // With normal vol, fill rate should be between 30% and 80%
        let fill_rate = fill_count as f64 / total as f64;
        assert!(
            fill_rate > 0.2 && fill_rate < 0.9,
            "Limit fill rate {} should be between 20% and 90%",
            fill_rate
        );
    }

    #[test]
    fn test_ioc_high_fill_rate() {
        let sim = FillSimulator::default();
        let mut fill_count = 0;
        let total = 1000;
        for step in 0..total {
            let result = sim.simulate_fill(
                OrderType::IoC,
                Urgency::Normal,
                1.0,
                10.0,
                step,
                0,
            );
            if result.filled {
                fill_count += 1;
            }
        }
        let fill_rate = fill_count as f64 / total as f64;
        assert!(
            fill_rate > 0.75,
            "IoC fill rate {} should be above 75%",
            fill_rate
        );
    }

    #[test]
    fn test_high_volatility_increases_limit_fill() {
        let sim = FillSimulator::default();
        let low_vol_prob = sim.fill_probability(OrderType::LimitMaker, Urgency::Normal, 0.1);
        let high_vol_prob = sim.fill_probability(OrderType::LimitMaker, Urgency::Normal, 2.0);
        assert!(
            high_vol_prob > low_vol_prob,
            "Higher vol ({}) should increase limit fill prob vs low vol ({})",
            high_vol_prob,
            low_vol_prob
        );
    }

    #[test]
    fn test_aggressive_increases_fill_prob() {
        let sim = FillSimulator::default();
        let patient = sim.fill_probability(OrderType::LimitMaker, Urgency::Patient, 1.0);
        let aggressive = sim.fill_probability(OrderType::LimitMaker, Urgency::Aggressive, 1.0);
        assert!(
            aggressive > patient,
            "Aggressive ({}) should have higher fill prob than Patient ({})",
            aggressive,
            patient
        );
    }

    #[test]
    fn test_spread_capture_limit_vs_market() {
        let sim = FillSimulator::default();
        let market_cost = sim.spread_cost(OrderType::Market, 10.0, true);
        let limit_cost = sim.spread_cost(OrderType::LimitMaker, 10.0, true);

        assert!(market_cost > 0.0, "Market should pay spread");
        assert!(limit_cost < 0.0, "Limit should capture spread");
        assert!(
            market_cost.abs() > limit_cost.abs() * 0.5,
            "Market cost ({}) should be meaningful relative to limit rebate ({})",
            market_cost,
            limit_cost
        );
    }

    #[test]
    fn test_no_fill_no_cost() {
        let sim = FillSimulator::default();
        let cost = sim.spread_cost(OrderType::LimitMaker, 100.0, false);
        assert!(
            cost.abs() < 1e-10,
            "Non-filled orders should have zero cost"
        );
    }

    #[test]
    fn test_deterministic_simulation() {
        let sim = FillSimulator::default();
        let r1 = sim.simulate_fill(OrderType::LimitMaker, Urgency::Normal, 1.0, 10.0, 42, 3);
        let r2 = sim.simulate_fill(OrderType::LimitMaker, Urgency::Normal, 1.0, 10.0, 42, 3);
        assert_eq!(r1.filled, r2.filled, "Same inputs should give same fill result");
        assert!(
            (r1.cost_adjustment - r2.cost_adjustment).abs() < 1e-10,
            "Same inputs should give same cost"
        );
    }

    #[test]
    fn test_splitmix64_deterministic() {
        // Verify the hash function itself is deterministic
        for step in 0..100 {
            for action in 0..10 {
                let a = splitmix64_unit(step, action);
                let b = splitmix64_unit(step, action);
                assert!(
                    (a - b).abs() < f64::EPSILON,
                    "splitmix64_unit({}, {}) not deterministic: {} vs {}",
                    step,
                    action,
                    a,
                    b
                );
                assert!(
                    (0.0..1.0).contains(&a),
                    "splitmix64_unit({}, {}) = {} out of [0.0, 1.0)",
                    step,
                    action,
                    a
                );
            }
        }
    }

    #[test]
    fn test_splitmix64_uniformity_chi_squared() {
        // Generate 10,000 samples across varied step/action combinations.
        // Bucket into 10 bins, verify chi-squared < 16.92 (p=0.05, df=9).
        let num_bins = 10;
        let num_samples = 10_000usize;
        let mut bins = vec![0u64; num_bins];

        // Use a range of step and action_idx values to exercise the hash
        for i in 0..num_samples {
            let step = i * 7 + 13; // spread steps out
            let action_idx = i % 47; // vary action across a prime range
            let value = splitmix64_unit(step, action_idx);

            // Map [0.0, 1.0) to bin index [0, num_bins)
            let bin = (value * num_bins as f64) as usize;
            // Clamp to handle the (extremely unlikely) edge case of value == 1.0
            let bin = bin.min(num_bins - 1);
            if let Some(count) = bins.get_mut(bin) {
                *count += 1;
            }
        }

        // Chi-squared goodness-of-fit test against uniform distribution
        let expected = num_samples as f64 / num_bins as f64; // 1000.0
        let mut chi_squared = 0.0;
        for bin_count in &bins {
            let observed = *bin_count as f64;
            let diff = observed - expected;
            chi_squared += (diff * diff) / expected;
        }

        // Critical value for chi-squared with df=9 at p=0.05 is 16.919
        assert!(
            chi_squared < 16.92,
            "Chi-squared test failed: {} >= 16.92 (bins: {:?})",
            chi_squared,
            bins
        );
    }
}
