use std::fmt::{Display, Formatter, Result as FmtResult};
use std::net::SocketAddr;
use std::str::FromStr;
use std::time::Duration;

use crate::error::GatewayError;

/// Telemetry gateway configuration
#[cfg(feature = "config")]
#[efdn_macros::config(runtime = efdn_config)]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct GatewayConfig {
    /// Enables the HTTP gateway listener.
    #[default("enabled")]
    pub enabled: GatewayEnabled,
    /// Socket address used by the HTTP listener.
    #[env]
    #[default("127.0.0.1:0")]
    pub bind: GatewayBind,
    /// Enables or disables the Prometheus `/metrics` route.
    #[default("enabled")]
    pub metrics: GatewayMetricsRoute,
    /// Upper bound for each gateway request.
    #[default("2s")]
    pub request_timeout: GatewayRequestTimeout,
}

/// Telemetry gateway configuration
#[cfg(not(feature = "config"))]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct GatewayConfig {
    /// Enables the HTTP gateway listener.
    pub enabled: GatewayEnabled,
    /// Socket address used by the HTTP listener.
    pub bind: GatewayBind,
    /// Enables or disables the Prometheus `/metrics` route.
    pub metrics: GatewayMetricsRoute,
    /// Upper bound for each gateway request.
    pub request_timeout: GatewayRequestTimeout,
}

impl Default for GatewayConfig {
    fn default() -> Self {
        Self {
            enabled: GatewayEnabled::Enabled,
            bind: GatewayBind::loopback_ephemeral(),
            metrics: GatewayMetricsRoute::Enabled,
            request_timeout: GatewayRequestTimeout::from_secs(2),
        }
    }
}

impl GatewayConfig {
    /// Validates manual gateway configuration.
    ///
    /// # Errors
    /// [`GatewayConfigValueError`] when a timeout value is invalid.
    pub fn validate(&self) -> Result<(), GatewayConfigValueError> {
        if self.request_timeout.get().is_zero() {
            return Err(GatewayConfigValueError::new(
                "GatewayRequestTimeout",
                "0s",
                "a positive duration such as 2s",
            ));
        }
        Ok(())
    }
}

/// Telemetry gateway listener state
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum GatewayEnabled {
    /// Start the gateway listener.
    Enabled,
    /// Skip binding the gateway listener.
    Disabled,
}

impl GatewayEnabled {
    /// Returns true when the gateway listener should start.
    pub const fn is_enabled(self) -> bool {
        matches!(self, Self::Enabled)
    }
}

impl Display for GatewayEnabled {
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
        f.write_str(match self {
            Self::Enabled => "enabled",
            Self::Disabled => "disabled",
        })
    }
}

impl FromStr for GatewayEnabled {
    type Err = GatewayConfigValueError;

    fn from_str(value: &str) -> Result<Self, Self::Err> {
        match value {
            "enabled" => Ok(Self::Enabled),
            "disabled" => Ok(Self::Disabled),
            _ => Err(GatewayConfigValueError::new(
                "GatewayEnabled",
                value,
                "enabled, disabled",
            )),
        }
    }
}

#[cfg(feature = "config")]
impl<'de> serde::Deserialize<'de> for GatewayEnabled {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        deserialize_from_str(deserializer)
    }
}

/// Telemetry gateway socket address
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct GatewayBind(SocketAddr);

impl GatewayBind {
    /// Creates a bind address wrapper.
    pub fn new(value: SocketAddr) -> Self {
        Self(value)
    }

    /// Creates a loopback bind address with an ephemeral port.
    pub fn loopback_ephemeral() -> Self {
        Self(([127, 0, 0, 1], 0).into())
    }

    /// Returns the socket address.
    pub fn socket_addr(self) -> SocketAddr {
        self.0
    }
}

impl Display for GatewayBind {
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
        Display::fmt(&self.0, f)
    }
}

impl FromStr for GatewayBind {
    type Err = GatewayError;

    fn from_str(value: &str) -> Result<Self, Self::Err> {
        value
            .parse()
            .map(Self)
            .map_err(|_| GatewayError::InvalidBind)
    }
}

