//! Error types for the docker-client library.

use thiserror::Error;

/// Result type alias for docker-client operations.
pub type Result<T> = std::result::Result<T, DockerError>;

/// Main error type for docker-client operations.
#[derive(Debug, Error)]
pub enum DockerError {
    /// Error connecting to Docker daemon
    #[error("Failed to connect to Docker daemon: {0}")]
    Connection(String),

    /// Container not found
    #[error("Container not found: {0}")]
    ContainerNotFound(String),

    /// Image not found
    #[error("Image not found: {0}")]
    ImageNotFound(String),

    /// Network not found
    #[error("Network not found: {0}")]
    NetworkNotFound(String),

    /// Volume not found
    #[error("Volume not found: {0}")]
    VolumeNotFound(String),

    /// Build operation failed
    #[error("Docker build failed: {0}")]
    BuildFailed(String),

    /// Push operation failed
    #[error("Docker push failed: {0}")]
    PushFailed(String),

    /// Pull operation failed
    #[error("Docker pull failed: {0}")]
    PullFailed(String),

    /// Container operation failed
    #[error("Container operation failed: {0}")]
    ContainerOperationFailed(String),

    /// Image operation failed
    #[error("Image operation failed: {0}")]
    ImageOperationFailed(String),

    /// Invalid configuration
    #[error("Invalid configuration: {0}")]
    InvalidConfiguration(String),

    /// Invalid image tag format
    #[error("Invalid image tag format: {0}")]
    InvalidImageTag(String),

    /// Authentication failed
    #[error("Authentication failed: {0}")]
    AuthenticationFailed(String),

    /// I/O error
    #[error("I/O error: {0}")]
    Io(#[from] std::io::Error),

    /// Bollard API error
    #[error("Docker API error: {0}")]
    Api(#[from] bollard::errors::Error),

    /// Serialization error
    #[error("Serialization error: {0}")]
    Serialization(#[from] serde_json::Error),

    /// Generic error
    #[error("{0}")]
    Other(String),
}

impl From<String> for DockerError {
    fn from(s: String) -> Self {
        DockerError::Other(s)
    }
}

impl From<&str> for DockerError {
    fn from(s: &str) -> Self {
        DockerError::Other(s.to_string())
    }
}

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

    #[test]
    fn test_error_display() {
        let err = DockerError::ContainerNotFound("test-container".to_string());
        assert_eq!(err.to_string(), "Container not found: test-container");

        let err = DockerError::ImageNotFound("test-image:latest".to_string());
        assert_eq!(err.to_string(), "Image not found: test-image:latest");

        let err = DockerError::Connection("timeout".to_string());
        assert_eq!(
            err.to_string(),
            "Failed to connect to Docker daemon: timeout"
        );

        let err = DockerError::BuildFailed("syntax error".to_string());
        assert_eq!(err.to_string(), "Docker build failed: syntax error");

        let err = DockerError::InvalidConfiguration("missing field".to_string());
        assert_eq!(err.to_string(), "Invalid configuration: missing field");
    }

    #[test]
    fn test_error_from_string() {
        let err: DockerError = "test error".into();
        assert_eq!(err.to_string(), "test error");

        let err: DockerError = String::from("another error").into();
        assert_eq!(err.to_string(), "another error");
    }

    #[test]
    fn test_error_from_io() {
        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
        let err: DockerError = io_err.into();
        assert!(err.to_string().contains("I/O error"));
    }

    #[test]
    fn test_result_type() {
        fn returns_error() -> Result<()> {
            Err(DockerError::Other("test".to_string()))
        }

        let result = returns_error();
        assert!(result.is_err());
    }

    #[test]
    fn test_all_error_variants() {
        let errors = vec![
            DockerError::Connection("test".to_string()),
            DockerError::ContainerNotFound("test".to_string()),
            DockerError::ImageNotFound("test".to_string()),
            DockerError::NetworkNotFound("test".to_string()),
            DockerError::VolumeNotFound("test".to_string()),
            DockerError::BuildFailed("test".to_string()),
            DockerError::PushFailed("test".to_string()),
            DockerError::PullFailed("test".to_string()),
            DockerError::ContainerOperationFailed("test".to_string()),
            DockerError::ImageOperationFailed("test".to_string()),
            DockerError::InvalidConfiguration("test".to_string()),
            DockerError::InvalidImageTag("test".to_string()),
            DockerError::AuthenticationFailed("test".to_string()),
            DockerError::Other("test".to_string()),
        ];

        for err in errors {
            assert!(!err.to_string().is_empty());
        }
    }
}
