1use anyhow::Result;
4use colored::*;
5use serde::{Deserialize, Serialize};
6use tabled::{Table, Tabled};
7
8use crate::client::Client;
9
10#[derive(Debug, Serialize, Deserialize, Tabled)]
11pub struct MailboxInfo {
12 pub name: String,
13 pub messages: u32,
14 pub unseen: u32,
15 pub size_mb: u64,
16 pub subscribed: bool,
17}
18
19pub async fn list(client: &Client, user: &str, json: bool) -> Result<()> {
21 let mailboxes: Vec<MailboxInfo> = client
22 .get(&format!("/api/users/{}/mailboxes", user))
23 .await?;
24
25 if json {
26 println!("{}", serde_json::to_string_pretty(&mailboxes)?);
27 } else {
28 if mailboxes.is_empty() {
29 println!("{}", "No mailboxes found".yellow());
30 return Ok(());
31 }
32
33 let table = Table::new(&mailboxes).to_string();
34 println!("{}", format!("Mailboxes for {}:", user).bold());
35 println!("{}", table);
36
37 let total_messages: u32 = mailboxes.iter().map(|m| m.messages).sum();
38 let total_size: u64 = mailboxes.iter().map(|m| m.size_mb).sum();
39
40 println!(
41 "\n{} mailboxes, {} messages, {} MB total",
42 mailboxes.len().to_string().bold(),
43 total_messages.to_string().bold(),
44 total_size.to_string().bold()
45 );
46 }
47
48 Ok(())
49}
50
51pub async fn create(client: &Client, user: &str, name: &str, json: bool) -> Result<()> {
53 #[derive(Serialize)]
54 struct CreateMailboxRequest {
55 name: String,
56 }
57
58 let request = CreateMailboxRequest {
59 name: name.to_string(),
60 };
61
62 #[derive(Deserialize, Serialize)]
63 struct CreateResponse {
64 success: bool,
65 }
66
67 let response: CreateResponse = client
68 .post(&format!("/api/users/{}/mailboxes", user), &request)
69 .await?;
70
71 if json {
72 println!("{}", serde_json::to_string_pretty(&response)?);
73 } else {
74 println!(
75 "{}",
76 format!("✓ Mailbox '{}' created for {}", name, user)
77 .green()
78 .bold()
79 );
80 }
81
82 Ok(())
83}
84
85pub async fn delete(
87 client: &Client,
88 user: &str,
89 name: &str,
90 force: bool,
91 json: bool,
92) -> Result<()> {
93 if !force && !json {
94 println!(
95 "{}",
96 format!("Delete mailbox '{}' for {}?", name, user).yellow()
97 );
98 println!("This will delete all messages in this mailbox.");
99 println!("Use --force to skip this confirmation.");
100
101 use std::io::{self, Write};
102 print!("Continue? [y/N]: ");
103 io::stdout().flush()?;
104
105 let mut input = String::new();
106 io::stdin().read_line(&mut input)?;
107
108 if !input.trim().eq_ignore_ascii_case("y") {
109 println!("{}", "Cancelled".yellow());
110 return Ok(());
111 }
112 }
113
114 #[derive(Deserialize, Serialize)]
115 struct DeleteResponse {
116 success: bool,
117 }
118
119 let response: DeleteResponse = client
120 .delete(&format!("/api/users/{}/mailboxes/{}", user, name))
121 .await?;
122
123 if json {
124 println!("{}", serde_json::to_string_pretty(&response)?);
125 } else {
126 println!("{}", format!("✓ Mailbox '{}' deleted", name).green().bold());
127 }
128
129 Ok(())
130}
131
132pub async fn rename(
134 client: &Client,
135 user: &str,
136 old_name: &str,
137 new_name: &str,
138 json: bool,
139) -> Result<()> {
140 #[derive(Serialize)]
141 struct RenameRequest {
142 new_name: String,
143 }
144
145 let request = RenameRequest {
146 new_name: new_name.to_string(),
147 };
148
149 #[derive(Deserialize, Serialize)]
150 struct RenameResponse {
151 success: bool,
152 }
153
154 let response: RenameResponse = client
155 .put(
156 &format!("/api/users/{}/mailboxes/{}/rename", user, old_name),
157 &request,
158 )
159 .await?;
160
161 if json {
162 println!("{}", serde_json::to_string_pretty(&response)?);
163 } else {
164 println!(
165 "{}",
166 format!("✓ Mailbox renamed: '{}' → '{}'", old_name, new_name)
167 .green()
168 .bold()
169 );
170 }
171
172 Ok(())
173}
174
175pub async fn repair(client: &Client, user: &str, name: &str, json: bool) -> Result<()> {
177 #[derive(Deserialize, Serialize)]
178 struct RepairResponse {
179 messages_scanned: u32,
180 errors_fixed: u32,
181 indexes_rebuilt: u32,
182 }
183
184 let response: RepairResponse = client
185 .post(
186 &format!("/api/users/{}/mailboxes/{}/repair", user, name),
187 &serde_json::json!({}),
188 )
189 .await?;
190
191 if json {
192 println!("{}", serde_json::to_string_pretty(&response)?);
193 } else {
194 println!(
195 "{}",
196 format!("✓ Mailbox '{}' repaired", name).green().bold()
197 );
198 println!(" Messages scanned: {}", response.messages_scanned);
199 println!(" Errors fixed: {}", response.errors_fixed);
200 println!(" Indexes rebuilt: {}", response.indexes_rebuilt);
201 }
202
203 Ok(())
204}
205
206pub async fn subscribe(client: &Client, user: &str, name: &str, json: bool) -> Result<()> {
208 #[derive(Serialize)]
209 struct SubscribeRequest {
210 subscribed: bool,
211 }
212
213 #[derive(Deserialize, Serialize)]
214 struct SubscribeResponse {
215 success: bool,
216 }
217
218 let request = SubscribeRequest { subscribed: true };
219
220 let response: SubscribeResponse = client
221 .put(
222 &format!("/api/users/{}/mailboxes/{}/subscribe", user, name),
223 &request,
224 )
225 .await?;
226
227 if json {
228 println!("{}", serde_json::to_string_pretty(&response)?);
229 } else {
230 println!(
231 "{}",
232 format!("✓ Subscribed to mailbox '{}'", name).green().bold()
233 );
234 }
235
236 Ok(())
237}
238
239pub async fn unsubscribe(client: &Client, user: &str, name: &str, json: bool) -> Result<()> {
241 #[derive(Serialize)]
242 struct SubscribeRequest {
243 subscribed: bool,
244 }
245
246 #[derive(Deserialize, Serialize)]
247 struct UnsubscribeResponse {
248 success: bool,
249 }
250
251 let request = SubscribeRequest { subscribed: false };
252
253 let response: UnsubscribeResponse = client
254 .put(
255 &format!("/api/users/{}/mailboxes/{}/subscribe", user, name),
256 &request,
257 )
258 .await?;
259
260 if json {
261 println!("{}", serde_json::to_string_pretty(&response)?);
262 } else {
263 println!(
264 "{}",
265 format!("✓ Unsubscribed from mailbox '{}'", name)
266 .yellow()
267 .bold()
268 );
269 }
270
271 Ok(())
272}
273
274pub async fn show(client: &Client, user: &str, name: &str, json: bool) -> Result<()> {
276 #[derive(Deserialize, Serialize)]
277 struct MailboxDetails {
278 name: String,
279 messages: u32,
280 unseen: u32,
281 recent: u32,
282 size_bytes: u64,
283 subscribed: bool,
284 created_at: String,
285 uid_validity: u32,
286 uid_next: u32,
287 }
288
289 let details: MailboxDetails = client
290 .get(&format!("/api/users/{}/mailboxes/{}", user, name))
291 .await?;
292
293 if json {
294 println!("{}", serde_json::to_string_pretty(&details)?);
295 } else {
296 println!("{}", format!("Mailbox: {}", name).bold());
297 println!(" User: {}", user);
298 println!(
299 " Messages: {} total, {} unseen, {} recent",
300 details.messages, details.unseen, details.recent
301 );
302 println!(" Size: {} MB", details.size_bytes / (1024 * 1024));
303 println!(
304 " Subscribed: {}",
305 if details.subscribed {
306 "Yes".green()
307 } else {
308 "No".yellow()
309 }
310 );
311 println!(" Created: {}", details.created_at);
312 println!(" UIDVALIDITY: {}", details.uid_validity);
313 println!(" UIDNEXT: {}", details.uid_next);
314 }
315
316 Ok(())
317}
318
319#[cfg(test)]
320mod tests {
321 use super::*;
322
323 #[test]
324 fn test_mailbox_info_serialization() {
325 let mailbox = MailboxInfo {
326 name: "INBOX".to_string(),
327 messages: 10,
328 unseen: 2,
329 size_mb: 5,
330 subscribed: true,
331 };
332
333 let json = serde_json::to_string(&mailbox).unwrap();
334 assert!(json.contains("INBOX"));
335 }
336
337 #[test]
338 fn test_mailbox_stats_calculation() {
339 let mailboxes = [
340 MailboxInfo {
341 name: "INBOX".to_string(),
342 messages: 10,
343 unseen: 2,
344 size_mb: 5,
345 subscribed: true,
346 },
347 MailboxInfo {
348 name: "Sent".to_string(),
349 messages: 5,
350 unseen: 0,
351 size_mb: 3,
352 subscribed: true,
353 },
354 ];
355
356 let total_messages: u32 = mailboxes.iter().map(|m| m.messages).sum();
357 let total_size: u64 = mailboxes.iter().map(|m| m.size_mb).sum();
358
359 assert_eq!(total_messages, 15);
360 assert_eq!(total_size, 8);
361 }
362
363 #[test]
364 fn test_mailbox_empty() {
365 let mailbox = MailboxInfo {
366 name: "Archive".to_string(),
367 messages: 0,
368 unseen: 0,
369 size_mb: 0,
370 subscribed: false,
371 };
372
373 assert_eq!(mailbox.messages, 0);
374 assert_eq!(mailbox.unseen, 0);
375 assert!(!mailbox.subscribed);
376 }
377
378 #[test]
379 fn test_mailbox_all_unseen() {
380 let mailbox = MailboxInfo {
381 name: "INBOX".to_string(),
382 messages: 10,
383 unseen: 10,
384 size_mb: 5,
385 subscribed: true,
386 };
387
388 assert_eq!(mailbox.messages, mailbox.unseen);
389 }
390
391 #[test]
392 fn test_mailbox_deserialization() {
393 let json = r#"{
394 "name": "Drafts",
395 "messages": 5,
396 "unseen": 3,
397 "size_mb": 2,
398 "subscribed": true
399 }"#;
400
401 let mailbox: MailboxInfo = serde_json::from_str(json).unwrap();
402 assert_eq!(mailbox.name, "Drafts");
403 assert_eq!(mailbox.messages, 5);
404 assert_eq!(mailbox.unseen, 3);
405 }
406
407 #[test]
408 fn test_mailbox_hierarchical_name() {
409 let mailbox = MailboxInfo {
410 name: "Archive/2024/January".to_string(),
411 messages: 100,
412 unseen: 0,
413 size_mb: 50,
414 subscribed: true,
415 };
416
417 assert!(mailbox.name.contains('/'));
418 assert_eq!(mailbox.name, "Archive/2024/January");
419 }
420
421 #[test]
422 fn test_mailbox_special_use() {
423 let mailboxes = [
424 MailboxInfo {
425 name: "Sent".to_string(),
426 messages: 10,
427 unseen: 0,
428 size_mb: 5,
429 subscribed: true,
430 },
431 MailboxInfo {
432 name: "Trash".to_string(),
433 messages: 20,
434 unseen: 0,
435 size_mb: 3,
436 subscribed: true,
437 },
438 ];
439
440 assert_eq!(mailboxes.len(), 2);
441 assert!(mailboxes.iter().any(|m| m.name == "Sent"));
442 assert!(mailboxes.iter().any(|m| m.name == "Trash"));
443 }
444}