/// Checked ceiling division for integer types.
pub trait CheckedDivCeil {
    fn checked_div_ceil(self, rhs: Self) -> Option<Self>
    where
        Self: Sized;
}

impl CheckedDivCeil for u128 {
    fn checked_div_ceil(self, divisor: u128) -> Option<u128> {
        if divisor == 0 {
            return None;
        }
        let quotient = self.checked_div(divisor)?;
        let product = quotient.checked_mul(divisor)?;
        let remainder = self.checked_sub(product)?;
        if remainder == 0 {
            Some(quotient)
        } else {
            quotient.checked_add(1)
        }
    }
}

impl CheckedDivCeil for i128 {
    fn checked_div_ceil(self, divisor: i128) -> Option<i128> {
        if divisor == 0 {
            return None;
        }
        let quotient = self.checked_div(divisor)?;
        let quotient_times_divisor = quotient.checked_mul(divisor)?;
        let remainder = self.checked_sub(quotient_times_divisor)?;
        if remainder == 0 {
            return Some(quotient);
        }
        // Round up only when both operands have the same sign
        let same_sign = (self > 0 && divisor > 0) || (self < 0 && divisor < 0);
        if same_sign {
            quotient.checked_add(1)
        } else {
            Some(quotient)
        }
    }
}

impl CheckedDivCeil for i64 {
    fn checked_div_ceil(self, divisor: i64) -> Option<i64> {
        if divisor == 0 {
            return None;
        }
        let quotient = self.checked_div(divisor)?;
        let quotient_times_divisor = quotient.checked_mul(divisor)?;
        let remainder = self.checked_sub(quotient_times_divisor)?;
        if remainder == 0 {
            return Some(quotient);
        }
        let same_sign = (self > 0 && divisor > 0) || (self < 0 && divisor < 0);
        if same_sign {
            quotient.checked_add(1)
        } else {
            Some(quotient)
        }
    }
}

#[cfg(test)]
#[allow(
    clippy::allow_attributes,
    clippy::allow_attributes_without_reason,
    clippy::unwrap_used,
    clippy::expect_used,
    clippy::panic,
    clippy::arithmetic_side_effects,
    reason = "test code"
)]
mod tests {
    use super::*;

    #[test]
    fn u128_div_ceil_exact() {
        assert_eq!(10u128.checked_div_ceil(5), Some(2));
    }

    #[test]
    fn u128_div_ceil_rounds_up() {
        assert_eq!(11u128.checked_div_ceil(5), Some(3));
    }

    #[test]
    fn u128_div_ceil_zero_divisor() {
        assert_eq!(10u128.checked_div_ceil(0), None);
    }

    #[test]
    fn i128_div_ceil_positive() {
        assert_eq!(11i128.checked_div_ceil(5), Some(3));
    }

    #[test]
    fn i128_div_ceil_negative_no_roundup() {
        // -11 / 5 = -2 remainder -1; different signs, no round up
        assert_eq!((-11i128).checked_div_ceil(5), Some(-2));
    }

    #[test]
    fn i128_div_ceil_both_negative_rounds_up() {
        // -11 / -5 = 2 remainder -1; same signs, round up
        assert_eq!((-11i128).checked_div_ceil(-5), Some(3));
    }

    #[test]
    fn i64_div_ceil_exact() {
        assert_eq!(10i64.checked_div_ceil(5), Some(2));
    }

    #[test]
    fn i64_div_ceil_rounds_up() {
        assert_eq!(11i64.checked_div_ceil(5), Some(3));
    }
}

#[cfg(test)]
#[allow(
    clippy::allow_attributes,
    clippy::allow_attributes_without_reason,
    clippy::unwrap_used,
    clippy::expect_used,
    clippy::panic,
    clippy::arithmetic_side_effects,
    reason = "test code"
)]
mod proptests {
    use super::*;
    use proptest::prelude::*;

    proptest! {
        #[test]
        fn u128_div_ceil_never_panics(a: u128, b: u128) {
            let _ = a.checked_div_ceil(b);
        }

        #[test]
        fn u128_div_ceil_correct(a in 0u128..=u128::MAX / 2, b in 1u128..=1_000_000_000) {
            let result = a.checked_div_ceil(b).unwrap();
            // result >= a/b (ceiling property)
            let floor = a / b;
            let remainder = a % b;
            if remainder == 0 {
                prop_assert_eq!(result, floor);
            } else {
                prop_assert_eq!(result, floor + 1);
            }
        }

        #[test]
        fn i128_div_ceil_never_panics(a: i64, b: i64) {
            let _ = (a as i128).checked_div_ceil(b as i128);
        }

        #[test]
        fn i128_div_ceil_rounds_toward_positive_infinity_for_same_sign(
            a in 1i128..=1_000_000_000_000,
            b in 1i128..=1_000_000_000,
        ) {
            let result = a.checked_div_ceil(b).unwrap();
            // For positive/positive: result * b >= a
            prop_assert!(result * b >= a);
            // But (result - 1) * b < a (tightest ceiling)
            prop_assert!((result - 1) * b < a);
        }
    }
}
