//! The non-panicking `checked_*` operators: each must return `Some(== op)`
//! wherever the panicking op succeeds, and `None` exactly where it overflows
//! (panics). This is the contract a caller relies on to use the U-width op as
//! a zero-cost fast path and escalate to a wider type only on `None`.

use crypto_bigint::U512;
use crypto_ratio::RatioU512;
use std::panic::catch_unwind;

fn r(n: u64, d: u64, neg: bool) -> RatioU512 {
    RatioU512::new_raw(U512::from_u64(n), U512::from_u64(d), neg)
}

/// On inputs that do not overflow, every `checked_X` equals `Some(X)`.
#[test]
fn checked_matches_panicking_on_non_overflowing_inputs() {
    let samples = [
        (r(2, 3, false), r(3, 4, false)),
        (r(7, 11, true), r(5, 13, false)),
        (r(1, 1, false), r(999_999, 1_000_000, true)),
        (r(0, 1, false), r(3, 7, false)),
        (r(5, 1, false), r(1, 5, true)),
        (r(1_000_000, 7, true), r(7, 1_000_000, true)),
    ];
    for (a, b) in &samples {
        assert_eq!(a.checked_mul(b), Some(a.mul(b)), "checked_mul parity");
        assert_eq!(a.checked_add(b), Some(a.add(b)), "checked_add parity");
        assert_eq!(
            a.checked_add_sub(b),
            Some(a.add_sub(b)),
            "checked_add_sub parity"
        );
        assert_eq!(
            a.checked_div_by_u64(7),
            Some(a.div_by_u64(7)),
            "checked_div parity"
        );
        // checked_sub == add(-other); compare against the same via checked_add.
        assert_eq!(a.checked_sub(b), a.checked_add(&b.clone().neg()));
    }
}

fn near_max(sub: u64) -> U512 {
    U512::MAX.wrapping_sub(&U512::from_u64(sub))
}

/// `checked_mul` returns `None` exactly where `mul` panics (product exceeds T
/// even after cross-cancellation).
#[test]
fn checked_mul_none_iff_mul_overflows() {
    // MAX/7 · MAX/11 — coprime cross terms, so no cancellation; MAX·MAX ≫ U512.
    let a = RatioU512::new_raw(U512::MAX, U512::from_u64(7), false);
    let b = RatioU512::new_raw(U512::MAX, U512::from_u64(11), false);
    assert_eq!(a.checked_mul(&b), None);
    assert!(
        catch_unwind(|| a.mul(&b)).is_err(),
        "mul must panic on the same input"
    );
}

/// `checked_add` / `checked_sub` / `checked_add_sub` return `None` exactly
/// where `add` / `sub` / `add_sub` panic (reduced result exceeds T).
#[test]
fn checked_add_family_none_iff_overflow() {
    // 1/MAX + 1/(MAX-2): denominators near-coprime, common denom ≈ MAX² ≫ U512.
    let a = RatioU512::new_raw(U512::ONE, U512::MAX, false);
    let b = RatioU512::new_raw(U512::ONE, near_max(2), false);

    assert_eq!(a.checked_add(&b), None);
    assert!(catch_unwind(|| a.add(&b)).is_err(), "add must panic");

    assert_eq!(a.checked_sub(&b), None);
    assert!(
        catch_unwind(|| a.clone() - b.clone()).is_err(),
        "sub must panic"
    );

    assert_eq!(a.checked_add_sub(&b), None);
    assert!(
        catch_unwind(|| a.add_sub(&b)).is_err(),
        "add_sub must panic"
    );
}

/// `checked_div_by_u64` returns `None` exactly where `div_by_u64` panics
/// (scaled denominator exceeds T).
#[test]
fn checked_div_by_u64_none_iff_overflow() {
    // denom = MAX; ·2 overflows U512.
    let a = RatioU512::new_raw(U512::ONE, U512::MAX, false);
    assert_eq!(a.checked_div_by_u64(2), None);
    assert!(
        catch_unwind(|| a.div_by_u64(2)).is_err(),
        "div_by_u64 must panic"
    );
}
