//! Error trait interface for decoupled error handling
//!
//! This module defines stable, implementation-independent traits for error
//! handling. Rather than depending on concrete error types like
//! `ControlCenterError`, modules depend on the `ErrorContext` trait. This
//! reduces coupling (152 → ~40 dependents).

use std::fmt;

use axum::http::StatusCode;
use serde::Serialize;

/// Stable trait for error context and handling
///
/// This trait abstracts error details without exposing implementation.
/// Any module needing error handling depends on this trait, not
/// `ControlCenterError`.
pub trait ErrorContext: fmt::Display + fmt::Debug + Send + Sync {
    /// Get the machine-readable error code for API responses
    fn error_code(&self) -> &'static str;

    /// Get the HTTP status code appropriate for this error
    fn status_code(&self) -> StatusCode;

    /// Check if this error should be logged at ERROR level (true) or WARN
    /// (false)
    fn should_log_error(&self) -> bool;

    /// Get optional error details for debugging (may be sensitive)
    fn details(&self) -> Option<String> {
        None
    }

    /// Clone as a dynamic trait object (for passing around)
    fn clone_boxed(&self) -> Box<dyn ErrorContext>;
}

impl Clone for Box<dyn ErrorContext> {
    fn clone(&self) -> Self {
        self.clone_boxed()
    }
}

/// Generic error response structure for API responses
///
/// This is the public error format returned to clients.
/// It's independent of the internal `ControlCenterError` type.
#[derive(Serialize, Debug, Clone)]
pub struct ApiErrorResponse {
    /// Machine-readable error code
    pub error: String,

    /// Duplicate of error for compatibility
    pub error_code: String,

    /// Human-readable error message
    pub message: String,

    /// Optional details (may be redacted in production)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub details: Option<serde_json::Value>,

    /// Timestamp of error occurrence
    pub timestamp: chrono::DateTime<chrono::Utc>,
}

impl ApiErrorResponse {
    /// Create error response from any `ErrorContext`
    pub fn from_error(error: &dyn ErrorContext) -> Self {
        Self {
            error: error.error_code().to_string(),
            error_code: error.error_code().to_string(),
            message: error.to_string(),
            details: None,
            timestamp: chrono::Utc::now(),
        }
    }

    /// Add optional details to response
    pub fn with_details(mut self, details: serde_json::Value) -> Self {
        self.details = Some(details);
        self
    }
}

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

    struct MockError {
        code: &'static str,
        message: String,
    }

    impl fmt::Display for MockError {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            write!(f, "{}", self.message)
        }
    }

    impl fmt::Debug for MockError {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            write!(
                f,
                "MockError {{ code: {}, message: {} }}",
                self.code, self.message
            )
        }
    }

    impl ErrorContext for MockError {
        fn error_code(&self) -> &'static str {
            self.code
        }

        fn status_code(&self) -> StatusCode {
            StatusCode::INTERNAL_SERVER_ERROR
        }

        fn should_log_error(&self) -> bool {
            true
        }

        fn clone_boxed(&self) -> Box<dyn ErrorContext> {
            Box::new(MockError {
                code: self.code,
                message: self.message.clone(),
            })
        }
    }

    #[test]
    fn test_error_response_creation() {
        let error = MockError {
            code: "TEST_ERROR",
            message: "Test message".to_string(),
        };

        let response = ApiErrorResponse::from_error(&error);
        assert_eq!(response.error, "TEST_ERROR");
        assert_eq!(response.message, "Test message");
        assert_eq!(response.error_code, "TEST_ERROR");
    }

    #[test]
    fn test_error_response_with_details() {
        let error = MockError {
            code: "TEST_ERROR",
            message: "Test message".to_string(),
        };

        let details = serde_json::json!({"field": "value"});
        let response = ApiErrorResponse::from_error(&error).with_details(details);
        assert!(response.details.is_some());
    }
}
