//! Error types for `clock-bigint` operations.
//!
//! This module defines the error types used throughout the `clock-bigint` library.
//! All errors implement `std::error::Error` when the `std` feature is enabled.
//!
//! ## Error Handling Philosophy
//!
//! The library follows these principles for error handling:
//! - **Constant-time**: Error conditions should not leak timing information
//! - **Minimal**: Only essential error types to avoid API surface complexity
//! - **Informative**: Error messages provide actionable information
//! - **Non-exhaustive**: New error variants may be added in future versions
//!
//! ## Common Error Scenarios
//!
//! - **Overflow**: Operations that would exceed the BigInt's maximum limb capacity
//! - **InvalidEncoding**: Malformed binary encodings or non-canonical representations
//! - **DivisionByZero**: Division or modulo operations with a zero divisor
//! - **InvalidModulus**: Montgomery arithmetic with unsupported modulus values

use core::fmt;

/// Errors that can occur during BigInt operations.
///
/// All variants are designed to be constant-time and provide minimal
/// information to prevent side-channel attacks while still being useful
/// for debugging.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum BigIntError {
    /// Operation would exceed the maximum capacity.
    ///
    /// This error occurs when a BigInt operation would require more limbs
    /// than the configured maximum capacity. This prevents unbounded memory
    /// allocation and ensures constant-time behavior.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use clock_bigint::{BigInt, add_magnitude};
    ///
    /// // Create BigInts with values that need 1 limb each
    /// let a = BigInt::from_u64(u64::MAX, 10);
    /// let b = BigInt::from_u64(u64::MAX, 10);
    ///
    /// // Try to add into a result with insufficient capacity (only 1 limb max)
    /// let mut result = BigInt::new(1); // Only 1 limb capacity
    /// let add_result = add_magnitude(&a, &b, &mut result);
    /// assert!(add_result.is_err());
    /// ```
    Overflow,

    /// Invalid encoding detected (non-canonical form).
    ///
    /// This error occurs when attempting to decode malformed or non-canonical
    /// binary representations of BigInt values. The encoding format requires
    /// specific structure and canonical forms for security and correctness.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use clock_bigint::BigInt;
    ///
    /// // Empty data cannot be decoded
    /// let result = clock_bigint::decode(&[]);
    /// assert!(matches!(result, Err(clock_bigint::BigIntError::InvalidEncoding)));
    ///
    /// // Invalid length prefix
    /// let invalid_data = vec![0u8; 10]; // Too short for valid encoding
    /// let result = clock_bigint::decode(&invalid_data);
    /// assert!(matches!(result, Err(clock_bigint::BigIntError::InvalidEncoding)));
    /// ```
    InvalidEncoding,

    /// Division by zero attempted.
    ///
    /// This error occurs when attempting to divide by zero or compute
    /// modulo zero. These operations are mathematically undefined.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use clock_bigint::{BigInt, div};
    ///
    /// let a = BigInt::from_u64(10, 10);
    /// let zero = BigInt::from_u64(0, 10);
    ///
    /// // Division by zero
    /// let result = div(&a, &zero);
    /// assert!(matches!(result, Err(clock_bigint::BigIntError::DivisionByZero)));
    /// ```
    DivisionByZero,

    /// Invalid modulus for Montgomery arithmetic.
    ///
    /// This error occurs when attempting to use Montgomery arithmetic with
    /// an unsupported modulus. Montgomery operations require the modulus to be
    /// odd and non-zero for the algorithms to work correctly.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use clock_bigint::{BigInt, MontgomeryContext};
    ///
    /// let even_modulus = BigInt::from_u64(10, 10); // Even modulus
    ///
    /// // Creating Montgomery context with even modulus fails
    /// let result = MontgomeryContext::new(even_modulus);
    /// assert!(matches!(result, Err(clock_bigint::BigIntError::InvalidModulus)));
    ///
    /// let zero_modulus = BigInt::from_u64(0, 10); // Zero modulus
    /// let result = MontgomeryContext::new(zero_modulus);
    /// assert!(matches!(result, Err(clock_bigint::BigIntError::InvalidModulus)));
    /// ```
    InvalidModulus,

    /// Invalid internal state.
    ///
    /// This error occurs when an operation is attempted but the BigInt
    /// is in an invalid internal state, such as having non-canonical
    /// representation when canonical form is required.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use clock_bigint::BigInt;
    ///
    /// let mut bi = BigInt::from_u64(10, 10);
    /// // Manually corrupt internal state (normally not possible through public API)
    /// // This would cause InvalidState if the corruption were detected
    /// ```
    InvalidState,
}

