//! Comprehensive Property-Based Tests for Financial Calculations
//!
//! This module provides exhaustive property-based testing for all financial calculations
//! in the Foxhunt HFT system to ensure REAL MONEY safety through mathematical correctness.
//!
//! CRITICAL: These tests prevent financial losses through precision errors and edge cases.

#[cfg(test)]
mod tests {
    use proptest::prelude::*;
    use std::f64::{INFINITY, NEG_INFINITY, NAN};
    use chrono::{DateTime, Utc};

    use common::Order;
use common::Position;
use common::Symbol;
use common::Price;
use common::Quantity;
use common::error::CommonError;
use common::error::CommonResult;
use common::OrderId;
use common::TradeId;
use common::ExecutionId;
use common::OrderType;
use common::OrderSide;

// Test Types - Simplified versions for property testing
#[derive(Debug, Clone, Copy, PartialEq)]
struct TestPrice(u64); // Fixed-point representation, 8 decimals

#[derive(Debug, Clone, Copy, PartialEq)]
struct Quantity(u64);

#[derive(Debug, Clone, Copy, PartialEq)]
struct TestVolume(u64);

#[derive(Debug, Clone, Copy, PartialEq)]
struct Amount(i64);

// Implementations
impl TestPrice {
    pub fn new(value: f64) -> Self {
        // Convert to fixed-point representation matching production types
        let decimal_value = Decimal::try_from(value).unwrap_or(Decimal::ZERO);
        let scaled = decimal_value * Decimal::from(100_000_000u64); // 8 decimal places
        Self(scaled.to_u64().unwrap_or(0))
    }
    
    pub fn value(&self) -> f64 {
        (self.0 as f64) / 100_000_000.0 // Convert back from fixed-point
    }
    
    pub fn from_f64(value: f64) -> Self {
        TestTestPrice::new(value.max(0.0)) // Clamp to non-negative
    }
    
    pub fn to_f64(&self) -> f64 {
        self.value()
    }
    
    pub fn add(&self, other: &TestPrice) -> TestPrice {
        TestTestPrice::from_f64(self.value() + other.value())
    }
    
    pub fn multiply(&self, factor: f64) -> TestPrice {
        TestTestPrice::from_f64(self.value() * factor)
    }
    
    pub fn percentage_change(&self, old_price: &TestPrice) -> f64 {
        let old_val = old_price.value();
        if old_val == 0.0 {
            0.0
        } else {
            (self.value() - old_val) / old_val
        }
    }
}

impl Quantity {
    pub fn new(value: i64) -> Self {
        // Ensure non-negative values to match production u64 type
        Quantity(value.max(0) as u64)
    }
    
    pub fn value(&self) -> i64 {
        self.0 as i64
    }
    
    pub fn to_i64(&self) -> i64 {
        self.0 as i64
    }
    
    pub fn abs(&self) -> Quantity {
        Quantity(self.0)
    }
    
    pub fn add(&self, other: &Quantity) -> Quantity {
        Quantity(self.0.saturating_add(other.0))
    }
}

impl TestVolume {
    pub fn new(value: u64) -> Self {
        TestVolume(value)
    }

    pub fn to_u64(&self) -> u64 {
        self.0
    }
}

#[derive(Debug, Clone, PartialEq)]
/// Position component.
pub struct Position {
    pub symbol: String,
    pub side: Side,
    pub quantity: Quantity,
    pub average_price: TestPrice,
    pub current_price: TestPrice,
    pub unrealized_pnl: Amount,
}

impl Position {
    pub fn new(symbol: String, side: Side, quantity: Quantity, average_price: TestPrice) -> Self {
        Self {
            symbol,
            side,
            quantity,
            average_price,
            current_price: average_price.clone(),
            unrealized_pnl: Amount(0),
        }
    }
    
    pub fn calculate_unrealized_pnl(&mut self, current_price: &TestPrice) -> f64 {
        self.current_price = current_price.clone();
        
        let price_diff = match self.side {
            Side::Buy => current_price.0 as f64 - self.average_price.0 as f64,
            Side::Sell => self.average_price.0 as f64 - current_price.0 as f64,
        };
        
        let pnl = price_diff * self.quantity.0 as f64;
        self.unrealized_pnl = Amount(pnl as i64);
        pnl
    }
    
