use serde::Serialize;
use std::fmt;

/// Numeric error codes for API and WebSocket responses.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
pub enum ApiErrorCode {
    // General (1xxx)
    InternalError = 1000,
    InvalidRequest = 1001,
    ParseError = 1002,
    RateLimitExceeded = 1003,
    GeoRestricted = 1004,

    // Market (2xxx)
    MarketNotFound = 2000,
    MarketPaused = 2001,

    // Order (3xxx)
    OrderNotFound = 3000,

    // Account/Session (4xxx)
    InvalidSignature = 4000,
    WhitelistNotConfigured = 4003,

    // Trade (5xxx)
    TradeNotFound = 5000,
    InvalidTradeCount = 5001,

    // Subscription/WebSocket (6xxx)
    AlreadySubscribed = 6000,
    TooManySubscriptions = 6001,
    SubscriptionError = 6002,

    // Validation (7xxx)
    InvalidAmount = 7000,
    InvalidTimeRange = 7001,
    InvalidPagination = 7002,
    NoActionsProvided = 7003,
    TooManyActions = 7004,

    // Block/Events (8xxx)
    BlockNotFound = 8000,
    EventsNotFound = 8001,
}

#[derive(Debug)]
pub struct ApiError {
    pub code: ApiErrorCode,
    pub message: String,
}

impl ApiError {
    pub fn new(code: ApiErrorCode, message: impl Into<String>) -> Self {
        Self {
            code,
            message: message.into(),
        }
    }

    pub fn error_code_number(&self) -> u32 {
        self.code as u32
    }
}

impl fmt::Display for ApiError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.message)
    }
}

impl std::error::Error for ApiError {}

/// Extract `ApiError` from an `anyhow::Error`, returning the code and message.
/// Falls back to `InternalError` (1000) if the error is not an `ApiError`.
pub fn extract_api_error(err: &anyhow::Error) -> (u32, String) {
    match err.downcast_ref::<ApiError>() {
        Some(api_err) => (api_err.error_code_number(), api_err.message.clone()),
        None => (ApiErrorCode::InternalError as u32, err.to_string()),
    }
}
