//! Comprehensive tests for financial.rs to achieve 95%+ coverage
//!
//! Tests all integer-based financial types with exact arithmetic

// CANONICAL TYPE IMPORTS - Use specific imports instead of wildcard
use common::{PRICE_SCALE, QUANTITY_SCALE, MONEY_SCALE, IntegerPrice, IntegerQuantity, IntegerMoney};

// ============================================================================
// CONSTANTS TESTS
// ============================================================================

#[test]
fn test_scaling_constants() {
    assert_eq!(PRICE_SCALE, 1_000_000);
    assert_eq!(QUANTITY_SCALE, 1_000_000);
    assert_eq!(MONEY_SCALE, 1_000_000);
}

// ============================================================================
// INTEGER PRICE TESTS
// ============================================================================

#[test]
fn test_integer_price_constants() {
    assert_eq!(IntegerPrice::ZERO.raw_value(), 0);
    assert_eq!(IntegerPrice::ONE.raw_value(), PRICE_SCALE);
}

#[test]
fn test_integer_price_creation() {
    let price = IntegerPrice::from_i64(1234567);
    assert_eq!(price.raw_value(), 1234567);
    assert_eq!(price.value(), 1234567);

    let price_f64 = IntegerPrice::from_f64(123.456);
    assert_eq!(price_f64.raw_value(), (123.456 * PRICE_SCALE as f64) as i64);
}

#[test]
fn test_integer_price_conversions() {
    let price = IntegerPrice::from_f64(123.456);

    // Test to_f64
    let f64_val = price.to_f64();
    assert!((f64_val - 123.456).abs() < 0.001);

    // Test as_f64 (should be same as to_f64)
    assert_eq!(price.as_f64(), price.to_f64());

    // Test to_f32
    let f32_val = price.to_f32();
    assert!((f32_val - 123.456).abs() < 0.01);
}

#[test]
fn test_integer_price_arithmetic() {
    let price1 = IntegerPrice::from_f64(100.0);
    let price2 = IntegerPrice::from_f64(50.0);

    // Addition
    let sum = price1 + price2;
    assert_eq!(sum.to_f64(), 150.0);

    // Subtraction
    let diff = price1 - price2;
    assert_eq!(diff.to_f64(), 50.0);

    // Multiplication by i64
    let product = price1 * 2;
    assert_eq!(product.to_f64(), 200.0);

    // Division by i64
    let quotient = price1 / 2;
    assert_eq!(quotient.to_f64(), 50.0);

    // Division by zero should return zero
    let div_zero = price1 / 0;
    assert_eq!(div_zero.raw_value(), 0);
}

#[test]
fn test_integer_price_add_assign() {
    let mut price = IntegerPrice::from_f64(100.0);
    let other = IntegerPrice::from_f64(50.0);

    price += other;
    assert_eq!(price.to_f64(), 150.0);
}

#[test]
fn test_integer_price_math_operations() {
    let price = IntegerPrice::from_f64(-123.456);

    // Absolute value
    let abs_price = price.abs();
    assert_eq!(abs_price.to_f64(), 123.456);

    // Square root
    let positive_price = IntegerPrice::from_f64(100.0);
    let sqrt_price = positive_price.sqrt();
    assert!((sqrt_price.to_f64() - 10.0).abs() < 0.1);
}

#[test]
fn test_integer_price_saturating_operations() {
    // Test that operations use saturating arithmetic
    let max_price = IntegerPrice::from_i64(i64::MAX);
    let added = max_price + IntegerPrice::from_i64(1);
    // Should saturate at max value
    assert_eq!(added.raw_value(), i64::MAX);

    let min_price = IntegerPrice::from_i64(i64::MIN);
    let subtracted = min_price - IntegerPrice::from_i64(1);
    // Should saturate at min value
    assert_eq!(subtracted.raw_value(), i64::MIN);
}

