use std::fmt;

#[derive(Debug)]
pub enum KnafehError {
    /// QUIC/HTTP3 transport error.
    Transport(String),
    /// Serialization or deserialization error.
    Codec(String),
    /// Service-level error (handler returned an error).
    Service {
        code: RpcStatusCode,
        message: String,
    },
    /// Request timed out.
    Timeout,
    /// Invalid or malformed RPC message.
    InvalidMessage(String),
    /// The underlying connection was closed.
    ConnectionClosed,
    /// TLS configuration error.
    Tls(String),
    /// I/O error.
    Io(std::io::Error),
}

/// Numeric status codes for RPC responses, modeled after gRPC status codes.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum RpcStatusCode {
    Ok = 0,
    Cancelled = 1,
    Unknown = 2,
    InvalidArgument = 3,
    DeadlineExceeded = 4,
    NotFound = 5,
    AlreadyExists = 6,
    PermissionDenied = 7,
    ResourceExhausted = 8,
    FailedPrecondition = 9,
    Aborted = 10,
    OutOfRange = 11,
    Unimplemented = 12,
    Internal = 13,
    Unavailable = 14,
    DataLoss = 15,
    Unauthenticated = 16,
}

impl fmt::Display for KnafehError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Transport(msg) => write!(f, "transport error: {msg}"),
            Self::Codec(msg) => write!(f, "codec error: {msg}"),
            Self::Service { code, message } => {
                write!(f, "service error ({code:?}): {message}")
            }
            Self::Timeout => write!(f, "request timed out"),
            Self::InvalidMessage(msg) => write!(f, "invalid message: {msg}"),
            Self::ConnectionClosed => write!(f, "connection closed"),
            Self::Tls(msg) => write!(f, "TLS error: {msg}"),
            Self::Io(err) => write!(f, "I/O error: {err}"),
        }
    }
}

impl std::error::Error for KnafehError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            Self::Io(err) => Some(err),
            _ => None,
        }
    }
}

impl From<std::io::Error> for KnafehError {
    fn from(err: std::io::Error) -> Self {
        Self::Io(err)
    }
}

impl From<serde_json::Error> for KnafehError {
    fn from(err: serde_json::Error) -> Self {
        Self::Codec(err.to_string())
    }
}

#[cfg(feature = "python")]
impl From<KnafehError> for pyo3::PyErr {
    fn from(err: KnafehError) -> pyo3::PyErr {
        use pyo3::exceptions::*;
        match &err {
            KnafehError::Transport(_) => PyConnectionError::new_err(err.to_string()),
            KnafehError::Codec(_) => PyValueError::new_err(err.to_string()),
            KnafehError::Service { .. } => PyRuntimeError::new_err(err.to_string()),
            KnafehError::Timeout => PyTimeoutError::new_err(err.to_string()),
            KnafehError::InvalidMessage(_) => PyValueError::new_err(err.to_string()),
            KnafehError::ConnectionClosed => PyConnectionError::new_err(err.to_string()),
            KnafehError::Tls(_) => PyRuntimeError::new_err(err.to_string()),
            KnafehError::Io(_) => PyOSError::new_err(err.to_string()),
        }
    }
}

impl RpcStatusCode {
    pub fn from_u8(value: u8) -> Self {
        match value {
            0 => Self::Ok,
            1 => Self::Cancelled,
            3 => Self::InvalidArgument,
            4 => Self::DeadlineExceeded,
            5 => Self::NotFound,
            6 => Self::AlreadyExists,
            7 => Self::PermissionDenied,
            8 => Self::ResourceExhausted,
            9 => Self::FailedPrecondition,
            10 => Self::Aborted,
            11 => Self::OutOfRange,
            12 => Self::Unimplemented,
            13 => Self::Internal,
            14 => Self::Unavailable,
            15 => Self::DataLoss,
            16 => Self::Unauthenticated,
            _ => Self::Unknown,
        }
    }
}
