//! Error types for Synor contracts.

use alloc::string::String;
use core::fmt;

/// Result type for contract operations.
pub type Result<T> = core::result::Result<T, Error>;

/// Contract error types.
#[derive(Clone, Debug)]
pub enum Error {
    /// Operation failed.
    Failed(String),

    /// Invalid method called.
    InvalidMethod,

    /// Invalid arguments provided.
    InvalidArgs(String),

    /// Insufficient balance.
    InsufficientBalance { required: u64, available: u64 },

    /// Unauthorized operation.
    Unauthorized,

    /// Not found (storage key, etc.).
    NotFound(String),

    /// Already exists.
    AlreadyExists(String),

    /// Overflow in arithmetic operation.
    Overflow,

    /// Underflow in arithmetic operation.
    Underflow,

    /// Division by zero.
    DivisionByZero,

    /// Storage error.
    StorageError(String),

    /// Serialization/deserialization error.
    SerializationError(String),

    /// Cross-contract call failed.
    CallFailed(String),

    /// Contract is paused.
    Paused,

    /// Reentrancy detected.
    Reentrancy,

    /// Custom error with code.
    Custom { code: u32, message: String },
}

impl Error {
    /// Creates a new failed error.
    pub fn failed(msg: impl Into<String>) -> Self {
        Error::Failed(msg.into())
    }

    /// Creates a new invalid args error.
    pub fn invalid_args(msg: impl Into<String>) -> Self {
        Error::InvalidArgs(msg.into())
    }

    /// Creates a new not found error.
    pub fn not_found(msg: impl Into<String>) -> Self {
        Error::NotFound(msg.into())
    }

    /// Creates a new already exists error.
    pub fn already_exists(msg: impl Into<String>) -> Self {
        Error::AlreadyExists(msg.into())
    }

    /// Creates a new storage error.
    pub fn storage(msg: impl Into<String>) -> Self {
        Error::StorageError(msg.into())
    }

    /// Creates a new serialization error.
    pub fn serialization(msg: impl Into<String>) -> Self {
        Error::SerializationError(msg.into())
    }

    /// Creates a new call failed error.
    pub fn call_failed(msg: impl Into<String>) -> Self {
        Error::CallFailed(msg.into())
    }

    /// Creates a custom error.
    pub fn custom(code: u32, msg: impl Into<String>) -> Self {
        Error::Custom {
            code,
            message: msg.into(),
        }
    }

    /// Returns the error code.
    pub fn code(&self) -> u32 {
        match self {
            Error::Failed(_) => 1,
            Error::InvalidMethod => 2,
            Error::InvalidArgs(_) => 3,
            Error::InsufficientBalance { .. } => 4,
            Error::Unauthorized => 5,
            Error::NotFound(_) => 6,
            Error::AlreadyExists(_) => 7,
            Error::Overflow => 8,
            Error::Underflow => 9,
            Error::DivisionByZero => 10,
            Error::StorageError(_) => 11,
            Error::SerializationError(_) => 12,
            Error::CallFailed(_) => 13,
            Error::Paused => 14,
            Error::Reentrancy => 15,
            Error::Custom { code, .. } => *code,
        }
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Error::Failed(msg) => write!(f, "Failed: {}", msg),
            Error::InvalidMethod => write!(f, "Invalid method"),
            Error::InvalidArgs(msg) => write!(f, "Invalid arguments: {}", msg),
            Error::InsufficientBalance {
                required,
                available,
            } => {
                write!(
                    f,
                    "Insufficient balance: required {}, available {}",
                    required, available
                )
            }
            Error::Unauthorized => write!(f, "Unauthorized"),
            Error::NotFound(msg) => write!(f, "Not found: {}", msg),
            Error::AlreadyExists(msg) => write!(f, "Already exists: {}", msg),
            Error::Overflow => write!(f, "Arithmetic overflow"),
            Error::Underflow => write!(f, "Arithmetic underflow"),
            Error::DivisionByZero => write!(f, "Division by zero"),
            Error::StorageError(msg) => write!(f, "Storage error: {}", msg),
            Error::SerializationError(msg) => write!(f, "Serialization error: {}", msg),
            Error::CallFailed(msg) => write!(f, "Call failed: {}", msg),
            Error::Paused => write!(f, "Contract is paused"),
            Error::Reentrancy => write!(f, "Reentrancy detected"),
            Error::Custom { code, message } => write!(f, "Error {}: {}", code, message),
        }
    }
}

/// Helper trait for converting Results to contract Results.
pub trait IntoContractResult<T> {
    /// Converts to a contract Result.
    fn into_contract_result(self) -> Result<T>;
}

impl<T, E: fmt::Display> IntoContractResult<T> for core::result::Result<T, E> {
    fn into_contract_result(self) -> Result<T> {
        self.map_err(|e| Error::failed(alloc::format!("{}", e)))
    }
}

/// Macro for creating custom errors.
#[macro_export]
macro_rules! contract_error {
    ($code:expr, $($arg:tt)*) => {
        $crate::Error::custom($code, alloc::format!($($arg)*))
    };
}

/// Macro for early return on error condition.
#[macro_export]
macro_rules! require {
    ($cond:expr, $err:expr) => {
        if !$cond {
            return Err($err);
        }
    };
    ($cond:expr) => {
        if !$cond {
            return Err($crate::Error::failed("Requirement not met"));
        }
    };
}

/// Macro for checking authorization.
#[macro_export]
macro_rules! require_auth {
    ($expected:expr) => {
        if $crate::context::caller() != $expected {
            return Err($crate::Error::Unauthorized);
        }
    };
}