#[test]
fn test_integer_price_type_alias() {
    // Test that SimplePrice is an alias for IntegerPrice
    let simple_price: SimplePrice = SimplePrice::from_f64(123.45);
    let integer_price: IntegerPrice = IntegerPrice::from_f64(123.45);
    assert_eq!(simple_price.raw_value(), integer_price.raw_value());
}

// ============================================================================
// INTEGER QUANTITY TESTS
// ============================================================================

#[test]
fn test_integer_quantity_constants() {
    assert_eq!(IntegerQuantity::ZERO.raw_value(), 0);
}

#[test]
fn test_integer_quantity_creation() {
    let qty = IntegerQuantity::from_i64(1234567);
    assert_eq!(qty.raw_value(), 1234567);
    assert_eq!(qty.value(), 1234567);

    let qty_f64 = IntegerQuantity::from_f64(123.456);
    assert_eq!(
        qty_f64.raw_value(),
        (123.456 * QUANTITY_SCALE as f64) as i64
    );
}

#[test]
fn test_integer_quantity_conversions() {
    let qty = IntegerQuantity::from_f64(123.456);

    // Test to_f64
    let f64_val = qty.to_f64();
    assert!((f64_val - 123.456).abs() < 0.001);

    // Test to_i64
    assert_eq!(qty.to_i64(), qty.raw_value());
}

#[test]
fn test_integer_quantity_arithmetic() {
    let qty1 = IntegerQuantity::from_f64(100.0);
    let qty2 = IntegerQuantity::from_f64(50.0);

    // Addition
    let sum = qty1 + qty2;
    assert_eq!(sum.to_f64(), 150.0);

    // Subtraction
    let diff = qty1 - qty2;
    assert_eq!(diff.to_f64(), 50.0);

    // Multiplication by i64
    let product = qty1 * 2;
    assert_eq!(product.to_f64(), 200.0);

    // Division by i64
    let quotient = qty1 / 2;
    assert_eq!(quotient.to_f64(), 50.0);

    // Division by zero should return zero
    let div_zero = qty1 / 0;
    assert_eq!(div_zero.raw_value(), 0);
}

#[test]
fn test_integer_quantity_saturating_operations() {
    let max_qty = IntegerQuantity::from_i64(i64::MAX);
    let added = max_qty + IntegerQuantity::from_i64(1);
    assert_eq!(added.raw_value(), i64::MAX);

    let min_qty = IntegerQuantity::from_i64(i64::MIN);
    let subtracted = min_qty - IntegerQuantity::from_i64(1);
    assert_eq!(subtracted.raw_value(), i64::MIN);
}

// ============================================================================
// INTEGER MONEY TESTS
// ============================================================================

#[test]
fn test_integer_money_constants_and_creation() {
    assert_eq!(IntegerMoney::ZERO.raw_value(), 0);
    assert_eq!(IntegerMoney::zero().raw_value(), 0);

    let money = IntegerMoney::from_i64(1234567);
    assert_eq!(money.raw_value(), 1234567);
    assert_eq!(money.value(), 1234567);
}

#[test]
fn test_integer_money_default() {
    let default_money = IntegerMoney::default();
    assert_eq!(default_money, IntegerMoney::ZERO);
}

#[test]
fn test_integer_money_display() {
    let money = IntegerMoney::from_i64(12345); // Represents $123.45 in cents
    let display = format!("{}", money);
    // Display format converts to dollars.cents
    assert!(display.contains("123.45"));

    // Test negative money
    let negative_money = IntegerMoney::from_i64(-12345);
    let negative_display = format!("{}", negative_money);
    assert!(negative_display.contains("-123.45"));

    // Test zero
    let zero_display = format!("{}", IntegerMoney::ZERO);
    assert!(zero_display.contains("0.00"));
}

