//! Error types for the KMS plugin.
//!
//! This module provides structured error handling with specific error kinds
//! for different failure scenarios in KMS operations.

use std::fmt;

/// Enum representing different kinds of KMS errors.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum KmsErrorKind {
    /// Backend not available or not configured
    BackendNotAvailable,
    /// Encryption operation failed
    EncryptionFailed,
    /// Decryption operation failed
    DecryptionFailed,
    /// Key generation failed
    KeyGenerationFailed,
    /// Invalid key specification
    InvalidKeySpec,
    /// Key not found
    KeyNotFound,
    /// Authentication to backend failed
    AuthenticationFailed,
    /// Network or HTTP request failed
    NetworkError,
    /// Configuration error
    ConfigurationError,
    /// Invalid input data
    InvalidInput,
    /// Backend-specific error
    BackendError,
    /// Internal error
    InternalError,
}

impl fmt::Display for KmsErrorKind {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::BackendNotAvailable => write!(f, "backend not available"),
            Self::EncryptionFailed => write!(f, "encryption failed"),
            Self::DecryptionFailed => write!(f, "decryption failed"),
            Self::KeyGenerationFailed => write!(f, "key generation failed"),
            Self::InvalidKeySpec => write!(f, "invalid key specification"),
            Self::KeyNotFound => write!(f, "key not found"),
            Self::AuthenticationFailed => write!(f, "authentication failed"),
            Self::NetworkError => write!(f, "network error"),
            Self::ConfigurationError => write!(f, "configuration error"),
            Self::InvalidInput => write!(f, "invalid input"),
            Self::BackendError => write!(f, "backend error"),
            Self::InternalError => write!(f, "internal error"),
        }
    }
}

/// Structured error type for KMS operations.
///
/// Provides detailed error information including:
/// - Error kind for programmatic handling
/// - Context message for additional details
/// - Optional source error for error chaining
#[derive(Debug)]
pub struct KmsError {
    /// The kind of error that occurred
    pub kind: KmsErrorKind,
    /// Additional context about the error
    pub context: String,
    /// Optional underlying error
    pub source: Option<Box<dyn std::error::Error + Send + Sync>>,
}

impl KmsError {
    /// Creates a new KmsError with the specified kind and context.
    pub fn new(kind: KmsErrorKind, context: impl Into<String>) -> Self {
        Self {
            kind,
            context: context.into(),
            source: None,
        }
    }

    /// Creates a KmsError with an underlying source error.
    pub fn with_source(
        kind: KmsErrorKind,
        context: impl Into<String>,
        source: impl std::error::Error + Send + Sync + 'static,
    ) -> Self {
        Self {
            kind,
            context: context.into(),
            source: Some(Box::new(source)),
        }
    }

    /// Creates a backend not available error.
    pub fn backend_not_available(context: impl Into<String>) -> Self {
        Self::new(KmsErrorKind::BackendNotAvailable, context)
    }

    /// Creates an encryption failed error.
    pub fn encryption_failed(context: impl Into<String>) -> Self {
        Self::new(KmsErrorKind::EncryptionFailed, context)
    }

    /// Creates a decryption failed error.
    pub fn decryption_failed(context: impl Into<String>) -> Self {
        Self::new(KmsErrorKind::DecryptionFailed, context)
    }

    /// Creates a key generation failed error.
    pub fn key_generation_failed(context: impl Into<String>) -> Self {
        Self::new(KmsErrorKind::KeyGenerationFailed, context)
    }

    /// Creates an invalid key spec error.
    pub fn invalid_key_spec(context: impl Into<String>) -> Self {
        Self::new(KmsErrorKind::InvalidKeySpec, context)
    }

    /// Creates a network error.
    pub fn network_error(context: impl Into<String>) -> Self {
        Self::new(KmsErrorKind::NetworkError, context)
    }

    /// Creates a configuration error.
    pub fn configuration_error(context: impl Into<String>) -> Self {
        Self::new(KmsErrorKind::ConfigurationError, context)
    }

    /// Creates an invalid input error.
    pub fn invalid_input(context: impl Into<String>) -> Self {
        Self::new(KmsErrorKind::InvalidInput, context)
    }

    /// Creates a backend error.
    pub fn backend_error(context: impl Into<String>) -> Self {
        Self::new(KmsErrorKind::BackendError, context)
    }
}

impl fmt::Display for KmsError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}: {}", self.kind, self.context)?;
        if let Some(ref source) = self.source {
            write!(f, " (caused by: {})", source)?;
        }
        Ok(())
    }
}

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

impl From<KmsError> for nu_protocol::LabeledError {
    fn from(err: KmsError) -> Self {
        nu_protocol::LabeledError::new(err.to_string())
    }
}

impl From<KmsError> for String {
    fn from(err: KmsError) -> Self {
        err.to_string()
    }
}

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

    #[test]
    fn test_kms_error_display() {
        let error = KmsError::new(KmsErrorKind::EncryptionFailed, "AES encryption failed");
        assert!(error.to_string().contains("encryption failed"));
        assert!(error.to_string().contains("AES"));
    }

    #[test]
    fn test_kms_error_with_source() {
        let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "key file not found");
        let error = KmsError::with_source(KmsErrorKind::KeyNotFound, "failed to load key", io_error);
        assert!(error.to_string().contains("caused by"));
    }

    #[test]
    fn test_convenience_constructors() {
        let error = KmsError::encryption_failed("test");
        assert_eq!(error.kind, KmsErrorKind::EncryptionFailed);

        let error = KmsError::decryption_failed("test");
        assert_eq!(error.kind, KmsErrorKind::DecryptionFailed);

        let error = KmsError::backend_not_available("test");
        assert_eq!(error.kind, KmsErrorKind::BackendNotAvailable);
    }
}
