use thiserror::Error;

use crate::discovery::DiscoveryError;

/// etcd adapter result type.
pub type EtcdDiscoveryResult<T> = Result<T, EtcdDiscoveryError>;

/// etcd adapter errors.
#[derive(Debug, Error)]
pub enum EtcdDiscoveryError {
    /// Serialization failed.
    #[error("etcd discovery codec error: {0}")]
    Codec(#[from] serde_json::Error),

    /// No endpoints were configured.
    #[error("etcd discovery requires at least one endpoint")]
    MissingEndpoint,

    /// Adapter configuration is invalid.
    #[error("invalid etcd discovery configuration: {0}")]
    InvalidConfig(String),

    /// The registry was created without an etcd connection.
    #[error("etcd registry is not connected; call EtcdRegistry::connect before use")]
    NotConnected,

    /// The requested instance does not exist in etcd.
    #[error("etcd service `{service}` does not have instance `{id}`")]
    MissingInstance { service: String, id: String },

    /// An etcd key could not be normalized into a discovery instance key.
    #[error("invalid etcd discovery key `{0}`")]
    InvalidKey(String),

    /// An etcd operation timed out.
    #[error("etcd operation `{operation}` timed out")]
    Timeout { operation: &'static str },

    /// Lease creation, revocation, or keep-alive failed.
    #[error("etcd lease error: {0}")]
    Lease(String),

    /// Watch creation or streaming failed.
    #[error("etcd watch error: {0}")]
    Watch(String),

    /// Backend operation failed.
    #[error("etcd backend error: {0}")]
    Backend(String),
}

impl EtcdDiscoveryError {
    /// Converts an etcd adapter error into the stable discovery trait error type.
    pub(crate) fn into_discovery_error(self) -> DiscoveryError {
        match self {
            Self::MissingInstance { service, id } => {
                DiscoveryError::MissingInstance { service, id }
            }
            error => DiscoveryError::Resolve {
                host: "etcd".to_string(),
                message: error.to_string(),
            },
        }
    }
}