#[test]
fn test_integer_money_conversions() {
    let money = IntegerMoney::from_f64(123.456);

    // Test to_f64
    let f64_val = money.to_f64();
    assert!((f64_val - 123.456).abs() < 0.001);

    // Test to_decimal
    let decimal = money.to_decimal();
    assert_eq!(decimal.scale(), 6);
    let decimal_f64 = decimal.to_f64().unwrap();
    assert!((decimal_f64 - 123.456).abs() < 0.001);

    // Test to_price
    let price = money.to_price();
    assert_eq!(price.raw_value(), money.raw_value());
}

#[test]
fn test_integer_money_arithmetic() {
    let money1 = IntegerMoney::from_f64(100.0);
    let money2 = IntegerMoney::from_f64(50.0);

    // Addition
    let sum = money1 + money2;
    assert_eq!(sum.to_f64(), 150.0);

    // Subtraction
    let diff = money1 - money2;
    assert_eq!(diff.to_f64(), 50.0);

    // Multiplication by i64
    let product = money1 * 2;
    assert_eq!(product.to_f64(), 200.0);

    // Division by i64
    let quotient = money1 / 2;
    assert_eq!(quotient.to_f64(), 50.0);

    // Division by zero should return zero
    let div_zero = money1 / 0;
    assert_eq!(div_zero.raw_value(), 0);
}

#[test]
fn test_integer_money_saturating_operations() {
    let max_money = IntegerMoney::from_i64(i64::MAX);
    let added = max_money + IntegerMoney::from_i64(1);
    assert_eq!(added.raw_value(), i64::MAX);

    let min_money = IntegerMoney::from_i64(i64::MIN);
    let subtracted = min_money - IntegerMoney::from_i64(1);
    assert_eq!(subtracted.raw_value(), i64::MIN);
}

// ============================================================================
// TRAIT TESTS
// ============================================================================

#[test]
fn test_price_operations_trait() {
    let price1 = IntegerPrice::from_f64(100.0);
    let price2 = IntegerPrice::from_f64(50.0);

    // Test add_price
    let sum = price1.add_price(price2);
    assert_eq!(sum.to_f64(), 150.0);

    // Test sub_price
    let diff = price1.sub_price(price2);
    assert_eq!(diff.to_f64(), 50.0);
}

#[test]
fn test_money_operations_trait() {
    let money1 = IntegerMoney::from_f64(100.0);
    let money2 = IntegerMoney::from_f64(50.0);

    // Test add_money
    let sum = money1.add_money(money2);
    assert_eq!(sum.to_f64(), 150.0);

    // Test sub_money
    let diff = money1.sub_money(money2);
    assert_eq!(diff.to_f64(), 50.0);
}

#[test]
fn test_quantity_operations_trait() {
    let qty1 = IntegerQuantity::from_f64(100.0);
    let qty2 = IntegerQuantity::from_f64(50.0);

    // Test add_quantity
    let sum = qty1.add_quantity(qty2);
    assert_eq!(sum.to_f64(), 150.0);

    // Test sub_quantity
    let diff = qty1.sub_quantity(qty2);
    assert_eq!(diff.to_f64(), 50.0);
}

// ============================================================================
// EDGE CASES AND PRECISION TESTS
// ============================================================================

#[test]
fn test_precision_handling() {
    // Test very small values
    let tiny_price = IntegerPrice::from_f64(0.000_001);
    assert!(tiny_price.to_f64() > 0.0);

    // Test large values
    let large_price = IntegerPrice::from_f64(1_000_000.0);
    assert_eq!(large_price.to_f64(), 1_000_000.0);

    // Test precision limits
    let precise_price = IntegerPrice::from_f64(123.123_456);
    let recovered = precise_price.to_f64();
    assert!((recovered - 123.123_456).abs() < 0.000_001);
}

#[test]
fn test_zero_division_handling() {
    let price = IntegerPrice::from_f64(100.0);
    let qty = IntegerQuantity::from_f64(100.0);
    let money = IntegerMoney::from_f64(100.0);

    // All types should handle zero division gracefully
    assert_eq!((price / 0).raw_value(), 0);
    assert_eq!((qty / 0).raw_value(), 0);
    assert_eq!((money / 0).raw_value(), 0);
}

