// Copyright (c) Subzero Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

//! Standard error codes and categories for Rialo CDK
//!
//! This module provides standardized error codes and categories that are
//! consistent across all language bindings (Python, TypeScript, etc.)

use serde::{Deserialize, Serialize};

/// Standard error categories used across all CDK bindings
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ErrorCategory {
    /// Input validation errors (invalid parameters, malformed data)
    InvalidInput,
    /// Network/RPC communication errors
    Network,
    /// Wallet operations errors (signing, key management)
    Wallet,
    /// Transaction construction and processing errors
    Transaction,
    /// Program deployment and invocation errors
    Program,
    /// Configuration management errors
    Config,
    /// Authentication and authorization errors
    Auth,
    /// Resource not found errors
    NotFound,
    /// Internal errors and unexpected conditions
    Internal,
}

/// Standard error codes with consistent numbering across bindings
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ErrorCode {
    // Input Validation Errors (1000-1999)
    /// Invalid byte array length
    InvalidByteLength = 1001,
    /// Invalid base58 string format
    InvalidBase58 = 1002,
    /// Invalid mnemonic phrase
    InvalidMnemonic = 1003,
    /// Empty or null input where value required
    EmptyInput = 1004,
    /// Parameter out of valid range
    OutOfRange = 1005,

    // Network/RPC Errors (2000-2999)
    /// RPC endpoint unreachable or timeout
    NetworkTimeout = 2001,
    /// RPC method not found or unsupported
    UnsupportedMethod = 2002,
    /// Invalid RPC response format
    InvalidResponse = 2003,
    /// Rate limit exceeded
    RateLimited = 2004,
    /// Authentication failed for RPC
    RpcAuthFailed = 2005,

    // Wallet Errors (3000-3999)
    /// Wallet not found
    WalletNotFound = 3001,
    /// Invalid password for wallet
    InvalidPassword = 3002,
    /// Wallet already exists
    WalletExists = 3003,
    /// Account not found in wallet
    AccountNotFound = 3004,
    /// Insufficient funds for operation
    InsufficientFunds = 3005,
    /// Signing operation failed
    SigningFailed = 3006,

    // Transaction Errors (4000-4999)
    /// Transaction too large
    TransactionTooLarge = 4001,
    /// Invalid transaction structure
    InvalidTransaction = 4002,
    /// Transaction simulation failed
    SimulationFailed = 4003,
    /// Blockhash expired
    BlockhashExpired = 4004,
    /// Insufficient fee for transaction
    InsufficientFee = 4005,

    // Program Errors (5000-5999)
    /// Program not found
    ProgramNotFound = 5001,
    /// Program deployment failed
    DeploymentFailed = 5002,
    /// Program invocation failed
    InvocationFailed = 5003,
    /// Resource already consumed
    ResourceConsumed = 5004,
    /// Program file invalid or corrupted
    InvalidProgramFile = 5005,

    // Configuration Errors (6000-6999)
    /// Configuration file not found
    ConfigNotFound = 6001,
    /// Invalid configuration format
    InvalidConfig = 6002,
    /// Permission denied for config operation
    ConfigPermissionDenied = 6003,

    // Feature/Platform Errors (7000-7999)
    /// Feature not available on current platform
    FeatureUnavailable = 7001,
    /// Operation not supported in WASM
    WasmUnsupported = 7002,
    /// File system operations not available
    FileSystemUnavailable = 7003,

    // Internal Errors (9000-9999)
    /// Unexpected internal error
    InternalError = 9001,
    /// Resource exhausted (memory, file handles, etc.)
    ResourceExhausted = 9002,
    /// Async operation failed
    AsyncOperationFailed = 9003,
}

impl ErrorCode {
    /// Get the category for this error code
    pub fn category(&self) -> ErrorCategory {
        match *self as u32 {
            1000..=1999 => ErrorCategory::InvalidInput,
            2000..=2999 => ErrorCategory::Network,
            3000..=3999 => ErrorCategory::Wallet,
            4000..=4999 => ErrorCategory::Transaction,
            5000..=5999 => ErrorCategory::Program,
            6000..=6999 => ErrorCategory::Config,
            7000..=7999 => ErrorCategory::Internal, // Feature availability
            9000..=9999 => ErrorCategory::Internal,
            _ => ErrorCategory::Internal,
        }
    }