    pub fn notional_value(&self) -> f64 {
        (self.average_price.0 as f64) * (self.quantity.0 as f64)
    }
}

// ============================================================================
// Property-Based Test Strategies
// ============================================================================

/// Strategy for generating valid prices (positive, finite)
fn valid_price_strategy() -> impl Strategy<Value = f64> {
    (0.0001f64..1_000_000.0f64)
}

/// Strategy for generating extreme but valid prices
fn extreme_price_strategy() -> impl Strategy<Value = f64> {
    prop_oneof![
        Just(f64::MIN_POSITIVE),
        Just(f64::MAX),
        (0.0001f64..0.01f64), // Very small prices
        (100_000.0f64..1_000_000.0f64), // Very large prices
    ]
}

/// Strategy for generating quantities with edge cases
fn quantity_strategy() -> impl Strategy<Value = i64> {
    prop_oneof![
        Just(i64::MIN),
        Just(i64::MAX),
        Just(0),
        Just(1),
        Just(-1),
        (-1_000_000i64..1_000_000i64),
    ]
}

/// Strategy for generating large quantities for stress testing
fn large_quantity_strategy() -> impl Strategy<Value = i64> {
    prop_oneof![
        (1_000_000i64..i64::MAX / 2),
        (i64::MIN / 2..-1_000_000i64),
    ]
}

// ============================================================================
// Price Arithmetic Properties
// ============================================================================

proptest! {
    /// Test that `price` addition is commutative: a + b = b + a
    #[test]
    fn price_addition_commutative(
        a in valid_price_strategy(),
        b in valid_price_strategy()
    ) {
        let price_a = TestPrice::from_f64(a).expect("Valid price");
        let price_b = TestPrice::from_f64(b).expect("Valid price");
        
        let sum_ab = price_a.add(&price_b);
        let sum_ba = price_b.add(&price_a);
        
        prop_assert_eq!(sum_ab, sum_ba);
    }
    
    /// Test that `price` addition is associative: (a + b) + c = a + (b + c)
    #[test]
    fn price_addition_associative(
        a in valid_price_strategy(),
        b in valid_price_strategy(),
        c in valid_price_strategy()
    ) {
        let price_a = TestPrice::from_f64(a).expect("Valid price");
        let price_b = TestPrice::from_f64(b).expect("Valid price");
        let price_c = TestPrice::from_f64(c).expect("Valid price");
        
        let left = price_a.add(&price_b).add(&price_c);
        let right = price_a.add(&price_b.add(&price_c));
        
        // Allow small floating point differences
        let diff = (left.to_f64() - right.to_f64()).abs();
        prop_assert!(diff < 1e-10);
    }
    
    /// Test that `price` remains non-negative under all operations
    #[test]
    fn price_always_non_negative(
        a in any::<f64>(),
        b in any::<f64>()
    ) {
        let price_a = TestPrice::from_f64(a).expect("Valid price");
        let price_b = TestPrice::from_f64(b).expect("Valid price");
        
        prop_assert!(price_a.to_f64() >= 0.0);
        prop_assert!(price_b.to_f64() >= 0.0);
        
        let sum = price_a.add(&price_b);
        prop_assert!(sum.to_f64() >= 0.0);
        
        let product = price_a.multiply(2.0);
        prop_assert!(product.to_f64() >= 0.0);
    }
    
    /// Test `price` multiplication properties
    #[test]
    fn price_multiplication_properties(
        price in valid_price_strategy(),
        factor in -1000.0f64..1000.0f64
    ) {
        let p = TestPrice::from_f64(price).expect("Valid price");
        let result = p.multiply(factor);
        
        // Result should always be non-negative due to clamping
        prop_assert!(result.to_f64() >= 0.0);
        
        // If factor is positive, result should be factor * price (or 0 if negative)
        if factor >= 0.0 {
            let expected = price * factor;
            let diff = (result.to_f64() - expected).abs();
            prop_assert!(diff < 1e-10 || expected < 0.0); // Account for clamping
        }
        
        // Multiplication by 0 should give 0
        let zero_result = p.multiply(0.0);
        prop_assert_eq!(zero_result.to_f64(), 0.0);
        
        // Multiplication by 1 should give original price
        let identity_result = p.multiply(1.0);
        let diff = (identity_result.to_f64() - price).abs();
        prop_assert!(diff < 1e-10);
    }
    
    /// Test percentage change calculations
    #[test]
    fn price_percentage_change_properties(
        old_price in valid_price_strategy(),
        new_price in valid_price_strategy()
    ) {
        let old_p = TestPrice::from_f64(old_price).expect("Valid price");
        let new_p = TestPrice::from_f64(new_price).expect("Valid price");
        
        let pct_change = new_p.percentage_change(&old_p);
        
        // Percentage change should be finite unless old_price is 0
        if old_price > f64::EPSILON {
            prop_assert!(pct_change.is_finite());
            
            // Verify the calculation: new = old * (1 + pct_change)
            let expected_new = old_price * (1.0 + pct_change);
            let diff = (new_price - expected_new).abs();
            prop_assert!(diff < 1e-10);
        }
        
        // When prices are equal, percentage change should be 0
        let zero_change = old_p.percentage_change(&old_p);
        prop_assert!(zero_change.abs() < 1e-10);
    }
}

// ============================================================================
// Quantity Arithmetic Properties
// ============================================================================

proptest! {
    /// Test `quantity` addition with overflow protection
    #[test]
    fn quantity_addition_overflow_safe(
        a in quantity_strategy(),
        b in quantity_strategy()
    ) {
        let qty_a = Quantity::new(a);
        let qty_b = Quantity::new(b);
        
        let sum = qty_a.add(&qty_b);
        
        // Addition should never panic (uses saturating_add)
        prop_assert!(sum.to_i64() >= i64::MIN);
        prop_assert!(sum.to_i64() <= i64::MAX);
        
        // If no overflow would occur, result should be exact
        if let Some(expected) = a.checked_add(b) {
            prop_assert_eq!(sum.to_i64(), expected);
        } else {
            // Overflow occurred, result should be saturated
            if (a > 0 && b > 0) || (a > 0 && b > 0) {
                prop_assert_eq!(sum.to_i64(), i64::MAX);
            } else if a < 0 && b < 0 {
                prop_assert_eq!(sum.to_i64(), i64::MIN);
            }
        }
    }
    
    /// Test `quantity` absolute value properties
    #[test]
    fn quantity_absolute_value_properties(
        value in quantity_strategy()
    ) {
        let qty = Quantity::new(value);
        let abs_qty = qty.abs();
        
        // Absolute value should always be non-negative
        prop_assert!(abs_qty.to_i64() >= 0);
        
        // |x| = x if x >= 0, -x if x < 0
        if value >= 0 {
            prop_assert_eq!(abs_qty.to_i64(), value);
        } else if value > i64::MIN { // Avoid overflow on MIN
            prop_assert_eq!(abs_qty.to_i64(), -value);
        }
        
        // ||x|| = |x| (idempotent)
        prop_assert_eq!(abs_qty.abs().to_i64(), abs_qty.to_i64());
    }
    
    /// Test `quantity` edge cases
    #[test]
    fn quantity_edge_cases(
        value in prop_oneof![Just(i64::MIN), Just(i64::MAX), Just(0)]
    ) {
        let qty = Quantity::new(value);
        
        // Should handle extreme values without panicking
        prop_assert_eq!(qty.to_i64(), value);
        
        // Test operations don't panic
        let _abs = qty.abs();
        let _sum = qty.add(&Quantity::new(0));
    }
}

// ============================================================================
// Position P&L Calculation Properties
// ============================================================================

proptest! {
    /// Test `P`&`L` calculation properties for buy positions
    #[test]
    fn buy_position_pnl_properties(
        entry_price in valid_price_strategy(),
        current_price in valid_price_strategy(),
        quantity in 1i64..1_000_000i64 // Positive quantities for buy positions
    ) {
        let mut position = Position::new(
            "EURUSD".to_string(),
            Side::Buy,
            Quantity::new(quantity),
            TestPrice::from_f64(entry_price).expect("Valid price")
        );
        
        let pnl = position.calculate_unrealized_pnl(&TestPrice::from_f64(current_price).expect("Valid price"));
        
        // P&L should equal (current_price - entry_price) * quantity
        let expected_pnl = (current_price - entry_price) * quantity as f64;
        let diff = (pnl - expected_pnl).abs();
        prop_assert!(diff < 1e-10);
        
        // If current price > entry price, P&L should be positive
        if current_price > entry_price {
            prop_assert!(pnl > 0.0);
        }
        
        // If current price < entry price, P&L should be negative
        if current_price < entry_price {
            prop_assert!(pnl < 0.0);
        }
        
        // If prices are equal, P&L should be zero
        if (current_price - entry_price).abs() < f64::EPSILON {
            prop_assert!(pnl.abs() < 1e-10);
        }
    }
    
    /// Test `P`&`L` calculation properties for sell positions
    #[test]
    fn sell_position_pnl_properties(
        entry_price in valid_price_strategy(),
        current_price in valid_price_strategy(),
        quantity in 1i64..1_000_000i64 // Positive quantities for sell positions
    ) {
        let mut position = Position::new(
            "EURUSD".to_string(),
            Side::Sell,
            Quantity::new(quantity),
            TestPrice::from_f64(entry_price).expect("Valid price")
        );
        
        let pnl = position.calculate_unrealized_pnl(&TestPrice::from_f64(current_price).expect("Valid price"));
        
        // P&L should equal (entry_price - current_price) * quantity
        let expected_pnl = (entry_price - current_price) * quantity as f64;
        let diff = (pnl - expected_pnl).abs();
        prop_assert!(diff < 1e-10);
        
        // If current price < entry price, P&L should be positive
        if current_price < entry_price {
            prop_assert!(pnl > 0.0);
        }
        
        // If current price > entry price, P&L should be negative
        if current_price > entry_price {
            prop_assert!(pnl < 0.0);
        }
        
        // If prices are equal, P&L should be zero
        if (current_price - entry_price).abs() < f64::EPSILON {
            prop_assert!(pnl.abs() < 1e-10);
        }
    }
    
    /// Test position notional value calculation
    #[test]
    fn position_notional_value_properties(
        price in valid_price_strategy(),
        quantity in quantity_strategy()
    ) {
        let position = Position::new(
            "EURUSD".to_string(),
            Side::Buy,
            Quantity::new(quantity),
            TestPrice::from_f64(price).expect("Valid price")
        );
        
        let notional = position.notional_value();
        
        // Notional should equal price * |quantity|
        let expected = price * quantity.abs() as f64;
        let diff = (notional - expected).abs();
        prop_assert!(diff < 1e-10);
        
        // Notional should always be non-negative
        prop_assert!(notional >= 0.0);
        
        // Notional should be zero if price or quantity is zero
        if price == 0.0 || quantity == 0 {
            prop_assert_eq!(notional, 0.0);
        }
    }
    
    /// Test large position `P`&`L` calculations don't overflow
    #[test]
    fn large_position_pnl_no_overflow(
        entry_price in valid_price_strategy(),
        current_price in valid_price_strategy(),
        quantity in large_quantity_strategy()
    ) {
        let mut position = Position::new(
            "EURUSD".to_string(),
            Side::Buy,
            Quantity::new(quantity),
            TestPrice::from_f64(entry_price).expect("Valid price")
        );
        
        let pnl = position.calculate_unrealized_pnl(&TestPrice::from_f64(current_price).expect("Valid price"));
        
        // P&L calculation should not overflow to infinity
        prop_assert!(pnl.is_finite());
        
        // Large quantities should still produce mathematically correct results
        // within floating point precision limits
        if quantity.abs() < 1_000_000 {
            let expected_pnl = (current_price - entry_price) * quantity as f64;
            let relative_error = if expected_pnl != 0.0 {
                ((pnl - expected_pnl) / expected_pnl).abs()
            } else {
                pnl.abs()
            };
            prop_assert!(relative_error < 1e-10);
        }
    }
}

// ============================================================================
// Edge Case and Boundary Condition Tests
// ============================================================================

proptest! {
    /// Test behavior with extreme `price` values
    #[test]
    fn extreme_price_handling(
        price in extreme_price_strategy()
    ) {
        let p = TestPrice::from_f64(price).expect("Valid price");
        
        // Should handle extreme values gracefully
        prop_assert!(p.to_f64().is_finite());
        prop_assert!(p.to_f64() >= 0.0);
        
        // Operations should remain stable
        let doubled = p.multiply(2.0);
        prop_assert!(doubled.to_f64().is_finite());
        
        let added = p.add(&TestPrice::from_f64(1.0).expect("Valid price"));
        prop_assert!(added.to_f64().is_finite());
    }
    
    /// Test precision preservation in financial calculations
    #[test]
    fn financial_precision_preservation(
        base_price in 1.0f64..2.0f64,
        pip_count in 1u32..10000u32
    ) {
        let pip_value = 0.0001; // Standard pip for EUR/USD
        let price_movement = pip_count as f64 * pip_value;
        
        let entry_price = TestPrice::from_f64(base_price).expect("Valid price");
        let exit_price = TestPrice::from_f64(base_price + price_movement).expect("Valid price");
        
        // Calculate P&L for a standard lot (100,000 units)
        let mut position = Position::new(
            "EURUSD".to_string(),
            Side::Buy,
            Quantity::new(100_000),
            entry_price
        );
        
        let pnl = position.calculate_unrealized_pnl(&exit_price);
        
        // P&L should be close to pip_count * 10 USD (for EUR/USD)
        let expected_pnl_usd = pip_count as f64 * 10.0;
        let error = (pnl - expected_pnl_usd).abs();
        
        // Allow small error due to floating point arithmetic
        prop_assert!(error < 0.01, "P&L error too large: {} vs {}", pnl, expected_pnl_usd);
    }
    
    /// Test handling of special floating point values
    #[test]
    fn special_float_value_handling(
        special_value in prop_oneof![
            Just(NAN),
            Just(INFINITY),
            Just(NEG_INFINITY),
            Just(f64::MIN),
            Just(f64::MAX),
            Just(f64::MIN_POSITIVE)
        ]
    ) {
        let price = TestPrice::from_f64(special_value).expect("Valid price");
        
        // Should convert special values to safe values
        prop_assert!(price.to_f64().is_finite());
        prop_assert!(price.to_f64() >= 0.0);
        
        // Operations should not propagate special values
        let result = price.multiply(1.5);
        prop_assert!(result.to_f64().is_finite());
        prop_assert!(result.to_f64() >= 0.0);
    }
    
    /// Test compound operations maintain precision
    #[test]
    fn compound_operations_precision(
        prices in prop::collection::vec(valid_price_strategy(), 1..100)
    ) {
        // Sum all prices
        let mut total = TestPrice::from_f64(0.0).expect("Valid price");
        let mut expected_sum = 0.0;
        
        for price_val in &prices {
            let price = TestPrice::from_f64(*price_val).expect("Valid price");
            total = total.add(&price);
            expected_sum += price_val;
        }
        
        // Precision should be maintained within reasonable bounds
        let error = (total.to_f64() - expected_sum).abs();
        let relative_error = if expected_sum > 0.0 {
            error / expected_sum
        } else {
            error
        };
        
        // Allow small accumulation of floating point errors
        prop_assert!(relative_error < 1e-12);
    }
}

// ============================================================================
// Integration Tests for Financial Operations
// ============================================================================

#[tokio::test]
async fn test_realistic_trading_scenario_precision() {
    // Test a realistic EUR/USD trading scenario
    let entry_price = TestPrice::from_f64(1.1000).expect("Valid price");
    let position_size = Quantity::new(100_000); // Standard lot
    
    let mut position = Position::new(
        "EURUSD".to_string(),
        Side::Buy,
        position_size,
        entry_price.clone()
    );
    
    // Simulate market movements of 1, 5, 10, 20 pips
    let pip_movements = vec![1, 5, 10, 20];
    
    for pips in pip_movements {
        let current_price = TestPrice::from_f64(1.1000 + pips as f64 * 0.0001).expect("Valid price");
        let pnl = position.calculate_unrealized_pnl(&current_price);
        
        // Each pip should be worth approximately $10 for EUR/USD standard lot
        let expected_pnl = pips as f64 * 10.0;
        let error = (pnl - expected_pnl).abs();
        
        assert!(error < 0.01, "P&L error for {} pips: {} vs {}", pips, pnl, expected_pnl);
    }
}

#[tokio::test]
async fn test_portfolio_level_precision() {
    // Test precision when managing multiple positions
    let symbols = vec!["EURUSD", "GBPUSD", "USDJPY", "USDCHF", "AUDUSD"];
    let mut positions = Vec::new();
    let mut total_pnl = 0.0;
    
    for (i, symbol) in symbols.into_iter().enumerate() {
        let entry_price = TestPrice::from_f64(1.0 + i as f64 * 0.1).expect("Valid price");
        let current_price = TestPrice::from_f64(1.0 + i as f64 * 0.1 + 0.01).expect("Valid price"); // 100 pip profit each
        let quantity = Quantity::new(10_000 * (i as i64 + 1)); // Different sizes
        
        let mut position = Position::new(
            symbol.to_string(),
            Side::Buy,
            quantity,
            entry_price
        );
        
        let pnl = position.calculate_unrealized_pnl(&current_price);
        total_pnl += pnl;
        
        positions.push(position);
    }
    
    // Total P&L should be the sum of individual P&Ls
    let manual_total = positions.iter()
        .map(|p| p.unrealized_pnl.0 as f64)
        .sum::<f64>();
    
    let error = (total_pnl - manual_total).abs();
    assert!(error < 1e-10, "Portfolio P&L calculation error: {}", error);
}

#[test]
fn test_stress_financial_calculations() {
    // Stress test with many small operations
    let base_price = TestPrice::from_f64(1.0).expect("Valid price");
    let increment = TestPrice::from_f64(0.00001).expect("Valid price"); // Half pip
    
    let mut accumulated = base_price.clone();
    
    // Perform 100,000 small additions
    for _ in 0..100_000 {
        accumulated = accumulated.add(&increment);
    }
    
    // Final price should be approximately 1.0 + 100,000 * 0.00001 = 2.0
    let expected = 2.0;
    let error = (accumulated.to_f64() - expected).abs();
    
    // Allow some accumulation of floating point errors
    assert!(error < 1e-8, "Stress test precision error: {}", error);
}
}  // end mod tests