#[test]
fn test_negative_values() {
    // Test negative prices
    let neg_price = IntegerPrice::from_f64(-100.0);
    assert_eq!(neg_price.to_f64(), -100.0);
    assert_eq!(neg_price.abs().to_f64(), 100.0);

    // Test negative quantities
    let neg_qty = IntegerQuantity::from_f64(-50.0);
    assert_eq!(neg_qty.to_f64(), -50.0);

    // Test negative money
    let neg_money = IntegerMoney::from_f64(-75.0);
    assert_eq!(neg_money.to_f64(), -75.0);
}

#[test]
fn test_comparison_operations() {
    let price1 = IntegerPrice::from_f64(100.0);
    let price2 = IntegerPrice::from_f64(50.0);
    let price3 = IntegerPrice::from_f64(100.0);

    // Test ordering
    assert!(price1 > price2);
    assert!(price2 < price1);
    assert!(price1 == price3);
    assert!(price1 >= price3);
    assert!(price1 <= price3);

    // Test with quantities
    let qty1 = IntegerQuantity::from_f64(100.0);
    let qty2 = IntegerQuantity::from_f64(50.0);
    assert!(qty1 > qty2);

    // Test with money
    let money1 = IntegerMoney::from_f64(100.0);
    let money2 = IntegerMoney::from_f64(50.0);
    assert!(money1 > money2);
}

#[test]
fn test_hash_and_debug() {
    use std::collections::HashMap;

    // Test that types can be used as HashMap keys (requires Hash + Eq)
    let mut price_map = HashMap::new();
    let price_key = IntegerPrice::from_f64(123.45);
    price_map.insert(price_key, "test_price");
    assert_eq!(price_map.get(&price_key), Some(&"test_price"));

    let mut qty_map = HashMap::new();
    let qty_key = IntegerQuantity::from_f64(100.0);
    qty_map.insert(qty_key, "test_qty");
    assert_eq!(qty_map.get(&qty_key), Some(&"test_qty"));

    let mut money_map = HashMap::new();
    let money_key = IntegerMoney::from_f64(500.0);
    money_map.insert(money_key, "test_money");
    assert_eq!(money_map.get(&money_key), Some(&"test_money"));

    // Test Debug formatting
    let debug_price = format!("{:?}", price_key);
    assert!(!debug_price.is_empty());

    let debug_qty = format!("{:?}", qty_key);
    assert!(!debug_qty.is_empty());

    let debug_money = format!("{:?}", money_key);
    assert!(!debug_money.is_empty());
}

// ============================================================================
// COMPREHENSIVE INTEGRATION TESTS
// ============================================================================

#[test]
fn test_type_interoperability() {
    let price = IntegerPrice::from_f64(100.0);
    let money = IntegerMoney::from_f64(100.0);

    // Test conversion from money to price
    let converted_price = money.to_price();
    assert_eq!(converted_price.raw_value(), money.raw_value());

    // Both should have same raw value when created from same f64
    assert_eq!(price.raw_value(), money.raw_value());
}

#[test]
fn test_scaling_factor_consistency() {
    // Verify all scaling factors produce consistent results
    let value = 123.456_789;

    let price = IntegerPrice::from_f64(value);
    let qty = IntegerQuantity::from_f64(value);
    let money = IntegerMoney::from_f64(value);

    // All should have same raw value since they use same scaling factor
    assert_eq!(price.raw_value(), qty.raw_value());
    assert_eq!(qty.raw_value(), money.raw_value());

    // All should convert back to approximately the same value
    assert!((price.to_f64() - value).abs() < 0.001);
    assert!((qty.to_f64() - value).abs() < 0.001);
    assert!((money.to_f64() - value).abs() < 0.001);
}
