//! cTrader Open API error types.

use std::fmt;

/// Errors from the cTrader Open API client.
#[derive(Debug, thiserror::Error)]
pub enum CTraderError {
    /// TCP or TLS connection failed.
    #[error("connection failed: {0}")]
    ConnectionFailed(String),

    /// TLS handshake error.
    #[error("TLS error: {0}")]
    TlsError(String),

    /// OAuth2 token exchange failed.
    #[error("OAuth error: {0}")]
    OAuthError(String),

    /// Application-level authentication rejected.
    #[error("application auth failed: {0}")]
    AppAuthFailed(String),

    /// Account-level authentication rejected.
    #[error("account auth failed: {0}")]
    AccountAuthFailed(String),

    /// Protobuf encode/decode error.
    #[error("protocol error: {0}")]
    ProtocolError(String),

    /// Codec framing error.
    #[error("codec error: {0}")]
    CodecError(String),

    /// Rate limit exceeded (50 req/s or 5 req/s for historical).
    #[error("rate limit exceeded for {bucket}")]
    RateLimitExceeded {
        /// Which bucket was exhausted.
        bucket: RateLimitBucket,
    },

    /// Request timed out waiting for a response.
    #[error("request timed out after {0:?}")]
    Timeout(std::time::Duration),

    /// Server returned an error payload.
    #[error("server error {code}: {description}")]
    ServerError {
        /// cTrader error code.
        code: String,
        /// Human-readable description.
        description: String,
    },

    /// Client is not connected.
    #[error("not connected")]
    NotConnected,

    /// Symbol not found in the cached symbol list.
    #[error("unknown symbol: {0}")]
    UnknownSymbol(String),

    /// I/O error (required by `tokio_util::codec`).
    #[error("io error: {0}")]
    Io(String),
}

impl From<std::io::Error> for CTraderError {
    fn from(e: std::io::Error) -> Self {
        Self::Io(e.to_string())
    }
}

impl From<prost::DecodeError> for CTraderError {
    fn from(e: prost::DecodeError) -> Self {
        Self::ProtocolError(format!("protobuf decode error: {e}"))
    }
}

/// Rate-limit bucket categories.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RateLimitBucket {
    /// Standard messages: 50 per second.
    NonHistorical,
    /// Historical data requests: 5 per second.
    Historical,
}

impl fmt::Display for RateLimitBucket {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::NonHistorical => f.write_str("non-historical (50/s)"),
            Self::Historical => f.write_str("historical (5/s)"),
        }
    }
}

/// Convenience alias used throughout the crate.
pub type Result<T> = std::result::Result<T, CTraderError>;
