use std::error::Error;
use std::fmt::{Display, Formatter, Result as FmtResult};
use std::io;

use efdn_telem::metrics::MetricsError;

/// Telemetry gateway failure
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum GatewayError {
    /// The listener could not bind its configured address.
    Bind(String),
    /// Gateway configuration failed validation.
    Config(String),
    /// The bind address string was invalid.
    InvalidBind,
    /// A health code had invalid syntax.
    InvalidHealthCode,
    /// A health gate name had invalid syntax.
    InvalidHealthGateName,
    /// Prometheus metrics failed to render.
    Metrics(MetricsError),
    /// A gateway task failed while running or shutting down.
    ServerTask(String),
    /// Gateway shutdown failed.
    Shutdown,
}

impl GatewayError {
    /// Creates a bind error from an I/O error.
    pub fn bind(error: io::Error) -> Self {
        Self::Bind(error.to_string())
    }
}

impl Display for GatewayError {
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
        match self {
            Self::Bind(error) => write!(
                f,
                "failed to bind telemetry gateway\nplease try: set gateway.bind to an available address\nreason: {error}"
            ),
            Self::Config(error) => write!(
                f,
                "invalid telemetry gateway config\nplease try: fix gateway config before startup\nreason: {error}"
            ),
            Self::InvalidBind => f.write_str(
                "invalid telemetry gateway bind address\nplease try: use an address such as 127.0.0.1:9100",
            ),
            Self::InvalidHealthCode => f.write_str(
                "invalid health code\nplease try: use lower-case code text such as connecting or store.unavailable",
            ),
            Self::InvalidHealthGateName => f.write_str(
                "invalid health gate name\nplease try: use lower_snake_case text such as store or nats_bus",
            ),
            Self::Metrics(error) => Display::fmt(error, f),
            Self::ServerTask(error) => write!(
                f,
                "telemetry gateway task failed\nplease try: inspect the gateway listener task\nreason: {error}"
            ),
            Self::Shutdown => f.write_str(
                "telemetry gateway shutdown failed\nplease try: call shutdown once from the service lifecycle",
            ),
        }
    }
}

impl Error for GatewayError {}

impl From<MetricsError> for GatewayError {
    fn from(error: MetricsError) -> Self {
        Self::Metrics(error)
    }
}

#[cfg(feature = "errors")]
impl miette::Diagnostic for GatewayError {
    fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
        Some(Box::new(match self {
            Self::Bind(_) => "telem::gateway::bind",
            Self::Config(_) => "telem::gateway::config",
            Self::InvalidBind => "telem::gateway::invalid_bind",
            Self::InvalidHealthCode => "telem::gateway::invalid_health_code",
            Self::InvalidHealthGateName => "telem::gateway::invalid_health_gate_name",
            Self::Metrics(_) => "telem::gateway::metrics",
            Self::ServerTask(_) => "telem::gateway::server_task",
            Self::Shutdown => "telem::gateway::shutdown",
        }))
    }

    fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
        Some(Box::new(match self {
            Self::Bind(_) => "check that no other process owns the configured gateway port",
            Self::Config(_) => "validate gateway config before starting the service",
            Self::InvalidBind => "use host:port syntax",
            Self::InvalidHealthCode => "use lower-case words separated by dots or underscores",
            Self::InvalidHealthGateName => "use a short lower_snake_case gate name",
            Self::Metrics(_) => "initialize telemetry metrics before attaching the handle",
            Self::ServerTask(_) => "treat gateway task failure as service startup failure",
            Self::Shutdown => "own shutdown from one lifecycle controller",
        }))
    }
}
