//! Service error trait for standardized error handling across all services
//!
//! This module provides a common trait that all service-specific errors implement,
//! enabling consistent error handling, metrics, and retry logic across the codebase.

use super::{CommonError, ErrorCategory, ErrorSeverity, RetryStrategy};

/// Common trait for service-specific error types
///
/// Implement this trait to provide standardized error handling for your service.
/// This enables consistent metrics collection, retry logic, and error categorization.
///
/// # Example
///
/// ```rust
/// use common::error::{ServiceErrorExt, CommonError, ErrorCategory, ErrorSeverity, RetryStrategy};
///
/// #[derive(Debug, thiserror::Error)]
/// pub enum MyServiceError {
///     #[error("Common error: {0}")]
///     Common(#[from] CommonError),
///
///     #[error("Custom error: {0}")]
///     Custom(String),
/// }
///
/// impl ServiceErrorExt for MyServiceError {
///     fn error_code(&self) -> &'static str {
///         match self {
///             Self::Common(_) => "MY_COMMON_ERROR",
///             Self::Custom(_) => "MY_CUSTOM_ERROR",
///         }
///     }
///
///     fn category(&self) -> ErrorCategory {
///         match self {
///             Self::Common(err) => err.category(),
///             Self::Custom(_) => ErrorCategory::System,
///         }
///     }
///
///     fn severity(&self) -> ErrorSeverity {
///         match self {
///             Self::Common(err) => err.severity(),
///             Self::Custom(_) => ErrorSeverity::Error,
///         }
///     }
///
///     fn retry_strategy(&self) -> RetryStrategy {
///         match self {
///             Self::Common(err) => err.retry_strategy(),
///             Self::Custom(_) => RetryStrategy::Linear { base_delay_ms: 1000 },
///         }
///     }
///
///     fn to_common_error(self) -> CommonError {
///         match self {
///             Self::Common(err) => err,
///             Self::Custom(msg) => CommonError::internal(msg),
///         }
///     }
/// }
/// ```
pub trait ServiceErrorExt: std::error::Error + Send + Sync {
    /// Get unique error code for monitoring and alerting
    ///
    /// Error codes should be:
    /// - Uppercase with underscores (e.g., "ML_MODEL_TRAINING_ERROR")
    /// - Service-prefixed (e.g., "ML_", "RISK_", "DATA_", "TLI_")
    /// - Descriptive and unique within the service
    fn error_code(&self) -> &'static str;

    /// Get error category for classification and metrics
    fn category(&self) -> ErrorCategory;

    /// Get error severity level for prioritization and alerting
    fn severity(&self) -> ErrorSeverity;

    /// Get retry strategy for automatic error recovery
    fn retry_strategy(&self) -> RetryStrategy;

    /// Convert service-specific error to CommonError for interoperability
    ///
    /// This enables seamless integration with common error handling infrastructure
    /// and allows service errors to be used in generic contexts.
    fn to_common_error(self) -> CommonError;

    /// Check if error is retryable based on retry strategy
    fn is_retryable(&self) -> bool {
        !matches!(self.retry_strategy(), RetryStrategy::NoRetry)
    }
}

/// Extension trait for Result types using service errors
pub trait ServiceResultExt<T, E: ServiceErrorExt> {
    /// Map service error to CommonError
    fn map_to_common(self) -> Result<T, CommonError>;

    /// Extract error code if Result is Err
    fn error_code(&self) -> Option<&'static str>;

    /// Check if error is retryable
    fn is_retryable(&self) -> bool;
}

impl<T, E: ServiceErrorExt> ServiceResultExt<T, E> for Result<T, E> {
    fn map_to_common(self) -> Result<T, CommonError> {
        self.map_err(ServiceErrorExt::to_common_error)
    }

    fn error_code(&self) -> Option<&'static str> {
        self.as_ref().err().map(ServiceErrorExt::error_code)
    }

    fn is_retryable(&self) -> bool {
        self.as_ref().err().is_some_and(ServiceErrorExt::is_retryable)
    }
}

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

    #[derive(Debug, thiserror::Error)]
    enum TestServiceError {
        #[error("Common error: {0}")]
        Common(#[from] CommonError),

        #[error("Test error: {0}")]
        Test(String),
    }

    impl ServiceErrorExt for TestServiceError {
        fn error_code(&self) -> &'static str {
            match self {
                Self::Common(_) => "TEST_COMMON_ERROR",
                Self::Test(_) => "TEST_CUSTOM_ERROR",
            }
        }

        fn category(&self) -> ErrorCategory {
            match self {
                Self::Common(err) => err.category(),
                Self::Test(_) => ErrorCategory::System,
            }
        }

        fn severity(&self) -> ErrorSeverity {
            match self {
                Self::Common(err) => err.severity(),
                Self::Test(_) => ErrorSeverity::Error,
            }
        }

        fn retry_strategy(&self) -> RetryStrategy {
            match self {
                Self::Common(err) => err.retry_strategy(),
                Self::Test(_) => RetryStrategy::NoRetry,
            }
        }

        fn to_common_error(self) -> CommonError {
            match self {
                Self::Common(err) => err,
                Self::Test(msg) => CommonError::internal(msg),
            }
        }
    }

    #[test]
    fn test_service_error_trait() {
        let error = TestServiceError::Test("test error".to_string());

        assert_eq!(error.error_code(), "TEST_CUSTOM_ERROR");
        assert_eq!(error.category(), ErrorCategory::System);
        assert_eq!(error.severity(), ErrorSeverity::Error);
        assert!(!error.is_retryable());
    }

    #[test]
    fn test_common_error_delegation() {
        let common_err = CommonError::config("test config error");
        let error = TestServiceError::Common(common_err);

        assert_eq!(error.error_code(), "TEST_COMMON_ERROR");
        assert_eq!(error.category(), ErrorCategory::Configuration);
        assert_eq!(error.severity(), ErrorSeverity::Critical);
        assert!(!error.is_retryable());
    }

    #[test]
    fn test_result_extensions() {
        let ok_result: Result<i32, TestServiceError> = Ok(42);
        assert!(ok_result.error_code().is_none());
        assert!(!ok_result.is_retryable());

        let err_result: Result<i32, TestServiceError> = Err(TestServiceError::Test("error".to_string()));
        assert_eq!(err_result.error_code(), Some("TEST_CUSTOM_ERROR"));
        assert!(!err_result.is_retryable());
    }
}
