//! Error types for dirigent_core
//!
//! This module defines the core error types used throughout the dirigent_core
//! orchestration engine. All operations that can fail should return a `Result<T, CoreError>`.

use std::fmt;

/// Core error type for dirigent_core operations
///
/// This enum represents all possible errors that can occur during
/// core runtime operations, including connector management, configuration,
/// and authorization.
#[derive(Debug, Clone, PartialEq)]
pub enum CoreError {
    /// Resource was not found
    ///
    /// Used when attempting to access a connector, user, or other resource
    /// that doesn't exist in the runtime registry.
    NotFound,

    /// Resource already exists
    ///
    /// Used when attempting to create a resource (e.g., connector) with an ID
    /// that is already in use.
    AlreadyExists,

    /// Invalid configuration provided
    ///
    /// Used when configuration validation fails, such as invalid URLs,
    /// missing required fields, or malformed connector parameters.
    InvalidConfig,

    /// Failed to start a connector or service
    ///
    /// Used when a connector fails to initialize or start its background task,
    /// such as connection failures or missing dependencies.
    StartFailed,

    /// Operation not authorized for the requesting user
    ///
    /// Used when ownership checks fail or the user doesn't have permission
    /// to perform the requested operation on a resource.
    Unauthorized,

    /// Internal error with descriptive message
    ///
    /// Used for unexpected errors, I/O failures, serialization errors,
    /// or any other internal issues that don't fit other categories.
    /// The string provides context about what went wrong.
    Internal(String),

    /// Operation not valid in current state
    ///
    /// Used when attempting an operation on a resource that is in an
    /// incompatible state (e.g., sending commands to an errored connector,
    /// starting an already-running connector).
    InvalidState,
}

impl fmt::Display for CoreError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            CoreError::NotFound => {
                write!(f, "Resource not found")
            }
            CoreError::AlreadyExists => {
                write!(f, "Resource already exists")
            }
            CoreError::InvalidConfig => {
                write!(f, "Invalid configuration provided")
            }
            CoreError::StartFailed => {
                write!(f, "Failed to start connector or service")
            }
            CoreError::Unauthorized => {
                write!(f, "Operation not authorized")
            }
            CoreError::Internal(msg) => {
                write!(f, "Internal error: {}", msg)
            }
            CoreError::InvalidState => {
                write!(f, "Operation not valid in current state")
            }
        }
    }
}

impl std::error::Error for CoreError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        // None of our error variants wrap other errors currently
        None
    }
}

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

    #[test]
    fn test_error_display() {
        assert_eq!(CoreError::NotFound.to_string(), "Resource not found");
        assert_eq!(
            CoreError::AlreadyExists.to_string(),
            "Resource already exists"
        );
        assert_eq!(
            CoreError::InvalidConfig.to_string(),
            "Invalid configuration provided"
        );
        assert_eq!(
            CoreError::StartFailed.to_string(),
            "Failed to start connector or service"
        );
        assert_eq!(
            CoreError::Unauthorized.to_string(),
            "Operation not authorized"
        );
        assert_eq!(
            CoreError::Internal("test error".to_string()).to_string(),
            "Internal error: test error"
        );
        assert_eq!(
            CoreError::InvalidState.to_string(),
            "Operation not valid in current state"
        );
    }

    #[test]
    fn test_error_equality() {
        assert_eq!(CoreError::NotFound, CoreError::NotFound);
        assert_eq!(CoreError::AlreadyExists, CoreError::AlreadyExists);
        assert_eq!(
            CoreError::Internal("same".to_string()),
            CoreError::Internal("same".to_string())
        );
        assert_ne!(
            CoreError::Internal("different".to_string()),
            CoreError::Internal("other".to_string())
        );
        assert_ne!(CoreError::NotFound, CoreError::AlreadyExists);
    }

    #[test]
    fn test_error_is_error_trait() {
        // Verify that CoreError implements std::error::Error
        fn assert_is_error<T: std::error::Error>() {}
        assert_is_error::<CoreError>();
    }

    #[test]
    fn test_error_clone() {
        let error = CoreError::Internal("test".to_string());
        let cloned = error.clone();
        assert_eq!(error, cloned);
    }

    #[test]
    fn test_error_debug() {
        let error = CoreError::NotFound;
        let debug_str = format!("{:?}", error);
        assert!(debug_str.contains("NotFound"));
    }
}
