use std::error::Error;
use std::fmt;
use std::io;

/// Specific error kinds for provctl operations.
///
/// This enum categorizes different error scenarios that can occur during
/// service management operations.
#[derive(Debug)]
pub enum ProvctlErrorKind {
    /// Service not found in configuration or system.
    ServiceNotFound(String),

    /// Permission denied when accessing service or system resources.
    PermissionDenied {
        /// The operation that was denied.
        operation: String,
        /// Details about why it was denied.
        detail: String,
    },

    /// Service is already running.
    AlreadyRunning {
        /// Name of the service.
        service: String,
        /// Process ID of the running service.
        pid: u32,
    },

    /// Service is not currently running.
    NotRunning(String),

    /// Failed to read or parse configuration file.
    ConfigError {
        /// Path to the configuration file.
        path: String,
        /// Reason for the failure.
        reason: String,
    },

    /// Invalid service configuration.
    InvalidConfig {
        /// Details about the configuration error.
        detail: String,
    },

    /// PID file operation failed.
    PidFileError {
        /// Path to the PID file.
        path: String,
        /// Reason for the failure.
        reason: String,
    },

    /// System backend not available (e.g., systemd not available).
    BackendNotAvailable(String),

    /// Failed to spawn process.
    SpawnError {
        /// Name of the service.
        service: String,
        /// Reason for the failure.
        reason: String,
    },

    /// Health check failed or timed out.
    HealthCheckFailed {
        /// Name of the service.
        service: String,
        /// Reason for the failure.
        reason: String,
    },

    /// Input/Output error from system.
    Io(io::Error),

    /// Other unclassified errors.
    Other(String),
}

impl fmt::Display for ProvctlErrorKind {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::ServiceNotFound(name) => {
                write!(f, "Service not found: {}", name)
            }
            Self::PermissionDenied { operation, detail } => {
                write!(f, "Permission denied for {}: {}", operation, detail)
            }
            Self::AlreadyRunning { service, pid } => {
                write!(f, "Service {} is already running (PID: {})", service, pid)
            }
            Self::NotRunning(name) => {
                write!(f, "Service {} is not running", name)
            }
            Self::ConfigError { path, reason } => {
                write!(f, "Configuration error in {}: {}", path, reason)
            }
            Self::InvalidConfig { detail } => {
                write!(f, "Invalid service configuration: {}", detail)
            }
            Self::PidFileError { path, reason } => {
                write!(f, "PID file error at {}: {}", path, reason)
            }
            Self::BackendNotAvailable(name) => {
                write!(f, "Backend not available: {}", name)
            }
            Self::SpawnError { service, reason } => {
                write!(f, "Failed to spawn service {}: {}", service, reason)
            }
            Self::HealthCheckFailed { service, reason } => {
                write!(f, "Health check failed for {}: {}", service, reason)
            }
            Self::Io(e) => write!(f, "I/O error: {}", e),
            Self::Other(msg) => write!(f, "{}", msg),
        }
    }
}

/// Primary error type for provctl operations.
///
/// This struct captures error context including the error kind, additional context,
/// and the upstream error source for proper error chaining.
///
/// Following M-ERRORS-CANONICAL-STRUCTS pattern: use concrete struct with nested kind enum.
#[derive(Debug)]
pub struct ProvctlError {
    /// The specific kind of error.
    kind: ProvctlErrorKind,

    /// Additional context about what was happening when the error occurred.
    context: String,

    /// Source error from downstream operations.
    source: Option<Box<dyn Error + Send + Sync>>,
}

impl ProvctlError {
    /// Creates a new error with the given kind and context.
    ///
    /// # Examples
    ///
    /// ```ignore
    /// use provctl_core::ProvctlError;
    /// use provctl_core::ProvctlErrorKind;
    ///
    /// let error = ProvctlError::new(
    ///     ProvctlErrorKind::ServiceNotFound("api".to_string()),
    ///     "Looking for service in config".to_string(),
    /// );
    /// ```
    pub fn new(kind: ProvctlErrorKind, context: String) -> Self {
        Self {
            kind,
            context,
            source: None,
        }
    }

    /// Creates an error with source error attached.
    ///
    /// # Examples
    ///
    /// ```ignore
    /// use provctl_core::ProvctlError;
    /// use provctl_core::ProvctlErrorKind;
    /// use std::io;
    ///
    /// let io_err: io::Error = io::Error::last_os_error();
    /// let error = ProvctlError::with_source(
    ///     ProvctlErrorKind::Other("Failed".to_string()),
    ///     "Context".to_string(),
    ///     Box::new(io_err),
    /// );
    /// ```
    pub fn with_source(
        kind: ProvctlErrorKind,
        context: String,
        source: Box<dyn Error + Send + Sync>,
    ) -> Self {
        Self {
            kind,
            context,
            source: Some(source),
        }
    }

    /// Checks if this is a "service not found" error.
    ///
    /// # Examples
    ///
    /// ```ignore
    /// if error.is_service_not_found() {
    ///     eprintln!("Service does not exist");
    /// }
    /// ```
    #[must_use]
    pub fn is_service_not_found(&self) -> bool {
        matches!(self.kind, ProvctlErrorKind::ServiceNotFound(_))
    }

