1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3use std::time::{SystemTime, UNIX_EPOCH};
4
5pub type Result<T> = std::result::Result<T, Error>;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct ErrorContext {
10    pub error_id: String,
12    pub timestamp: u64,
14    pub operation: String,
16    pub component: String,
18    pub error_chain: Vec<String>,
20    pub context_data: HashMap<String, String>,
22    pub recovery_suggestions: Vec<String>,
24    pub is_retryable: bool,
26    pub severity: ErrorSeverity,
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
31pub enum ErrorSeverity {
32    Info,
33    Warning,
34    Error,
35    Critical,
36}
37
38impl ErrorContext {
39    pub fn new(operation: &str, component: &str) -> Self {
40        let error_id = match uuid::Uuid::try_parse("00000000-0000-0000-0000-000000000000") {
42            Ok(_) => {
43                uuid::Uuid::new_v4().to_string()
45            }
46            Err(_) => {
47                format!("err_{}", 
49                    SystemTime::now()
50                        .duration_since(UNIX_EPOCH)
51                        .map(|d| d.as_nanos())
52                        .unwrap_or_else(|_| {
53                            std::process::id() as u128 * 1_000_000
55                        })
56                )
57            }
58        };
59
60        Self {
61            error_id,
62            timestamp: SystemTime::now()
63                .duration_since(UNIX_EPOCH)
64                .unwrap_or_else(|_| {
65                    tracing::warn!("Failed to get system time for error context, using epoch");
66                    std::time::Duration::from_secs(0)
67                })
68                .as_secs(),
69            operation: operation.to_string(),
70            component: component.to_string(),
71            error_chain: Vec::new(),
72            context_data: HashMap::new(),
73            recovery_suggestions: Vec::new(),
74            is_retryable: false,
75            severity: ErrorSeverity::Error,
76        }
77    }
78
79    pub fn add_cause(mut self, cause: &str) -> Self {
80        self.error_chain.push(cause.to_string());
81        self
82    }
83
84    pub fn add_context(mut self, key: &str, value: &str) -> Self {
85        let sanitized_value = if Self::is_sensitive_key(key) {
87            "[REDACTED]".to_string()
88        } else {
89            value.to_string()
90        };
91        self.context_data.insert(key.to_string(), sanitized_value);
92        self
93    }
94
95    fn is_sensitive_key(key: &str) -> bool {
97        let key_lower = key.to_lowercase();
98        let sensitive_patterns = [
99            "password", "passwd", "pwd",
100            "token", "auth", "authorization", "bearer",
101            "secret", "key", "api_key", "apikey",
102            "credential", "cred", "login",
103            "session", "cookie", "jwt",
104            "private", "signature", "hash",
105            "cert", "certificate", "pem"
106        ];
107        
108        sensitive_patterns.iter().any(|pattern| key_lower.contains(pattern))
109    }
110
111    pub fn add_recovery_suggestion(mut self, suggestion: &str) -> Self {
112        self.recovery_suggestions.push(suggestion.to_string());
113        self
114    }
115
116    pub fn set_retryable(mut self, retryable: bool) -> Self {
117        self.is_retryable = retryable;
118        self
119    }
120
121    pub fn set_severity(mut self, severity: ErrorSeverity) -> Self {
122        self.severity = severity;
123        self
124    }
125
126    pub fn format_detailed(&self) -> String {
128        let mut message = format!(
129            "Error [{}] in {} during {}\n",
130            self.error_id, self.component, self.operation
131        );
132
133        if !self.error_chain.is_empty() {
134            message.push_str("Error Chain:\n");
135            for (i, cause) in self.error_chain.iter().enumerate() {
136                message.push_str(&format!("  {}: {}\n", i + 1, cause));
137            }
138        }
139
140        if !self.context_data.is_empty() {
141            message.push_str("Context:\n");
142            for (key, value) in &self.context_data {
143                message.push_str(&format!("  {key}: {value}\n"));
144            }
145        }
146
147        if !self.recovery_suggestions.is_empty() {
148            message.push_str("Recovery Suggestions:\n");
149            for suggestion in &self.recovery_suggestions {
150                message.push_str(&format!("  - {suggestion}\n"));
151            }
152        }
153
154        message.push_str(&format!("Retryable: {}\n", self.is_retryable));
155        message.push_str(&format!("Severity: {:?}\n", self.severity));
156
157        message
158    }
159}
160
161impl std::fmt::Display for ErrorContext {
162    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
163        write!(
164            f,
165            "{} in {} ({})",
166            self.operation, self.component, self.error_id
167        )
168    }
169}
170
171#[derive(thiserror::Error, Debug)]
172pub enum Error {
173    #[error("Configuration error: {0}")]
174    Config(String),
175
176    #[error("WebSocket error: {0}")]
177    WebSocket(#[from] Box<tokio_tungstenite::tungstenite::Error>),
178
179    #[error("IO error: {0}")]
180    Io(#[from] std::io::Error),
181
182    #[error("JSON error: {0}")]
183    Json(#[from] serde_json::Error),
184
185    #[error("Connection error: {0}")]
186    Connection(String),
187
188    #[error("MCP protocol error: {0}")]
189    Mcp(String),
190
191    #[error("BRP error: {0}")]
192    Brp(String),
193
194    #[error("Validation error: {0}")]
195    Validation(String),
196
197    #[error("Serialization error: {0}")]
198    Serialization(String),
199
200    #[error("UUID error: {0}")]
201    Uuid(#[from] uuid::Error),
202
203    #[error("Debug error: {0}")]
204    DebugError(String),
205
206    #[error("Timeout: {0}")]
207    Timeout(String),
208
209    #[error("Error: {context}")]
211    WithContext {
212        context: ErrorContext,
213        #[source]
214        source: Option<Box<Error>>,
215    },
216}