    /// Get a human-readable description of the error code
    pub fn description(&self) -> &'static str {
        match self {
            ErrorCode::InvalidByteLength => "Invalid byte array length",
            ErrorCode::InvalidBase58 => "Invalid base58 string format",
            ErrorCode::InvalidMnemonic => "Invalid mnemonic phrase",
            ErrorCode::EmptyInput => "Empty or null input where value required",
            ErrorCode::OutOfRange => "Parameter out of valid range",

            ErrorCode::NetworkTimeout => "Network timeout or connection failed",
            ErrorCode::UnsupportedMethod => "RPC method not supported",
            ErrorCode::InvalidResponse => "Invalid RPC response format",
            ErrorCode::RateLimited => "Rate limit exceeded",
            ErrorCode::RpcAuthFailed => "RPC authentication failed",

            ErrorCode::WalletNotFound => "Wallet not found",
            ErrorCode::InvalidPassword => "Invalid password",
            ErrorCode::WalletExists => "Wallet already exists",
            ErrorCode::AccountNotFound => "Account not found",
            ErrorCode::InsufficientFunds => "Insufficient funds",
            ErrorCode::SigningFailed => "Signing operation failed",

            ErrorCode::TransactionTooLarge => "Transaction exceeds size limit",
            ErrorCode::InvalidTransaction => "Invalid transaction structure",
            ErrorCode::SimulationFailed => "Transaction simulation failed",
            ErrorCode::BlockhashExpired => "Blockhash has expired",
            ErrorCode::InsufficientFee => "Insufficient fee for transaction",

            ErrorCode::ProgramNotFound => "Program not found",
            ErrorCode::DeploymentFailed => "Program deployment failed",
            ErrorCode::InvocationFailed => "Program invocation failed",
            ErrorCode::ResourceConsumed => "Resource has been consumed",
            ErrorCode::InvalidProgramFile => "Invalid program file",

            ErrorCode::ConfigNotFound => "Configuration not found",
            ErrorCode::InvalidConfig => "Invalid configuration",
            ErrorCode::ConfigPermissionDenied => "Permission denied for configuration",

            ErrorCode::FeatureUnavailable => "Feature not available on this platform",
            ErrorCode::WasmUnsupported => "Operation not supported in WASM environment",
            ErrorCode::FileSystemUnavailable => "File system operations not available",

            ErrorCode::InternalError => "Internal error occurred",
            ErrorCode::ResourceExhausted => "Resource exhausted",
            ErrorCode::AsyncOperationFailed => "Async operation failed",
        }
    }

    /// Check if this is a retryable error
    pub fn is_retryable(&self) -> bool {
        matches!(
            self,
            ErrorCode::NetworkTimeout
                | ErrorCode::RateLimited
                | ErrorCode::ResourceExhausted
                | ErrorCode::AsyncOperationFailed
        )
    }

    /// Check if this indicates a client-side error (vs server-side)
    pub fn is_client_error(&self) -> bool {
        matches!(
            self.category(),
            ErrorCategory::InvalidInput | ErrorCategory::Wallet | ErrorCategory::Config
        )
    }
}

/// Standard error information that can be serialized across language boundaries
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ErrorInfo {
    /// The error code
    pub code: ErrorCode,
    /// Human-readable error message
    pub message: String,
    /// Additional context or details
    pub details: Option<String>,
    /// Whether the operation can be retried
    pub retryable: bool,
}

impl ErrorInfo {
    /// Create a new error info
    pub fn new(code: ErrorCode, message: String) -> Self {
        Self {
            retryable: code.is_retryable(),
            code,
            message,
            details: None,
        }
    }

    /// Create error info with additional details
    pub fn with_details(code: ErrorCode, message: String, details: String) -> Self {
        Self {
            retryable: code.is_retryable(),
            code,
            message,
            details: Some(details),
        }
    }

    /// Get the error category
    pub fn category(&self) -> ErrorCategory {
        self.code.category()
    }
}