    /// Checks if this is a "not running" error.
    #[must_use]
    pub fn is_not_running(&self) -> bool {
        matches!(self.kind, ProvctlErrorKind::NotRunning(_))
    }

    /// Checks if this is a "permission denied" error.
    #[must_use]
    pub fn is_permission_denied(&self) -> bool {
        matches!(self.kind, ProvctlErrorKind::PermissionDenied { .. })
    }

    /// Checks if this is an "already running" error.
    #[must_use]
    pub fn is_already_running(&self) -> bool {
        matches!(self.kind, ProvctlErrorKind::AlreadyRunning { .. })
    }

    /// Gets the error kind.
    #[must_use]
    pub fn kind(&self) -> &ProvctlErrorKind {
        &self.kind
    }

    /// Gets the context string.
    #[must_use]
    pub fn context(&self) -> &str {
        &self.context
    }

    /// Extracts service name if error is about a specific service.
    #[must_use]
    pub fn service_name(&self) -> Option<&str> {
        match &self.kind {
            ProvctlErrorKind::ServiceNotFound(name) => Some(name),
            ProvctlErrorKind::AlreadyRunning { service, .. } => Some(service),
            ProvctlErrorKind::NotRunning(name) => Some(name),
            ProvctlErrorKind::SpawnError { service, .. } => Some(service),
            ProvctlErrorKind::HealthCheckFailed { service, .. } => Some(service),
            _ => None,
        }
    }
}

impl fmt::Display for ProvctlError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{} [{}]", self.kind, self.context)
    }
}

impl Error for ProvctlError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        self.source.as_ref().map(|e| e.as_ref() as &dyn Error)
    }
}

impl From<io::Error> for ProvctlError {
    fn from(err: io::Error) -> Self {
        Self {
            kind: ProvctlErrorKind::Io(err),
            context: "I/O operation failed".to_string(),
            source: None,
        }
    }
}

/// Convenience type alias for Results in provctl operations.
///
/// # Examples
///
/// ```ignore
/// use provctl_core::ProvctlResult;
///
/// fn start_service(name: &str) -> ProvctlResult<u32> {
///     // Returns either the PID or a ProvctlError
///     Ok(1234)
/// }
/// ```
pub type ProvctlResult<T> = std::result::Result<T, ProvctlError>;

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

    #[test]
    fn test_service_not_found_error() {
        let error = ProvctlError::new(
            ProvctlErrorKind::ServiceNotFound("api".to_string()),
            "Loading config".to_string(),
        );

        assert!(error.is_service_not_found());
        assert_eq!(error.service_name(), Some("api"));
        assert!(!error.is_not_running());
    }

    #[test]
    fn test_not_running_error() {
        let error = ProvctlError::new(
            ProvctlErrorKind::NotRunning("api".to_string()),
            "Checking process status".to_string(),
        );

        assert!(error.is_not_running());
        assert_eq!(error.service_name(), Some("api"));
    }

    #[test]
    fn test_permission_denied_error() {
        let error = ProvctlError::new(
            ProvctlErrorKind::PermissionDenied {
                operation: "start".to_string(),
                detail: "requires root".to_string(),
            },
            "Starting service".to_string(),
        );

        assert!(error.is_permission_denied());
    }

    #[test]
    fn test_already_running_error() {
        let error = ProvctlError::new(
            ProvctlErrorKind::AlreadyRunning {
                service: "api".to_string(),
                pid: 1234,
            },
            "Attempting to start service".to_string(),
        );

        assert!(error.is_already_running());
        assert_eq!(error.service_name(), Some("api"));
    }

    #[test]
    fn test_error_display() {
        let error = ProvctlError::new(
            ProvctlErrorKind::ServiceNotFound("test-service".to_string()),
            "Config loading".to_string(),
        );

        let display = format!("{}", error);
        assert!(display.contains("test-service"));
        assert!(display.contains("Config loading"));
    }

    #[test]
    fn test_error_with_source() {
        let io_err = io::Error::new(io::ErrorKind::NotFound, "file not found");
        let source_box: Box<dyn Error + Send + Sync> = Box::new(io_err);

        let error = ProvctlError::with_source(
            ProvctlErrorKind::ConfigError {
                path: "/etc/config.toml".to_string(),
                reason: "read failed".to_string(),
            },
            "Reading configuration".to_string(),
            source_box,
        );

        assert!(error.source().is_some());
    }

    #[test]
    fn test_error_kind_display_all_variants() {
        let test_cases = vec![
            (
                ProvctlErrorKind::ServiceNotFound("api".to_string()),
                "Service not found: api",
            ),
            (
                ProvctlErrorKind::NotRunning("api".to_string()),
                "Service api is not running",
            ),
            (
                ProvctlErrorKind::AlreadyRunning {
                    service: "api".to_string(),
                    pid: 5678,
                },
                "Service api is already running (PID: 5678)",
            ),
        ];

        for (kind, expected) in test_cases {
            assert_eq!(format!("{}", kind), expected);
        }
    }
}