#[cfg(feature = "config")]
impl<'de> serde::Deserialize<'de> for GatewayBind {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        deserialize_from_str(deserializer)
    }
}

/// Metrics route state
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum GatewayMetricsRoute {
    /// Serve Prometheus metrics at `/metrics`.
    Enabled,
    /// Return `404` from `/metrics`.
    Disabled,
}

impl GatewayMetricsRoute {
    /// Returns true when `/metrics` should serve data.
    pub const fn is_enabled(self) -> bool {
        matches!(self, Self::Enabled)
    }
}

impl Display for GatewayMetricsRoute {
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
        f.write_str(match self {
            Self::Enabled => "enabled",
            Self::Disabled => "disabled",
        })
    }
}

impl FromStr for GatewayMetricsRoute {
    type Err = GatewayConfigValueError;

    fn from_str(value: &str) -> Result<Self, Self::Err> {
        match value {
            "enabled" => Ok(Self::Enabled),
            "disabled" => Ok(Self::Disabled),
            _ => Err(GatewayConfigValueError::new(
                "GatewayMetricsRoute",
                value,
                "enabled, disabled",
            )),
        }
    }
}

#[cfg(feature = "config")]
impl<'de> serde::Deserialize<'de> for GatewayMetricsRoute {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        deserialize_from_str(deserializer)
    }
}

/// Per-request gateway timeout
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct GatewayRequestTimeout(Duration);

impl GatewayRequestTimeout {
    /// Creates a request timeout.
    ///
    /// # Errors
    /// [`GatewayConfigValueError`] when `value` is zero.
    pub fn new(value: Duration) -> Result<Self, GatewayConfigValueError> {
        if !value.is_zero() {
            return Ok(Self(value));
        }
        Err(GatewayConfigValueError::new(
            "GatewayRequestTimeout",
            "0s",
            "a positive duration such as 2s",
        ))
    }

    /// Creates a request timeout from seconds.
    pub const fn from_secs(value: u64) -> Self {
        Self(Duration::from_secs(value))
    }

    /// Returns the timeout duration.
    pub const fn get(self) -> Duration {
        self.0
    }
}

impl Display for GatewayRequestTimeout {
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
        write!(f, "{:?}", self.0)
    }
}

impl FromStr for GatewayRequestTimeout {
    type Err = GatewayConfigValueError;

    fn from_str(value: &str) -> Result<Self, Self::Err> {
        parse_duration(value).and_then(Self::new)
    }
}

#[cfg(feature = "config")]
impl<'de> serde::Deserialize<'de> for GatewayRequestTimeout {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        deserialize_from_str(deserializer)
    }
}

/// Invalid gateway config value
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct GatewayConfigValueError {
    type_name: &'static str,
    value: String,
    expected: &'static str,
}

impl GatewayConfigValueError {
    fn new(type_name: &'static str, value: impl Into<String>, expected: &'static str) -> Self {
        Self {
            type_name,
            value: value.into(),
            expected,
        }
    }
}

impl Display for GatewayConfigValueError {
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
        write!(
            f,
            "invalid {} `{}`; please try one of: {}",
            self.type_name, self.value, self.expected
        )
    }
}

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

#[cfg(feature = "config")]
fn parse_duration(value: &str) -> Result<Duration, GatewayConfigValueError> {
    humantime::parse_duration(value).map_err(|_| {
        GatewayConfigValueError::new(
            "GatewayRequestTimeout",
            value,
            "a duration such as 2s, 500ms, or 1m",
        )
    })
}

#[cfg(not(feature = "config"))]
fn parse_duration(value: &str) -> Result<Duration, GatewayConfigValueError> {
    let seconds = value.parse::<u64>().map_err(|_| {
        GatewayConfigValueError::new("GatewayRequestTimeout", value, "whole seconds such as 2")
    })?;
    Ok(Duration::from_secs(seconds))
}

#[cfg(feature = "config")]
fn deserialize_from_str<'de, D, T>(deserializer: D) -> Result<T, D::Error>
where
    D: serde::Deserializer<'de>,
    T: FromStr,
    T::Err: Display,
{
    let value = <String as serde::Deserialize>::deserialize(deserializer)?;
    value.parse().map_err(serde::de::Error::custom)
}
