use std::fmt;
use std::path::PathBuf;

/// A validated service name.
///
/// Service names must be non-empty and contain only alphanumeric characters,
/// hyphens, and underscores.
///
/// # Examples
///
/// ```ignore
/// use provctl_core::ServiceName;
///
/// let name = ServiceName::new("syntaxis-api")?;
/// assert_eq!(name.as_str(), "syntaxis-api");
/// ```
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ServiceName(String);

impl ServiceName {
    /// Creates a new service name with validation.
    ///
    /// # Errors
    ///
    /// Returns an error if the name is empty or contains invalid characters.
    ///
    /// Valid characters: `a-z`, `A-Z`, `0-9`, `-`, `_`
    pub fn new(name: &str) -> crate::ProvctlResult<Self> {
        if name.is_empty() {
            return Err(crate::ProvctlError::new(
                crate::ProvctlErrorKind::InvalidConfig {
                    detail: "Service name cannot be empty".to_string(),
                },
                "Validating service name".to_string(),
            ));
        }

        if !name
            .chars()
            .all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_')
        {
            return Err(crate::ProvctlError::new(
                crate::ProvctlErrorKind::InvalidConfig {
                    detail: format!(
                        "Service name '{}' contains invalid characters. \
                         Use alphanumeric, hyphens, or underscores only.",
                        name
                    ),
                },
                "Validating service name".to_string(),
            ));
        }

        Ok(Self(name.to_string()))
    }

    /// Returns the service name as a string slice.
    #[must_use]
    pub fn as_str(&self) -> &str {
        &self.0
    }
}

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

impl AsRef<str> for ServiceName {
    fn as_ref(&self) -> &str {
        &self.0
    }
}

/// The current status of a service/process.
///
/// # Examples
///
/// ```ignore
/// use provctl_core::ProcessStatus;
///
/// let running = ProcessStatus::Running { pid: 1234 };
/// match running {
///     ProcessStatus::Running { pid } => println!("PID: {}", pid),
///     _ => println!("Not running"),
/// }
/// ```
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ProcessStatus {
    /// Process is currently running with given PID.
    Running { pid: u32 },

    /// Process exited with the given exit code.
    Exited { code: i32 },

    /// Process was terminated by a signal.
    Terminated { signal: i32 },

    /// Process status is unknown.
    Unknown,

    /// Process is not running.
    NotRunning,
}

impl fmt::Display for ProcessStatus {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Running { pid } => write!(f, "Running (PID: {})", pid),
            Self::Exited { code } => write!(f, "Exited (code: {})", code),
            Self::Terminated { signal } => write!(f, "Terminated (signal: {})", signal),
            Self::Unknown => write!(f, "Unknown"),
            Self::NotRunning => write!(f, "Not running"),
        }
    }
}

impl ProcessStatus {
    /// Returns `true` if the process is currently running.
    #[must_use]
    pub fn is_running(&self) -> bool {
        matches!(self, Self::Running { .. })
    }

    /// Extracts the PID if the process is running.
    #[must_use]
    pub fn pid(&self) -> Option<u32> {
        match self {
            Self::Running { pid } => Some(*pid),
            _ => None,
        }
    }
}

/// Configuration for a managed service.
///
/// This struct defines how provctl should manage a service, including
/// the executable to run, arguments, working directory, and runtime options.
///
/// # Examples
///
/// ```ignore
/// use provctl_core::{ServiceDefinition, ServiceName};
/// use std::path::PathBuf;
///
/// let service = ServiceDefinition {
///     name: ServiceName::new("api")?,
///     binary: "/usr/local/bin/api".into(),
///     args: vec!["--config".to_string(), "/etc/api.toml".to_string()],
///     working_dir: Some("/opt/api".into()),
///     env_vars: Default::default(),
/// };
/// ```
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ServiceDefinition {
    /// Name of the service.
    pub name: ServiceName,

    /// Path to the binary to execute.
    pub binary: PathBuf,

    /// Command-line arguments to pass to the binary.
    pub args: Vec<String>,

    /// Optional working directory for the process.
    pub working_dir: Option<PathBuf>,

    /// Optional environment variables to set.
    pub env_vars: std::collections::HashMap<String, String>,
}

