use std::fmt::{Display, Formatter, Result as FmtResult};
use std::str::FromStr;

use serde::Serialize;

use crate::error::GatewayError;

/// Process health phase used by health endpoints.
#[repr(u8)]
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum HealthPhase {
    /// The process is starting and not ready yet.
    Starting = 0,
    /// The process is live and may be ready.
    Live = 1,
    /// The process is shutting down.
    Draining = 2,
    /// The process should be restarted.
    Dead = 3,
}

impl HealthPhase {
    /// Converts a stored numeric phase to a health phase.
    pub fn from_u8(value: u8) -> Self {
        match value {
            0 => Self::Starting,
            1 => Self::Live,
            2 => Self::Draining,
            _ => Self::Dead,
        }
    }
}

/// Lowercase machine-readable health reason.
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct HealthCode(String);

impl HealthCode {
    /// Creates a health code from lower-case words, digits, underscores, or dots.
    ///
    /// # Errors
    /// [`GatewayError`] if the value is empty or contains invalid characters.
    pub fn new(value: impl Into<String>) -> Result<Self, GatewayError> {
        let value = value.into();
        if checked_segmented(&value, true) {
            return Ok(Self(value));
        }
        Err(GatewayError::InvalidHealthCode)
    }

    /// Creates a health code from a static trusted value.
    ///
    /// # Panics
    /// Panics when `value` is not a valid health code.
    pub fn from_static(value: &'static str) -> Self {
        Self::new(value).unwrap_or_else(|error| panic!("{error}"))
    }

    /// Returns the health code string.
    pub fn as_str(&self) -> &str {
        &self.0
    }
}

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

impl FromStr for HealthCode {
    type Err = GatewayError;

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

impl Serialize for HealthCode {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        serializer.serialize_str(self.as_str())
    }
}

/// Lowercase readiness gate name.
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize)]
pub struct HealthGateName(String);

impl HealthGateName {
    /// Creates a gate name from lower_snake_case text.
    ///
    /// # Errors
    /// [`GatewayError`] if the value is empty or contains invalid characters.
    pub fn new(value: impl Into<String>) -> Result<Self, GatewayError> {
        let value = value.into();
        if checked_segmented(&value, false) {
            return Ok(Self(value));
        }
        Err(GatewayError::InvalidHealthGateName)
    }

    /// Creates a gate name from a static trusted value.
    ///
    /// # Panics
    /// Panics when `value` is not a valid gate name.
    pub fn from_static(value: &'static str) -> Self {
        Self::new(value).unwrap_or_else(|error| panic!("{error}"))
    }

    /// Returns the gate name string.
    pub fn as_str(&self) -> &str {
        &self.0
    }
}

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

impl FromStr for HealthGateName {
    type Err = GatewayError;

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

/// Readiness state for one dependency gate.
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case", tag = "status", content = "code")]
pub enum HealthGateStatus {
    /// The dependency is ready.
    Pass,
    /// The dependency is degraded but still usable.
    Warn(HealthCode),
    /// The dependency blocks readiness.
    Fail(HealthCode),
}

impl HealthGateStatus {
    /// Returns true when this gate should allow readiness.
    pub fn allows_readiness(&self) -> bool {
        matches!(self, Self::Pass | Self::Warn(_))
    }

    /// Returns the numeric status value used by Prometheus gauges.
    pub fn metric_value(&self) -> f64 {
        match self {
            Self::Pass => 2.0,
            Self::Warn(_) => 1.0,
            Self::Fail(_) => 0.0,
        }
    }
}

/// JSON-ready process health report.
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct HealthReport {
    phase: HealthPhase,
    live: bool,
    ready: bool,
    started: bool,
    uptime_secs: u64,
    #[serde(skip_serializing_if = "Option::is_none")]
    dead_code: Option<HealthCode>,
    gates: Vec<HealthGateReport>,
}

impl HealthReport {
    /// Creates a health report from endpoint state.
    pub fn new(
        phase: HealthPhase,
        live: bool,
        ready: bool,
        started: bool,
        uptime_secs: u64,
        dead_code: Option<HealthCode>,
        gates: Vec<HealthGateReport>,
    ) -> Self {
        Self {
            phase,
            live,
            ready,
            started,
            uptime_secs,
            dead_code,
            gates,
        }
    }

    /// Returns the current process health phase.
    pub fn phase(&self) -> HealthPhase {
        self.phase
    }

    /// Returns true while liveness should pass.
    pub fn is_live(&self) -> bool {
        self.live
    }

    /// Returns true while readiness should pass.
    pub fn is_ready(&self) -> bool {
        self.ready
    }

    /// Returns true after startup completed once.
    pub fn is_started(&self) -> bool {
        self.started
    }

    /// Returns all registered readiness gates.
    pub fn gates(&self) -> &[HealthGateReport] {
        &self.gates
    }
}

/// JSON-ready readiness gate report.
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct HealthGateReport {
    name: HealthGateName,
    #[serde(flatten)]
    status: HealthGateStatus,
}

impl HealthGateReport {
    /// Creates a readiness gate report.
    pub fn new(name: HealthGateName, status: HealthGateStatus) -> Self {
        Self { name, status }
    }

    /// Returns true when this gate allows readiness.
    pub fn is_ready(&self) -> bool {
        self.status.allows_readiness()
    }

    /// Returns the gate name.
    pub fn name(&self) -> &HealthGateName {
        &self.name
    }

    /// Returns the gate status.
    pub fn status(&self) -> &HealthGateStatus {
        &self.status
    }
}

fn checked_segmented(value: &str, allow_dot: bool) -> bool {
    let mut chars = value.chars();
    let Some(first) = chars.next() else {
        return false;
    };
    first.is_ascii_lowercase()
        && chars.all(|ch| {
            ch.is_ascii_lowercase() || ch.is_ascii_digit() || ch == '_' || (allow_dot && ch == '.')
        })
}
