rvoip_client_core/client/
calls.rs

1//! Call operations for the client-core library
2//! 
3//! This module contains all call-related operations including making calls,
4//! answering, rejecting, hanging up, and querying call information.
5//!
6//! # Call Management Overview
7//!
8//! The call operations provide a comprehensive API for managing SIP calls through
9//! the session-core infrastructure. This includes:
10//!
11//! - **Outgoing Calls**: Initiate calls with `make_call()`
12//! - **Incoming Calls**: Handle with `answer_call()` and `reject_call()`
13//! - **Call Control**: Terminate calls with `hangup_call()`
14//! - **Call Information**: Query call state and history
15//! - **Statistics**: Track call metrics and performance
16//!
17//! # Architecture
18//!
19//! ```text
20//! ┌─────────────────────────┐
21//! │   Client Application    │
22//! └───────────┬─────────────┘
23//!             │
24//! ┌───────────▼─────────────┐
25//! │    Call Operations      │ ◄── This Module
26//! │ ┌─────────────────────┐ │
27//! │ │ make_call()         │ │
28//! │ │ answer_call()       │ │
29//! │ │ hangup_call()       │ │
30//! │ │ get_call_*()        │ │
31//! │ └─────────────────────┘ │
32//! └───────────┬─────────────┘
33//!             │
34//! ┌───────────▼─────────────┐
35//! │    session-core         │
36//! │  SessionControl API     │
37//! └─────────────────────────┘
38//! ```
39//!
40//! # Usage Examples
41//!
42//! ```rust
43//! use rvoip_client_core::{ClientManager, ClientConfig, CallId, CallState};
44//! 
45//! async fn call_operations_example() -> Result<(), Box<dyn std::error::Error>> {
46//!     // Create and start client
47//!     let config = ClientConfig::new()
48//!         .with_sip_addr("127.0.0.1:5060".parse()?);
49//!     let client = ClientManager::new(config).await?;
50//!     client.start().await?;
51//!     
52//!     // Make an outgoing call
53//!     let call_id = client.make_call(
54//!         "sip:alice@example.com".to_string(),
55//!         "sip:bob@example.com".to_string(),
56//!         Some("Business call".to_string()),
57//!     ).await?;
58//!     
59//!     // Check call information
60//!     let call_info = client.get_call(&call_id).await?;
61//!     println!("Call state: {:?}", call_info.state);
62//!     
63//!     // List all active calls
64//!     let active_calls = client.get_active_calls().await;
65//!     
66//!     // End the call
67//!     client.hangup_call(&call_id).await?;
68//!     
69//!     Ok(())
70//! }
71//! ```
72
73use std::collections::HashMap;
74use chrono::Utc;
75
76// Import session-core APIs
77use rvoip_session_core::api::{
78    SessionControl,
79    MediaControl,
80};
81
82// Import client-core types
83use crate::{
84    ClientResult, ClientError,
85    call::{CallId, CallInfo, CallDirection},
86};
87
88use super::types::*;
89use super::recovery::{retry_with_backoff, RetryConfig, ErrorContext};
90
91/// Call operations implementation for ClientManager
92impl super::manager::ClientManager {
93    /// Make an outgoing call with enhanced information tracking
94    /// 
95    /// This method initiates a new outgoing call using the session-core infrastructure.
96    /// It handles SDP generation, session creation, and proper event notification.
97    /// 
98    /// # Arguments
99    /// 
100    /// * `from` - The caller's SIP URI (e.g., "sip:alice@example.com")
101    /// * `to` - The callee's SIP URI (e.g., "sip:bob@example.com")  
102    /// * `subject` - Optional call subject/reason
103    /// 
104    /// # Returns
105    /// 
106    /// Returns a unique `CallId` that can be used to track and control the call.
107    /// 
108    /// # Errors
109    /// 
110    /// * `ClientError::InvalidConfiguration` - If the URIs are malformed
111    /// * `ClientError::NetworkError` - If there's a network connectivity issue
112    /// * `ClientError::CallSetupFailed` - If the call cannot be initiated
113    /// 
114    /// # Examples
115    /// 
116    /// Basic call:
117    /// ```rust
118    /// use rvoip_client_core::{ClientManager, ClientConfig, CallId};
119    /// 
120    /// async fn make_basic_call() -> Result<CallId, Box<dyn std::error::Error>> {
121    ///     let config = ClientConfig::new()
122    ///         .with_sip_addr("127.0.0.1:5060".parse()?);
123    ///     let client = ClientManager::new(config).await?;
124    ///     client.start().await?;
125    ///     
126    ///     let call_id = client.make_call(
127    ///         "sip:alice@ourcompany.com".to_string(),
128    ///         "sip:bob@example.com".to_string(),
129    ///         None,
130    ///     ).await?;
131    /// 
132    ///     println!("Outgoing call started: {}", call_id);
133    ///     Ok(call_id)
134    /// }
135    /// ```
136    /// 
137    /// Call with subject:
138    /// ```rust
139    /// use rvoip_client_core::{ClientManager, ClientConfig, CallId};
140    /// 
141    /// async fn make_call_with_subject() -> Result<CallId, Box<dyn std::error::Error>> {
142    ///     let config = ClientConfig::new()
143    ///         .with_sip_addr("127.0.0.1:5061".parse()?);
144    ///     let client = ClientManager::new(config).await?;
145    ///     client.start().await?;
146    ///     
147    ///     let call_id = client.make_call(
148    ///         "sip:support@ourcompany.com".to_string(),
149    ///         "sip:customer@example.com".to_string(),
150    ///         Some("Technical Support Call".to_string()),
151    ///     ).await?;
152    ///     
153    ///     Ok(call_id)
154    /// }
155    /// ```
156    /// 
157    /// # Call Flow
158    /// 
159    /// 1. Validates the SIP URIs
160    /// 2. Creates a new session via session-core
161    /// 3. Generates and stores call metadata
162    /// 4. Emits appropriate events
163    /// 5. Returns the CallId for tracking
164    pub async fn make_call(
165        &self,
166        from: String,
167        to: String,
168        subject: Option<String>,
169    ) -> ClientResult<CallId> {
170        // Check if client is running
171        if !*self.is_running.read().await {
172            return Err(ClientError::InternalError {
173                message: "Client is not started. Call start() before making calls.".to_string()
174            });
175        }
176        
177        // Create call via session-core with retry logic for network errors
178        let prepared_call = retry_with_backoff(
179            "prepare_outgoing_call",
180            RetryConfig::quick(),
181            || async {
182                SessionControl::prepare_outgoing_call(
183                    &self.coordinator,
184                    &from,
185                    &to
186                )
187                .await
188                .map_err(|e| ClientError::CallSetupFailed { 
189                    reason: format!("Failed to prepare call: {}", e) 
190                })
191            }
192        )
193        .await
194        .with_context(|| format!("Failed to prepare call from {} to {}", from, to))?;
195        
196        // Log the allocated RTP port
197        tracing::info!("Prepared call with allocated RTP port: {}", prepared_call.local_rtp_port);
198        
199        // Now initiate the prepared call
200        let session = retry_with_backoff(
201            "initiate_prepared_call", 
202            RetryConfig::quick(),
203            || async {
204                SessionControl::initiate_prepared_call(
205                    &self.coordinator,
206                    &prepared_call
207                )
208                .await
209                .map_err(|e| ClientError::CallSetupFailed { 
210                    reason: format!("Failed to initiate call: {}", e) 
211                })
212            }
213        )
214        .await
215        .with_context(|| format!("Failed to initiate call from {} to {}", from, to))?;
216            
217        // Create call ID and mapping
218        let call_id = CallId::new_v4();
219        self.call_handler.call_mapping.insert(session.id.clone(), call_id);
220        self.session_mapping.insert(call_id, session.id.clone());
221        
222        // Create enhanced call info
223        let mut metadata = HashMap::new();
224        metadata.insert("created_via".to_string(), "make_call".to_string());
225        if let Some(ref subj) = subject {
226            metadata.insert("subject".to_string(), subj.clone());
227        }
228        
229        let call_info = CallInfo {
230            call_id,
231            state: crate::call::CallState::Initiating,
232            direction: CallDirection::Outgoing,
233            local_uri: from,
234            remote_uri: to,
235            remote_display_name: None,
236            subject,
237            created_at: Utc::now(),
238            connected_at: None,
239            ended_at: None,
240            remote_addr: None,
241            media_session_id: None,
242            sip_call_id: session.id.0.clone(),
243            metadata,
244        };
245        
246        self.call_info.insert(call_id, call_info.clone());
247        
248        // Emit call created event
249        let _ = self.event_tx.send(crate::events::ClientEvent::CallStateChanged {
250            info: crate::events::CallStatusInfo {
251                call_id,
252                new_state: crate::call::CallState::Initiating,
253                previous_state: None, // No previous state for new calls
254                reason: Some("Call created".to_string()),
255                timestamp: Utc::now(),
256            },
257            priority: crate::events::EventPriority::Normal,
258        });
259        
260        // Update stats
261        let mut stats = self.stats.lock().await;
262        stats.total_calls += 1;
263        
264        tracing::info!("Created outgoing call {} -> {} (call_id: {})", 
265                      call_info.local_uri, call_info.remote_uri, call_id);
266        
267        Ok(call_id)
268    }
269    
270    /// Answer an incoming call with SDP negotiation
271    /// 
272    /// This method accepts an incoming call that was previously stored by the event handler.
273    /// It performs SDP offer/answer negotiation and establishes the media session.
274    /// 
275    /// # Arguments
276    /// 
277    /// * `call_id` - The unique identifier of the incoming call to answer
278    /// 
279    /// # Returns
280    /// 
281    /// Returns `Ok(())` if the call was successfully answered and connected.
282    /// 
283    /// # Errors
284    /// 
285    /// * `ClientError::CallNotFound` - If no incoming call exists with the given ID
286    /// * `ClientError::CallSetupFailed` - If SDP negotiation or call setup fails
287    /// * `ClientError::InvalidCallState` - If the call is not in an answerable state
288    /// 
289    /// # Examples
290    /// 
291    /// Basic call answering:
292    /// ```rust
293    /// use rvoip_client_core::{ClientManager, ClientConfig, CallId, ClientEventHandler, CallAction, IncomingCallInfo};
294    /// use std::sync::Arc;
295    /// 
296    /// struct MyEventHandler;
297    /// 
298    /// #[async_trait::async_trait]
299    /// impl ClientEventHandler for MyEventHandler {
300    ///     async fn on_incoming_call(&self, call_info: IncomingCallInfo) -> CallAction {
301    ///         // Store call_id for later use
302    ///         println!("Incoming call from: {}", call_info.caller_uri);
303    ///         CallAction::Ignore // Let application handle it
304    ///     }
305    ///     
306    ///     async fn on_call_state_changed(&self, _info: rvoip_client_core::CallStatusInfo) {}
307    ///     async fn on_registration_status_changed(&self, _info: rvoip_client_core::RegistrationStatusInfo) {}
308    ///     async fn on_media_event(&self, _info: rvoip_client_core::MediaEventInfo) {}
309    ///     async fn on_client_error(&self, _error: rvoip_client_core::ClientError, _call_id: Option<CallId>) {}
310    ///     async fn on_network_event(&self, _connected: bool, _reason: Option<String>) {}
311    /// }
312    /// 
313    /// async fn answer_incoming_call(call_id: CallId) -> Result<(), Box<dyn std::error::Error>> {
314    ///     let config = ClientConfig::new()
315    ///         .with_sip_addr("127.0.0.1:5062".parse()?);
316    ///     let client = ClientManager::new(config).await?;
317    ///     client.set_event_handler(Arc::new(MyEventHandler)).await;
318    ///     client.start().await?;
319    ///     
320    ///     // Answer the call (assuming call_id was obtained from event handler)
321    ///     client.answer_call(&call_id).await?;
322    ///     println!("Successfully answered call: {}", call_id);
323    ///     
324    ///     Ok(())
325    /// }
326    /// ```
327    /// 
328    /// # SDP Negotiation Process
329    /// 
330    /// 1. Retrieves the stored incoming call information
331    /// 2. If an SDP offer was provided, generates an appropriate SDP answer
332    /// 3. If no offer was provided, generates an SDP offer (rare case)
333    /// 4. Calls session-core to accept the call with the negotiated SDP
334    /// 5. Updates call state to Connected and emits events
335    /// 
336    /// # Thread Safety
337    /// 
338    /// This method is async and thread-safe. Multiple calls can be answered concurrently.
339    pub async fn answer_call(&self, call_id: &CallId) -> ClientResult<()> {
340        // Get the stored IncomingCall object
341        let incoming_call = self.call_handler.get_incoming_call(call_id)
342            .await
343            .ok_or(ClientError::CallNotFound { call_id: *call_id })?;
344        
345        // Generate SDP answer based on the offer
346        let sdp_answer = if let Some(offer) = &incoming_call.sdp {
347            // Use MediaControl to generate SDP answer
348            MediaControl::generate_sdp_answer(
349                &self.coordinator,
350                &incoming_call.id,
351                offer
352            )
353            .await
354            .map_err(|e| ClientError::CallSetupFailed { 
355                reason: format!("Failed to generate SDP answer: {}", e) 
356            })?
357        } else {
358            // No offer provided, generate our own SDP
359            MediaControl::generate_sdp_offer(
360                &self.coordinator,
361                &incoming_call.id
362            )
363            .await
364            .map_err(|e| ClientError::CallSetupFailed { 
365                reason: format!("Failed to generate SDP: {}", e) 
366            })?
367        };
368        
369        // Use SessionControl to accept the call with SDP answer
370        SessionControl::accept_incoming_call(
371            &self.coordinator,
372            &incoming_call,
373            Some(sdp_answer)  // Provide the generated SDP answer
374        )
375        .await
376        .map_err(|e| ClientError::CallSetupFailed { 
377            reason: format!("Failed to answer call: {}", e) 
378        })?;
379        
380        // Update call info
381        if let Some(mut call_info) = self.call_info.get_mut(call_id) {
382            call_info.state = crate::call::CallState::Connected;
383            call_info.connected_at = Some(Utc::now());
384            call_info.metadata.insert("answered_at".to_string(), Utc::now().to_rfc3339());
385        }
386        
387        // Set up automatic audio frame subscription for the call
388        if let Err(e) = self.setup_call_audio(call_id).await {
389            // Log the error but don't fail the call - audio might still work
390            // through other means or this might be a non-audio call
391            tracing::warn!("Failed to set up audio for call {}: {}", call_id, e);
392        }
393        
394        // Update stats
395        let mut stats = self.stats.lock().await;
396        stats.connected_calls += 1;
397        
398        tracing::info!("Answered call {}", call_id);
399        Ok(())
400    }
401    
402    /// Reject an incoming call with optional reason
403    /// 
404    /// This method rejects an incoming call that was previously stored by the event handler.
405    /// The call will be terminated with a SIP rejection response.
406    /// 
407    /// # Arguments
408    /// 
409    /// * `call_id` - The unique identifier of the incoming call to reject
410    /// 
411    /// # Returns
412    /// 
413    /// Returns `Ok(())` if the call was successfully rejected.
414    /// 
415    /// # Errors
416    /// 
417    /// * `ClientError::CallNotFound` - If no incoming call exists with the given ID
418    /// * `ClientError::CallTerminated` - If the rejection fails to send properly
419    /// 
420    /// # Examples
421    /// 
422    /// Basic call rejection:
423    /// ```rust
424    /// use rvoip_client_core::{ClientManager, ClientConfig, CallId, ClientEventHandler, CallAction, IncomingCallInfo};
425    /// use std::sync::Arc;
426    /// 
427    /// struct RejectingEventHandler;
428    /// 
429    /// #[async_trait::async_trait]
430    /// impl ClientEventHandler for RejectingEventHandler {
431    ///     async fn on_incoming_call(&self, call_info: IncomingCallInfo) -> CallAction {
432    ///         // Automatically reject calls from unknown numbers
433    ///         if !call_info.caller_uri.contains("@trusted-domain.com") {
434    ///             CallAction::Reject
435    ///         } else {
436    ///             CallAction::Ignore
437    ///         }
438    ///     }
439    ///     
440    ///     async fn on_call_state_changed(&self, _info: rvoip_client_core::CallStatusInfo) {}
441    ///     async fn on_registration_status_changed(&self, _info: rvoip_client_core::RegistrationStatusInfo) {}
442    ///     async fn on_media_event(&self, _info: rvoip_client_core::MediaEventInfo) {}
443    ///     async fn on_client_error(&self, _error: rvoip_client_core::ClientError, _call_id: Option<CallId>) {}
444    ///     async fn on_network_event(&self, _connected: bool, _reason: Option<String>) {}
445    /// }
446    /// 
447    /// async fn reject_unwanted_call(call_id: CallId) -> Result<(), Box<dyn std::error::Error>> {
448    ///     let config = ClientConfig::new()
449    ///         .with_sip_addr("127.0.0.1:5063".parse()?);
450    ///     let client = ClientManager::new(config).await?;
451    ///     client.set_event_handler(Arc::new(RejectingEventHandler)).await;
452    ///     client.start().await?;
453    ///     
454    ///     // Reject the call
455    ///     client.reject_call(&call_id).await?;
456    ///     println!("Successfully rejected call: {}", call_id);
457    ///     
458    ///     Ok(())
459    /// }
460    /// ```
461    /// 
462    /// # Call Rejection Process
463    /// 
464    /// 1. Retrieves the stored incoming call information
465    /// 2. Sends a SIP rejection response (typically 603 Decline)
466    /// 3. Updates call state to Terminated
467    /// 4. Records rejection reason in metadata
468    /// 5. Emits appropriate events
469    /// 
470    /// # SIP Response Codes
471    /// 
472    /// The rejection will typically result in a SIP 603 "Decline" response being sent
473    /// to the caller, indicating that the call was explicitly rejected by the user.
474    pub async fn reject_call(&self, call_id: &CallId) -> ClientResult<()> {
475        // Get the stored IncomingCall object
476        let incoming_call = self.call_handler.get_incoming_call(call_id)
477            .await
478            .ok_or(ClientError::CallNotFound { call_id: *call_id })?;
479        
480        // Use SessionControl to reject the call
481        SessionControl::reject_incoming_call(
482            &self.coordinator,
483            &incoming_call,
484            "User rejected"
485        )
486        .await
487        .map_err(|e| ClientError::CallTerminated { 
488            reason: format!("Failed to reject call: {}", e) 
489        })?;
490        
491        // Update call info
492        if let Some(mut call_info) = self.call_info.get_mut(call_id) {
493            call_info.state = crate::call::CallState::Terminated;
494            call_info.ended_at = Some(Utc::now());
495            call_info.metadata.insert("rejected_at".to_string(), Utc::now().to_rfc3339());
496            call_info.metadata.insert("rejection_reason".to_string(), "user_rejected".to_string());
497        }
498        
499        tracing::info!("Rejected call {}", call_id);
500        Ok(())
501    }
502    
503    /// Terminate an active call (hang up)
504    /// 
505    /// This method terminates any call regardless of its current state. It handles
506    /// proper session cleanup and state management.
507    /// 
508    /// # Arguments
509    /// 
510    /// * `call_id` - The unique identifier of the call to terminate
511    /// 
512    /// # Returns
513    /// 
514    /// Returns `Ok(())` if the call was successfully terminated or was already terminated.
515    /// 
516    /// # Errors
517    /// 
518    /// * `ClientError::CallNotFound` - If no call exists with the given ID
519    /// * `ClientError::CallTerminated` - If the termination process fails
520    /// 
521    /// # Examples
522    /// 
523    /// Basic call hangup:
524    /// ```rust
525    /// use rvoip_client_core::{ClientManager, ClientConfig, CallId};
526    /// 
527    /// async fn hangup_active_call(call_id: CallId) -> Result<(), Box<dyn std::error::Error>> {
528    ///     let config = ClientConfig::new()
529    ///         .with_sip_addr("127.0.0.1:5064".parse()?);
530    ///     let client = ClientManager::new(config).await?;
531    ///     client.start().await?;
532    ///     
533    ///     // Terminate the call
534    ///     client.hangup_call(&call_id).await?;
535    ///     println!("Successfully hung up call: {}", call_id);
536    ///     
537    ///     Ok(())
538    /// }
539    /// ```
540    /// 
541    /// Hangup with error handling:
542    /// ```rust
543    /// use rvoip_client_core::{ClientManager, ClientConfig, CallId, ClientError};
544    /// 
545    /// async fn safe_hangup(call_id: CallId) -> Result<(), Box<dyn std::error::Error>> {
546    ///     let config = ClientConfig::new()
547    ///         .with_sip_addr("127.0.0.1:5065".parse()?);
548    ///     let client = ClientManager::new(config).await?;
549    ///     client.start().await?;
550    ///     
551    ///     match client.hangup_call(&call_id).await {
552    ///         Ok(()) => {
553    ///             println!("Call terminated successfully");
554    ///         }
555    ///         Err(ClientError::CallNotFound { .. }) => {
556    ///             println!("Call was already terminated or doesn't exist");
557    ///         }
558    ///         Err(e) => {
559    ///             eprintln!("Failed to hangup call: {}", e);
560    ///             return Err(e.into());
561    ///         }
562    ///     }
563    ///     
564    ///     Ok(())
565    /// }
566    /// ```
567    /// 
568    /// # Termination Process
569    /// 
570    /// 1. Locates the session associated with the call
571    /// 2. Checks current call state (skips if already terminated)
572    /// 3. Calls session-core to terminate the SIP session
573    /// 4. Updates call state to Terminated
574    /// 5. Updates statistics and emits events
575    /// 
576    /// # Idempotent Operation
577    /// 
578    /// This method is idempotent - calling it multiple times on the same call
579    /// will not cause errors. If the call is already terminated, it will return
580    /// success immediately.
581    pub async fn hangup_call(&self, call_id: &CallId) -> ClientResult<()> {
582        let session_id = self.session_mapping.get(call_id)
583            .ok_or(ClientError::CallNotFound { call_id: *call_id })?
584            .clone();
585            
586        // Check the current call state first
587        if let Some(call_info) = self.call_info.get(call_id) {
588            match call_info.state {
589                crate::call::CallState::Terminated |
590                crate::call::CallState::Failed |
591                crate::call::CallState::Cancelled => {
592                    tracing::info!("Call {} is already terminated (state: {:?}), skipping hangup", 
593                                 call_id, call_info.state);
594                    return Ok(());
595                }
596                _ => {
597                    // Proceed with termination for other states
598                }
599            }
600        }
601            
602        // Terminate the session using SessionControl trait
603        match SessionControl::terminate_session(&self.coordinator, &session_id).await {
604            Ok(()) => {
605                tracing::info!("Successfully terminated session for call {}", call_id);
606            }
607            Err(e) => {
608                // Check if the error is because the session is already terminated
609                let error_msg = e.to_string();
610                if error_msg.contains("No INVITE transaction found") || 
611                   error_msg.contains("already terminated") ||
612                   error_msg.contains("already in state") {
613                    tracing::warn!("Session already terminated for call {}: {}", call_id, error_msg);
614                    // Continue to update our internal state even if session is already gone
615                } else {
616                    return Err(ClientError::CallTerminated { 
617                        reason: format!("Failed to hangup call: {}", e) 
618                    });
619                }
620            }
621        }
622        
623        // Update call info
624        if let Some(mut call_info) = self.call_info.get_mut(call_id) {
625            let old_state = call_info.state.clone();
626            call_info.state = crate::call::CallState::Terminated;
627            call_info.ended_at = Some(Utc::now());
628            call_info.metadata.insert("hangup_at".to_string(), Utc::now().to_rfc3339());
629            call_info.metadata.insert("hangup_reason".to_string(), "user_hangup".to_string());
630            
631            // Emit state change event
632            let _ = self.event_tx.send(crate::events::ClientEvent::CallStateChanged {
633                info: crate::events::CallStatusInfo {
634                    call_id: *call_id,
635                    new_state: crate::call::CallState::Terminated,
636                    previous_state: Some(old_state),
637                    reason: Some("User hangup".to_string()),
638                    timestamp: Utc::now(),
639                },
640                priority: crate::events::EventPriority::Normal,
641            });
642        }
643        
644        // Clean up audio setup state if it exists
645        self.audio_setup_calls.remove(call_id);
646        
647        // Update stats - use saturating_sub to prevent integer underflow
648        let mut stats = self.stats.lock().await;
649        stats.connected_calls = stats.connected_calls.saturating_sub(1);
650        
651        tracing::info!("Hung up call {}", call_id);
652        Ok(())
653    }
654    
655    /// Get basic information about a specific call
656    /// 
657    /// Retrieves the current state and metadata for a call by its ID.
658    /// 
659    /// # Arguments
660    /// 
661    /// * `call_id` - The unique identifier of the call to query
662    /// 
663    /// # Returns
664    /// 
665    /// Returns a `CallInfo` struct containing all information about the call.
666    /// 
667    /// # Errors
668    /// 
669    /// * `ClientError::CallNotFound` - If no call exists with the given ID
670    /// 
671    /// # Examples
672    /// 
673    /// ```rust
674    /// use rvoip_client_core::{ClientManager, ClientConfig, CallId, CallState};
675    /// 
676    /// async fn check_call_status(call_id: CallId) -> Result<(), Box<dyn std::error::Error>> {
677    ///     let config = ClientConfig::new()
678    ///         .with_sip_addr("127.0.0.1:5066".parse()?);
679    ///     let client = ClientManager::new(config).await?;
680    ///     client.start().await?;
681    ///     
682    ///     let call_info = client.get_call(&call_id).await?;
683    ///     
684    ///     println!("Call ID: {}", call_info.call_id);
685    ///     println!("State: {:?}", call_info.state);
686    ///     println!("From: {}", call_info.local_uri);
687    ///     println!("To: {}", call_info.remote_uri);
688    ///     
689    ///     if let Some(connected_at) = call_info.connected_at {
690    ///         println!("Connected at: {}", connected_at);
691    ///     }
692    ///     
693    ///     match call_info.state {
694    ///         CallState::Connected => println!("Call is active"),
695    ///         CallState::Terminated => println!("Call has ended"),
696    ///         _ => println!("Call is in progress"),
697    ///     }
698    ///     
699    ///     Ok(())
700    /// }
701    /// ```
702    pub async fn get_call(&self, call_id: &CallId) -> ClientResult<CallInfo> {
703        self.call_info.get(call_id)
704            .map(|entry| entry.value().clone())
705            .ok_or(ClientError::CallNotFound { call_id: *call_id })
706    }
707    
708    /// Get detailed call information with enhanced metadata
709    /// 
710    /// Retrieves comprehensive information about a call including session metadata
711    /// and real-time statistics.
712    /// 
713    /// # Arguments
714    /// 
715    /// * `call_id` - The unique identifier of the call to query
716    /// 
717    /// # Returns
718    /// 
719    /// Returns a `CallInfo` struct with additional metadata fields populated.
720    /// 
721    /// # Errors
722    /// 
723    /// * `ClientError::CallNotFound` - If no call exists with the given ID
724    /// 
725    /// # Examples
726    /// 
727    /// ```rust
728    /// use rvoip_client_core::{ClientManager, ClientConfig, CallId};
729    /// 
730    /// async fn get_detailed_call_info(call_id: CallId) -> Result<(), Box<dyn std::error::Error>> {
731    ///     let config = ClientConfig::new()
732    ///         .with_sip_addr("127.0.0.1:5067".parse()?);
733    ///     let client = ClientManager::new(config).await?;
734    ///     client.start().await?;
735    ///     
736    ///     let detailed_info = client.get_call_detailed(&call_id).await?;
737    ///     
738    ///     println!("Call Details:");
739    ///     println!("  ID: {}", detailed_info.call_id);
740    ///     println!("  SIP Call-ID: {}", detailed_info.sip_call_id);
741    ///     
742    ///     // Check enhanced metadata
743    ///     for (key, value) in &detailed_info.metadata {
744    ///         println!("  {}: {}", key, value);
745    ///     }
746    ///     
747    ///     if let Some(session_id) = detailed_info.metadata.get("session_id") {
748    ///         println!("  Session tracking: {}", session_id);
749    ///     }
750    ///     
751    ///     Ok(())
752    /// }
753    /// ```
754    /// 
755    /// # Enhanced Metadata
756    /// 
757    /// The detailed call information includes additional metadata fields:
758    /// 
759    /// - `session_id` - The internal session-core session identifier
760    /// - `last_updated` - ISO 8601 timestamp of the last metadata update
761    /// - Plus any existing metadata from the basic call info
762    pub async fn get_call_detailed(&self, call_id: &CallId) -> ClientResult<CallInfo> {
763        // Get base call info
764        let mut call_info = self.call_info.get(call_id)
765            .map(|entry| entry.value().clone())
766            .ok_or(ClientError::CallNotFound { call_id: *call_id })?;
767            
768        // Add session metadata if available
769        if let Some(session_id) = self.session_mapping.get(call_id) {
770            call_info.metadata.insert("session_id".to_string(), session_id.0.clone());
771            call_info.metadata.insert("last_updated".to_string(), Utc::now().to_rfc3339());
772        }
773        
774        Ok(call_info)
775    }
776    
777    /// List all calls (active and historical)
778    /// 
779    /// Returns a vector of all calls known to the client, regardless of their state.
780    /// This includes active calls, completed calls, and failed calls.
781    /// 
782    /// # Returns
783    /// 
784    /// Returns a `Vec<CallInfo>` containing all calls. The list is not sorted.
785    /// 
786    /// # Examples
787    /// 
788    /// ```rust
789    /// use rvoip_client_core::{ClientManager, ClientConfig, CallState};
790    /// 
791    /// async fn review_all_calls() -> Result<(), Box<dyn std::error::Error>> {
792    ///     let config = ClientConfig::new()
793    ///         .with_sip_addr("127.0.0.1:5068".parse()?);
794    ///     let client = ClientManager::new(config).await?;
795    ///     client.start().await?;
796    ///     
797    ///     let all_calls = client.list_calls().await;
798    ///     
799    ///     println!("Total calls: {}", all_calls.len());
800    ///     
801    ///     for call in all_calls {
802    ///         println!("Call {}: {} -> {} ({:?})", 
803    ///                  call.call_id, 
804    ///                  call.local_uri, 
805    ///                  call.remote_uri, 
806    ///                  call.state);
807    ///     }
808    ///     
809    ///     Ok(())
810    /// }
811    /// ```
812    /// 
813    /// # Performance Note
814    /// 
815    /// This method iterates through all stored calls. For applications with
816    /// many historical calls, consider using filtered methods like
817    /// `get_active_calls()` or `get_calls_by_state()` instead.
818    pub async fn list_calls(&self) -> Vec<CallInfo> {
819        self.call_info.iter()
820            .map(|entry| entry.value().clone())
821            .collect()
822    }
823    
824    /// Get calls filtered by state
825    /// 
826    /// Returns all calls that are currently in the specified state.
827    /// 
828    /// # Arguments
829    /// 
830    /// * `state` - The call state to filter by
831    /// 
832    /// # Returns
833    /// 
834    /// Returns a `Vec<CallInfo>` containing all calls in the specified state.
835    /// 
836    /// # Examples
837    /// 
838    /// Get all connected calls:
839    /// ```rust
840    /// use rvoip_client_core::{ClientManager, ClientConfig, CallState};
841    /// 
842    /// async fn list_connected_calls() -> Result<(), Box<dyn std::error::Error>> {
843    ///     let config = ClientConfig::new()
844    ///         .with_sip_addr("127.0.0.1:5069".parse()?);
845    ///     let client = ClientManager::new(config).await?;
846    ///     client.start().await?;
847    ///     
848    ///     let connected_calls = client.get_calls_by_state(CallState::Connected).await;
849    ///     
850    ///     println!("Currently connected calls: {}", connected_calls.len());
851    ///     for call in connected_calls {
852    ///         if let Some(connected_at) = call.connected_at {
853    ///             let duration = chrono::Utc::now().signed_duration_since(connected_at);
854    ///             println!("Call {}: {} minutes active", 
855    ///                      call.call_id, 
856    ///                      duration.num_minutes());
857    ///         }
858    ///     }
859    ///     
860    ///     Ok(())
861    /// }
862    /// ```
863    /// 
864    /// Get all failed calls:
865    /// ```rust
866    /// use rvoip_client_core::{ClientManager, ClientConfig, CallState};
867    /// 
868    /// async fn review_failed_calls() -> Result<(), Box<dyn std::error::Error>> {
869    ///     let config = ClientConfig::new()
870    ///         .with_sip_addr("127.0.0.1:5070".parse()?);
871    ///     let client = ClientManager::new(config).await?;
872    ///     client.start().await?;
873    ///     
874    ///     let failed_calls = client.get_calls_by_state(CallState::Failed).await;
875    ///     
876    ///     for call in failed_calls {
877    ///         println!("Failed call: {} -> {}", call.local_uri, call.remote_uri);
878    ///         if let Some(reason) = call.metadata.get("failure_reason") {
879    ///             println!("  Reason: {}", reason);
880    ///         }
881    ///     }
882    ///     
883    ///     Ok(())
884    /// }
885    /// ```
886    pub async fn get_calls_by_state(&self, state: crate::call::CallState) -> Vec<CallInfo> {
887        self.call_info.iter()
888            .filter(|entry| entry.value().state == state)
889            .map(|entry| entry.value().clone())
890            .collect()
891    }
892    
893    /// Get calls filtered by direction (incoming or outgoing)
894    /// 
895    /// Returns all calls that match the specified direction.
896    /// 
897    /// # Arguments
898    /// 
899    /// * `direction` - The call direction to filter by (`Incoming` or `Outgoing`)
900    /// 
901    /// # Returns
902    /// 
903    /// Returns a `Vec<CallInfo>` containing all calls with the specified direction.
904    /// 
905    /// # Examples
906    /// 
907    /// Get all outgoing calls:
908    /// ```rust
909    /// use rvoip_client_core::{ClientManager, ClientConfig, CallDirection};
910    /// 
911    /// async fn review_outgoing_calls() -> Result<(), Box<dyn std::error::Error>> {
912    ///     let config = ClientConfig::new()
913    ///         .with_sip_addr("127.0.0.1:5071".parse()?);
914    ///     let client = ClientManager::new(config).await?;
915    ///     client.start().await?;
916    ///     
917    ///     let outgoing_calls = client.get_calls_by_direction(CallDirection::Outgoing).await;
918    ///     
919    ///     println!("Outgoing calls made: {}", outgoing_calls.len());
920    ///     for call in outgoing_calls {
921    ///         println!("Called: {} at {}", call.remote_uri, call.created_at);
922    ///     }
923    ///     
924    ///     Ok(())
925    /// }
926    /// ```
927    /// 
928    /// Get all incoming calls:
929    /// ```rust
930    /// use rvoip_client_core::{ClientManager, ClientConfig, CallDirection};
931    /// 
932    /// async fn review_incoming_calls() -> Result<(), Box<dyn std::error::Error>> {
933    ///     let config = ClientConfig::new()
934    ///         .with_sip_addr("127.0.0.1:5072".parse()?);
935    ///     let client = ClientManager::new(config).await?;
936    ///     client.start().await?;
937    ///     
938    ///     let incoming_calls = client.get_calls_by_direction(CallDirection::Incoming).await;
939    ///     
940    ///     println!("Calls received: {}", incoming_calls.len());
941    ///     for call in incoming_calls {
942    ///         println!("From: {} ({})", 
943    ///                  call.remote_display_name.as_deref().unwrap_or("Unknown"),
944    ///                  call.remote_uri);
945    ///     }
946    ///     
947    ///     Ok(())
948    /// }
949    /// ```
950    pub async fn get_calls_by_direction(&self, direction: CallDirection) -> Vec<CallInfo> {
951        self.call_info.iter()
952            .filter(|entry| entry.value().direction == direction)
953            .map(|entry| entry.value().clone())
954            .collect()
955    }
956    
957    /// Get call history (completed and terminated calls)
958    /// 
959    /// Returns all calls that have finished, regardless of how they ended.
960    /// This includes successfully completed calls, failed calls, and cancelled calls.
961    /// 
962    /// # Returns
963    /// 
964    /// Returns a `Vec<CallInfo>` containing all terminated calls.
965    /// 
966    /// # Examples
967    /// 
968    /// ```rust
969    /// use rvoip_client_core::{ClientManager, ClientConfig, CallState};
970    /// 
971    /// async fn generate_call_report() -> Result<(), Box<dyn std::error::Error>> {
972    ///     let config = ClientConfig::new()
973    ///         .with_sip_addr("127.0.0.1:5073".parse()?);
974    ///     let client = ClientManager::new(config).await?;
975    ///     client.start().await?;
976    ///     
977    ///     let history = client.get_call_history().await;
978    ///     
979    ///     let mut completed = 0;
980    ///     let mut failed = 0;
981    ///     let mut cancelled = 0;
982    ///     let mut total_duration = chrono::Duration::zero();
983    ///     
984    ///     for call in history {
985    ///         match call.state {
986    ///             CallState::Terminated => {
987    ///                 completed += 1;
988    ///                 if let (Some(connected), Some(ended)) = (call.connected_at, call.ended_at) {
989    ///                     total_duration = total_duration + ended.signed_duration_since(connected);
990    ///                 }
991    ///             }
992    ///             CallState::Failed => failed += 1,
993    ///             CallState::Cancelled => cancelled += 1,
994    ///             _ => {} // Should not happen in history
995    ///         }
996    ///     }
997    ///     
998    ///     println!("Call History Summary:");
999    ///     println!("  Completed: {}", completed);
1000    ///     println!("  Failed: {}", failed);
1001    ///     println!("  Cancelled: {}", cancelled);
1002    ///     println!("  Total talk time: {} minutes", total_duration.num_minutes());
1003    ///     
1004    ///     Ok(())
1005    /// }
1006    /// ```
1007    /// 
1008    /// # Use Cases
1009    /// 
1010    /// - Call reporting and analytics
1011    /// - Billing and usage tracking
1012    /// - Debugging call quality issues
1013    /// - User activity monitoring
1014    pub async fn get_call_history(&self) -> Vec<CallInfo> {
1015        self.call_info.iter()
1016            .filter(|entry| {
1017                matches!(entry.value().state, 
1018                    crate::call::CallState::Terminated |
1019                    crate::call::CallState::Failed |
1020                    crate::call::CallState::Cancelled
1021                )
1022            })
1023            .map(|entry| entry.value().clone())
1024            .collect()
1025    }
1026    
1027    /// Get all currently active calls
1028    /// 
1029    /// Returns all calls that are not in a terminated state. This includes
1030    /// calls that are connecting, ringing, connected, or in any other non-final state.
1031    /// 
1032    /// # Returns
1033    /// 
1034    /// Returns a `Vec<CallInfo>` containing all active calls.
1035    /// 
1036    /// # Examples
1037    /// 
1038    /// Monitor active calls:
1039    /// ```rust
1040    /// use rvoip_client_core::{ClientManager, ClientConfig, CallState};
1041    /// 
1042    /// async fn monitor_active_calls() -> Result<(), Box<dyn std::error::Error>> {
1043    ///     let config = ClientConfig::new()
1044    ///         .with_sip_addr("127.0.0.1:5074".parse()?);
1045    ///     let client = ClientManager::new(config).await?;
1046    ///     client.start().await?;
1047    ///     
1048    ///     let active_calls = client.get_active_calls().await;
1049    ///     
1050    ///     if active_calls.is_empty() {
1051    ///         println!("No active calls");
1052    ///     } else {
1053    ///         println!("Active calls: {}", active_calls.len());
1054    ///         
1055    ///         for call in active_calls {
1056    ///             match call.state {
1057    ///                 CallState::Initiating => {
1058    ///                     println!("📞 Dialing {} -> {}", call.local_uri, call.remote_uri);
1059    ///                 }
1060    ///                 CallState::Ringing => {
1061    ///                     println!("📳 Ringing {} -> {}", call.local_uri, call.remote_uri);
1062    ///                 }
1063    ///                 CallState::Connected => {
1064    ///                     if let Some(connected_at) = call.connected_at {
1065    ///                         let duration = chrono::Utc::now().signed_duration_since(connected_at);
1066    ///                         println!("☎️  Connected {} -> {} ({}:{})", 
1067    ///                                  call.local_uri, call.remote_uri,
1068    ///                                  duration.num_minutes(), 
1069    ///                                  duration.num_seconds() % 60);
1070    ///                     }
1071    ///                 }
1072    ///                 _ => {
1073    ///                     println!("📱 {} -> {} ({:?})", call.local_uri, call.remote_uri, call.state);
1074    ///                 }
1075    ///             }
1076    ///         }
1077    ///     }
1078    ///     
1079    ///     Ok(())
1080    /// }
1081    /// ```
1082    /// 
1083    /// # Real-time Monitoring
1084    /// 
1085    /// This method is useful for:
1086    /// - Dashboard displays showing current call status
1087    /// - Resource management (checking call limits)
1088    /// - User interface updates
1089    /// - Load balancing decisions
1090    pub async fn get_active_calls(&self) -> Vec<CallInfo> {
1091        self.call_info.iter()
1092            .filter(|entry| {
1093                !matches!(entry.value().state, 
1094                    crate::call::CallState::Terminated |
1095                    crate::call::CallState::Failed |
1096                    crate::call::CallState::Cancelled
1097                )
1098            })
1099            .map(|entry| entry.value().clone())
1100            .collect()
1101    }
1102    
1103    /// Get comprehensive client statistics
1104    /// 
1105    /// Returns detailed statistics about the client's call activity and performance.
1106    /// The statistics are recalculated on each call to ensure accuracy.
1107    /// 
1108    /// # Returns
1109    /// 
1110    /// Returns a `ClientStats` struct containing:
1111    /// - Total number of calls ever made/received
1112    /// - Currently connected calls count
1113    /// - Other performance metrics
1114    /// 
1115    /// # Examples
1116    /// 
1117    /// Basic statistics display:
1118    /// ```rust
1119    /// use rvoip_client_core::{ClientManager, ClientConfig};
1120    /// 
1121    /// async fn display_client_stats() -> Result<(), Box<dyn std::error::Error>> {
1122    ///     let config = ClientConfig::new()
1123    ///         .with_sip_addr("127.0.0.1:5075".parse()?);
1124    ///     let client = ClientManager::new(config).await?;
1125    ///     client.start().await?;
1126    ///     
1127    ///     let stats = client.get_client_stats().await;
1128    ///     
1129    ///     println!("Client Statistics:");
1130    ///     println!("  Total calls: {}", stats.total_calls);
1131    ///     println!("  Connected calls: {}", stats.connected_calls);
1132    ///     println!("  Utilization: {:.1}%", 
1133    ///              if stats.total_calls > 0 {
1134    ///                  (stats.connected_calls as f64 / stats.total_calls as f64) * 100.0
1135    ///              } else {
1136    ///                  0.0
1137    ///              });
1138    ///     
1139    ///     Ok(())
1140    /// }
1141    /// ```
1142    /// 
1143    /// Monitoring loop:
1144    /// ```rust
1145    /// use rvoip_client_core::{ClientManager, ClientConfig};
1146    /// use tokio::time::{interval, Duration};
1147    /// 
1148    /// async fn monitor_client_performance() -> Result<(), Box<dyn std::error::Error>> {
1149    ///     let config = ClientConfig::new()
1150    ///         .with_sip_addr("127.0.0.1:5076".parse()?);
1151    ///     let client = ClientManager::new(config).await?;
1152    ///     client.start().await?;
1153    ///     
1154    ///     let mut interval = interval(Duration::from_secs(30));
1155    ///     
1156    ///     // Monitor for a limited time in doc test
1157    ///     for _ in 0..3 {
1158    ///         interval.tick().await;
1159    ///         let stats = client.get_client_stats().await;
1160    ///         
1161    ///         println!("📊 Stats: {} total, {} active", 
1162    ///                  stats.total_calls, stats.connected_calls);
1163    ///         
1164    ///         if stats.connected_calls > 10 {
1165    ///             println!("⚠️  High call volume detected");
1166    ///         }
1167    ///     }
1168    ///     
1169    ///     Ok(())
1170    /// }
1171    /// ```
1172    /// 
1173    /// # Accuracy Guarantee
1174    /// 
1175    /// This method recalculates statistics from the actual call states rather than
1176    /// relying on potentially inconsistent counters. This prevents issues with:
1177    /// - Race conditions in concurrent call handling
1178    /// - Integer overflow/underflow
1179    /// - Inconsistent state after error recovery
1180    /// 
1181    /// # Performance Note
1182    /// 
1183    /// The recalculation involves iterating through all calls, so for applications
1184    /// with very large call histories, consider calling this method judiciously.
1185    pub async fn get_client_stats(&self) -> ClientStats {
1186        let mut stats = self.stats.lock().await.clone();
1187        
1188        // Always recalculate call counts from actual call states to avoid counter bugs
1189        // This prevents integer overflow/underflow issues from race conditions
1190        let _active_calls = self.get_active_calls().await;
1191        let connected_calls = self.get_calls_by_state(crate::call::CallState::Connected).await;
1192        
1193        // Use actual counts instead of potentially corrupted stored counters
1194        stats.connected_calls = connected_calls.len();
1195        stats.total_calls = self.call_info.len();
1196        
1197        // Ensure connected_calls never exceeds total_calls (defensive programming)
1198        if stats.connected_calls > stats.total_calls {
1199            tracing::warn!("Connected calls ({}) exceeded total calls ({}), correcting to total", 
1200                         stats.connected_calls, stats.total_calls);
1201            stats.connected_calls = stats.total_calls;
1202        }
1203        
1204        stats
1205    }
1206}