impl fmt::Display for BigIntError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            BigIntError::Overflow => {
                write!(f, "BigInt operation would exceed maximum capacity")
            }
            BigIntError::InvalidEncoding => {
                write!(
                    f,
                    "Invalid BigInt encoding: data is malformed or non-canonical"
                )
            }
            BigIntError::DivisionByZero => {
                write!(f, "Division by zero is undefined")
            }
            BigIntError::InvalidModulus => {
                write!(
                    f,
                    "Invalid modulus for Montgomery arithmetic: must be odd and non-zero"
                )
            }
            BigIntError::InvalidState => {
                write!(f, "BigInt is in an invalid internal state")
            }
        }
    }
}


/// Result type alias for BigInt operations.
///
/// This is a convenience type that uses `BigIntError` as the error type
/// for all fallible BigInt operations.
pub type Result<T> = core::result::Result<T, BigIntError>;

#[cfg(all(test, feature = "alloc"))]
mod tests {
    use super::*;
    use alloc::string::String;
    use alloc::format;
    use core::fmt::Write;

    #[test]
    fn test_error_display() {
        let mut buf = String::new();

        // Test Overflow display
        write!(buf, "{}", BigIntError::Overflow).unwrap();
        assert!(buf.contains("exceed maximum capacity"));
        buf.clear();

        // Test InvalidEncoding display
        write!(buf, "{}", BigIntError::InvalidEncoding).unwrap();
        assert!(buf.contains("malformed or non-canonical"));
        buf.clear();

        // Test DivisionByZero display
        write!(buf, "{}", BigIntError::DivisionByZero).unwrap();
        assert!(buf.contains("undefined"));
        buf.clear();

        // Test InvalidModulus display
        write!(buf, "{}", BigIntError::InvalidModulus).unwrap();
        assert!(buf.contains("odd and non-zero"));
        buf.clear();
    }

    #[test]
    fn test_error_debug() {
        // Test that Debug works and includes the variant name
        let overflow_str = format!("{:?}", BigIntError::Overflow);
        assert!(overflow_str.contains("Overflow"));

        let encoding_str = format!("{:?}", BigIntError::InvalidEncoding);
        assert!(encoding_str.contains("InvalidEncoding"));

        let div_zero_str = format!("{:?}", BigIntError::DivisionByZero);
        assert!(div_zero_str.contains("DivisionByZero"));

        let modulus_str = format!("{:?}", BigIntError::InvalidModulus);
        assert!(modulus_str.contains("InvalidModulus"));

        let state_str = format!("{:?}", BigIntError::InvalidState);
        assert!(state_str.contains("InvalidState"));
    }

    #[test]
    fn test_error_equality() {
        // Test that errors are equal to themselves
        assert_eq!(BigIntError::Overflow, BigIntError::Overflow);
        assert_eq!(BigIntError::InvalidEncoding, BigIntError::InvalidEncoding);
        assert_eq!(BigIntError::DivisionByZero, BigIntError::DivisionByZero);
        assert_eq!(BigIntError::InvalidModulus, BigIntError::InvalidModulus);
        assert_eq!(BigIntError::InvalidState, BigIntError::InvalidState);

        // Test that different errors are not equal
        assert_ne!(BigIntError::Overflow, BigIntError::InvalidEncoding);
        assert_ne!(BigIntError::DivisionByZero, BigIntError::InvalidModulus);
        assert_ne!(BigIntError::InvalidModulus, BigIntError::InvalidState);
    }

    #[test]
    fn test_error_clone() {
        // Test that errors can be cloned
        let original = BigIntError::Overflow;
        let cloned = original.clone();
        assert_eq!(original, cloned);
    }

    #[test]
    fn test_error_copy() {
        // Test that errors implement Copy
        let original = BigIntError::InvalidEncoding;
        let copied = original; // This uses Copy, not Clone
        assert_eq!(original, copied);
    }

    #[test]
    fn test_result_type_alias() {
        // Test that the Result type alias works
        let ok_result: Result<i32> = Ok(42);
        assert_eq!(ok_result.unwrap(), 42);

        let err_result: Result<i32> = Err(BigIntError::Overflow);
        assert!(err_result.is_err());
        assert_eq!(err_result.unwrap_err(), BigIntError::Overflow);
    }

}