impl ServiceDefinition {
    /// Creates a new service definition.
    ///
    /// # Examples
    ///
    /// ```ignore
    /// use provctl_core::{ServiceDefinition, ServiceName};
    /// use std::path::PathBuf;
    ///
    /// let service = ServiceDefinition::new(
    ///     ServiceName::new("api")?,
    ///     "/usr/local/bin/api".into(),
    /// );
    /// ```
    pub fn new(name: ServiceName, binary: PathBuf) -> Self {
        Self {
            name,
            binary,
            args: Vec::new(),
            working_dir: None,
            env_vars: std::collections::HashMap::new(),
        }
    }

    /// Adds an argument to the service.
    ///
    /// # Examples
    ///
    /// ```ignore
    /// let mut service = ServiceDefinition::new(name, binary);
    /// service.with_arg("--port 3000");
    /// ```
    #[must_use]
    pub fn with_arg(mut self, arg: impl Into<String>) -> Self {
        self.args.push(arg.into());
        self
    }

    /// Adds multiple arguments to the service.
    #[must_use]
    pub fn with_args(mut self, args: Vec<String>) -> Self {
        self.args.extend(args);
        self
    }

    /// Sets the working directory.
    #[must_use]
    pub fn with_working_dir(mut self, dir: impl Into<PathBuf>) -> Self {
        self.working_dir = Some(dir.into());
        self
    }

    /// Adds an environment variable.
    #[must_use]
    pub fn with_env(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
        self.env_vars.insert(key.into(), value.into());
        self
    }
}

/// Health check configuration for a service.
///
/// Defines how to verify that a service is healthy after starting.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HealthCheck {
    /// URL to check for health (HTTP GET expected to return 200-299).
    pub url: Option<String>,

    /// Port to check if available (TCP connect).
    pub port: Option<u16>,

    /// Interval between health checks in seconds.
    pub interval_secs: u64,

    /// Timeout for each health check in seconds.
    pub timeout_secs: u64,

    /// Maximum number of failed checks before marking unhealthy.
    pub max_failures: u32,
}

impl Default for HealthCheck {
    fn default() -> Self {
        Self {
            url: None,
            port: None,
            interval_secs: 10,
            timeout_secs: 5,
            max_failures: 3,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_service_name_valid() {
        let name = ServiceName::new("valid-service_123").unwrap();
        assert_eq!(name.as_str(), "valid-service_123");
    }

    #[test]
    fn test_service_name_empty() {
        assert!(ServiceName::new("").is_err());
    }

    #[test]
    fn test_service_name_invalid_chars() {
        assert!(ServiceName::new("invalid@service").is_err());
        assert!(ServiceName::new("invalid.service").is_err());
        assert!(ServiceName::new("invalid service").is_err());
    }

    #[test]
    fn test_process_status_running() {
        let status = ProcessStatus::Running { pid: 1234 };
        assert!(status.is_running());
        assert_eq!(status.pid(), Some(1234));
    }

    #[test]
    fn test_process_status_not_running() {
        let status = ProcessStatus::NotRunning;
        assert!(!status.is_running());
        assert_eq!(status.pid(), None);
    }

    #[test]
    fn test_service_definition_builder() {
        let name = ServiceName::new("api").unwrap();
        let service = ServiceDefinition::new(name.clone(), "/bin/api".into())
            .with_arg("--port")
            .with_arg("3000")
            .with_working_dir("/opt/api")
            .with_env("DEBUG", "1");

        assert_eq!(service.name, name);
        assert_eq!(service.args.len(), 2);
        assert_eq!(service.args[0], "--port");
        assert_eq!(service.args[1], "3000");
        assert_eq!(service.working_dir, Some(PathBuf::from("/opt/api")));
        assert_eq!(service.env_vars.get("DEBUG"), Some(&"1".to_string()));
    }

    #[test]
    fn test_health_check_default() {
        let hc = HealthCheck::default();
        assert_eq!(hc.interval_secs, 10);
        assert_eq!(hc.timeout_secs, 5);
        assert_eq!(hc.max_failures, 3);
    }
}
