//! Session Manager for ACP Server
//!
//! This module provides session mapping and client connection tracking for the ACP Server.
//! It manages the relationship between client-facing session IDs and internal session IDs,
//! as well as tracking connected clients and their capabilities.
//!
//! ## Architecture
//!
//! The `SessionManager` uses `Arc<RwLock<...>>` internally for thread-safe access,
//! allowing multiple readers or a single writer at a time. This enables concurrent
//! read operations while maintaining data consistency during writes.
//!
//! ## Example
//!
//! ```rust
//! use dirigent_acp_api::session_manager::SessionManager;
//!
//! // Create a new session manager
//! let manager = SessionManager::new();
//!
//! // Register a client
//! let client_id = manager.register_client(None);
//!
//! // Create a session mapping for the client
//! let mapping = manager.create_mapping(
//!     &client_id,
//!     None,  // Auto-generate client_session_id
//!     "connector-1".to_string(),
//! );
//! ```

use std::collections::HashMap;
use std::sync::{Arc, RwLock};

use chrono::{DateTime, Utc};
use dirigent_protocol::streaming::{BusEvent, EventKind, EventOrigin, EventRouting};
use dirigent_protocol::{Event, SessionOwnership};
use serde::{Deserialize, Serialize};
use uuid::Uuid;

use crate::error::AcpServerError;

/// Closure that publishes a `BusEvent` to the shared event bus.
///
/// The ACP api crate can't depend on `dirigent_core::SharingBus` directly
/// (dependency direction is the other way round). Callers in
/// `web::acp_server` / `api::acp_controller` wrap the core's
/// `SharingBus::publish` call in a closure and install it via
/// [`SessionManagerBuilder::with_bus_publisher`]. The closure is
/// synchronous; implementations are expected to spawn the publish onto a
/// tokio task if the publish API is async.
pub type BusPublishFn = Arc<dyn Fn(BusEvent) + Send + Sync>;

/// Mapping between client-facing session ID and internal session ID
///
/// This struct tracks the relationship between the session ID exposed to ACP clients
/// and the internal session ID used by connectors. It also records which client and
/// connector the session belongs to.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionMapping {
    /// The session ID exposed to the ACP client
    pub client_session_id: String,

    /// The internal session ID used by the connector
    pub internal_session_id: String,

    /// The ID of the connector handling this session
    pub connector_id: String,

    /// The ID of the client that owns this session
    pub client_id: String,

    /// When this mapping was created
    pub created_at: DateTime<Utc>,

    /// Ownership model for this session
    ///
    /// Tracks whether this session is internal to Dirigent or originates from
    /// an external client, and how tool calls should be handled.
    pub ownership: SessionOwnership,
}

impl SessionMapping {
    /// Create a new session mapping
    pub fn new(
        client_session_id: String,
        internal_session_id: String,
        connector_id: String,
        client_id: String,
    ) -> Self {
        Self {
            client_session_id,
            internal_session_id,
            connector_id,
            client_id,
            created_at: Utc::now(),
            ownership: SessionOwnership::default(),
        }
    }
}

/// Information about a connected ACP client
///
/// This struct tracks client connections, including when they connected,
/// their last activity, and which sessions they have active.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClientConnection {
    /// Unique identifier for this client
    pub client_id: String,

    /// When the client first connected
    pub connected_at: DateTime<Utc>,

    /// When the client was last active (e.g., sent a request)
    pub last_activity: DateTime<Utc>,

    /// List of client_session_ids owned by this client
    pub sessions: Vec<String>,

    /// Optional client capabilities from the initialize handshake
    ///
    /// Contains information about what the client supports, such as
    /// protocol version, supported methods, etc.
    pub capabilities: Option<serde_json::Value>,

    /// Preferred agent type for connector routing (from --select-connector)
    pub preferred_connector: Option<String>,
}

impl ClientConnection {
    /// Create a new client connection
    pub fn new(
        client_id: String,
        capabilities: Option<serde_json::Value>,
        preferred_connector: Option<String>,
    ) -> Self {
        let now = Utc::now();
        Self {
            client_id,
            connected_at: now,
            last_activity: now,
            sessions: Vec::new(),
            capabilities,
            preferred_connector,
        }
    }

    /// Add a session to this client's session list
    pub fn add_session(&mut self, client_session_id: String) {
        if !self.sessions.contains(&client_session_id) {
            self.sessions.push(client_session_id);
        }
    }

    /// Remove a session from this client's session list
    pub fn remove_session(&mut self, client_session_id: &str) -> bool {
        if let Some(pos) = self.sessions.iter().position(|s| s == client_session_id) {
            self.sessions.remove(pos);
            true
        } else {
            false
        }
    }

    /// Update the last activity timestamp to now
    pub fn touch(&mut self) {
        self.last_activity = Utc::now();
    }
}

/// Lightweight client information for API responses
///
/// This struct provides a summary of a connected client suitable for
/// listing in admin interfaces and API responses. It contains only
/// the essential information without internal details.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClientInfo {
    /// Unique identifier for this client
    pub client_id: String,

    /// When the client first connected (ISO 8601 format)
    pub connected_at: String,

    /// Number of active sessions owned by this client
    pub active_sessions_count: usize,
}

impl ClientInfo {
    /// Create a new ClientInfo from a ClientConnection
    pub fn from_connection(conn: &ClientConnection) -> Self {
        Self {
            client_id: conn.client_id.clone(),
            connected_at: conn.connected_at.to_rfc3339(),
            active_sessions_count: conn.sessions.len(),
        }
    }
}

/// Internal state for the session manager
#[derive(Debug, Default)]
struct SessionManagerState {
    /// Map from client_session_id to SessionMapping
    mappings: HashMap<String, SessionMapping>,

    /// Map from client_id to ClientConnection
    clients: HashMap<String, ClientConnection>,
}

/// Builder configuration for SessionManager
pub struct SessionManagerBuilder {
    bus_publisher: Option<BusPublishFn>,
    acceptor_connector_uid: Option<Uuid>,
}

impl SessionManagerBuilder {
    /// Create a new builder
    pub fn new() -> Self {
        Self {
            bus_publisher: None,
            acceptor_connector_uid: None,
        }
    }

    /// Set the bus publisher for emitting ACP client events.
    ///
    /// When set, the SessionManager will publish events like
    /// `AcpClientConnected` and `AcpClientDisconnected` onto the shared
    /// event bus via this callback.
    pub fn with_bus_publisher(mut self, publisher: BusPublishFn) -> Self {
        self.bus_publisher = Some(publisher);
        self
    }

    /// Set the Acceptor connector's UID for meta session creation
    ///
    /// This UID is included in `AcpClientConnected` events so the archivist
    /// can create meta sessions under the correct connector.
    pub fn with_acceptor_connector_uid(mut self, uid: Uuid) -> Self {
        self.acceptor_connector_uid = Some(uid);
        self
    }

    /// Build the SessionManager
    pub fn build(self) -> SessionManager {
        SessionManager {
            state: Arc::new(RwLock::new(SessionManagerState::default())),
            bus_publisher: self.bus_publisher,
            acceptor_connector_uid: self.acceptor_connector_uid,
        }
    }
}

impl Default for SessionManagerBuilder {
    fn default() -> Self {
        Self::new()
    }
}

/// Thread-safe session manager for the ACP Server
///
/// Manages session mappings between client-facing and internal session IDs,
/// as well as tracking connected clients and their capabilities.
///
/// The `SessionManager` is designed to be cloned and shared across async tasks.
/// All operations are thread-safe due to internal `Arc<RwLock<...>>` synchronization.
///
/// ## Event Emission
///
/// When configured with a bus publisher (via `SessionManagerBuilder`), the
/// manager will emit ACP client connection events onto the shared event bus:
/// - `AcpClientConnected` when a client registers
/// - `AcpClientDisconnected` when a client unregisters or is disconnected
#[derive(Clone)]
pub struct SessionManager {
    state: Arc<RwLock<SessionManagerState>>,
    /// Optional bus publisher for emitting ACP client events
    bus_publisher: Option<BusPublishFn>,
    /// The Acceptor connector's UID for meta session creation
    acceptor_connector_uid: Option<Uuid>,
}

impl std::fmt::Debug for SessionManager {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("SessionManager")
            .field("state", &self.state)
            .field("has_bus_publisher", &self.bus_publisher.is_some())
            .field("acceptor_connector_uid", &self.acceptor_connector_uid)
            .finish()
    }
}

impl Default for SessionManager {
    fn default() -> Self {
        Self::new()
    }
}

impl SessionManager {
    /// Create a new session manager
    pub fn new() -> Self {
        Self {
            state: Arc::new(RwLock::new(SessionManagerState::default())),
            bus_publisher: None,
            acceptor_connector_uid: None,
        }
    }

    /// Create a builder for more advanced configuration
    pub fn builder() -> SessionManagerBuilder {
        SessionManagerBuilder::new()
    }

    /// Emit an event if a bus publisher is configured.
    ///
    /// Wraps the event in a default-routed `BusEvent` with
    /// `EventOrigin::Runtime`. Subscribers interested in more precise
    /// routing (e.g., ACP-specific filtering) can match on the event
    /// payload itself.
    fn emit_event(&self, event: Event) {
        if let Some(ref publisher) = self.bus_publisher {
            let kind = match &event {
                Event::AcpClientConnected { .. }
                | Event::AcpClientDisconnected { .. }
                | Event::AcpClientSessionOpened { .. }
                | Event::AcpClientSessionRouted { .. } => EventKind::System,
                _ => EventKind::SessionLifecycle,
            };
            let bus_event = BusEvent {
                routing: EventRouting {
                    kind,
                    ..Default::default()
                },
                origin: EventOrigin::Runtime,
                event: Arc::new(event),
            };
            publisher(bus_event);
        }
    }

    /// Create a new session mapping
    ///
    /// # Parameters
    /// - `client_id`: The ID of the client creating this session
    /// - `client_session_id`: Optional client-provided session ID. If None, a UUID will be generated.
    /// - `connector_id`: The ID of the connector that will handle this session
    ///
    /// # Returns
    /// The created `SessionMapping`
    ///
    /// # Panics
    /// Panics if the internal lock is poisoned.
    pub fn create_mapping(
        &self,
        client_id: &str,
        client_session_id: Option<String>,
        connector_id: String,
    ) -> SessionMapping {
        // client_session_id is an external ID from the connector client (can be UUID4, string, etc.)
        // We preserve it as-is for external system compatibility.
        let client_session_id = client_session_id.unwrap_or_else(|| Uuid::new_v4().to_string());

        // internal_session_id is used internally and passed to archivist as native_session_id.
        // Use UUID7 for time-ordered, sortable identifiers.
        let internal_session_id = Uuid::now_v7().to_string();

        let mapping = SessionMapping::new(
            client_session_id.clone(),
            internal_session_id,
            connector_id,
            client_id.to_string(),
        );

        let mut state = self.state.write().expect("Lock poisoned");

        // Store the mapping
        state.mappings.insert(client_session_id.clone(), mapping.clone());

        // Add session to client's session list
        if let Some(client) = state.clients.get_mut(client_id) {
            client.add_session(client_session_id.clone());
        }

        // Emit AcpClientSessionOpened event
        self.emit_event(Event::AcpClientSessionOpened {
            client_id: client_id.to_string(),
            gateway_session_id: mapping.internal_session_id.clone(),
            client_session_id: client_session_id,
            timestamp: chrono::Utc::now().to_rfc3339(),
        });

        mapping
    }

    /// Get a session mapping by client session ID
    ///
    /// # Parameters
    /// - `client_session_id`: The client-facing session ID to look up
    ///
    /// # Returns
    /// `Some(SessionMapping)` if found, `None` otherwise
    pub fn get_mapping(&self, client_session_id: &str) -> Option<SessionMapping> {
        let state = self.state.read().expect("Lock poisoned");
        state.mappings.get(client_session_id).cloned()
    }

    /// Remove a session mapping by client session ID
    ///
    /// This also removes the session from the owning client's session list.
    ///
    /// # Parameters
    /// - `client_session_id`: The client-facing session ID to remove
    ///
    /// # Returns
    /// `Some(SessionMapping)` if found and removed, `None` otherwise
    pub fn remove_mapping(&self, client_session_id: &str) -> Option<SessionMapping> {
        let mut state = self.state.write().expect("Lock poisoned");

        // Remove the mapping
        let mapping = state.mappings.remove(client_session_id);

        // Also remove from client's session list if mapping existed
        if let Some(ref m) = mapping {
            if let Some(client) = state.clients.get_mut(&m.client_id) {
                client.remove_session(client_session_id);
            }
        }

        mapping
    }

    /// Register a new client connection
    ///
    /// # Parameters
    /// - `capabilities`: Optional client capabilities from the initialize handshake
    ///
    /// # Returns
    /// The generated unique client ID (UUID7 for time-ordered tracking)
    pub fn register_client(&self, capabilities: Option<serde_json::Value>) -> String {
        let client_id = Uuid::now_v7().to_string();
        let connected_at = Utc::now();
        let client = ClientConnection::new(client_id.clone(), capabilities.clone(), None);

        let mut state = self.state.write().expect("Lock poisoned");
        state.clients.insert(client_id.clone(), client);

        tracing::info!(client_id = %client_id, "ACP client connected");

        // Emit AcpClientConnected event
        self.emit_event(Event::AcpClientConnected {
            client_id: client_id.clone(),
            connected_at: connected_at.to_rfc3339(),
            capabilities,
            connector_uid: self.acceptor_connector_uid
                .map(|u| u.to_string())
                .unwrap_or_else(|| Uuid::nil().to_string()),
        });

        client_id
    }

    /// Register a client with a specific client_id (for HTTP clients with X-Client-Id header)
    ///
    /// # Parameters
    /// - `client_id`: The client ID provided by the client (from X-Client-Id header)
    /// - `capabilities`: Optional client capabilities
    ///
    /// # Panics
    /// Panics if the internal lock is poisoned.
    pub fn register_client_with_id(&self, client_id: String, capabilities: Option<serde_json::Value>) {
        let connected_at = Utc::now();
        let is_new_client;

        {
            let mut state = self.state.write().expect("Lock poisoned");
            is_new_client = !state.clients.contains_key(&client_id);

            let client = ClientConnection::new(client_id.clone(), capabilities.clone(), None);
            state.clients.insert(client_id.clone(), client);
        }

        if is_new_client {
            tracing::info!(client_id = %client_id, "ACP client connected with provided ID");

            // Emit AcpClientConnected event for new clients
            self.emit_event(Event::AcpClientConnected {
                client_id: client_id.clone(),
                connected_at: connected_at.to_rfc3339(),
                capabilities,
                connector_uid: self.acceptor_connector_uid
                    .map(|u| u.to_string())
                    .unwrap_or_else(|| Uuid::nil().to_string()),
            });
        } else {
            tracing::debug!(client_id = %client_id, "ACP client reconnected with existing ID");
        }
    }

    /// Unregister a client and remove all associated session mappings
    ///
    /// # Parameters
    /// - `client_id`: The ID of the client to unregister
    ///
    /// # Returns
    /// A list of session mappings that were removed (for cleanup purposes)
    pub fn unregister_client(&self, client_id: &str) -> Vec<SessionMapping> {
        let disconnected_at = Utc::now();
        let removed_mappings;
        let was_connected;

        {
            let mut state = self.state.write().expect("Lock poisoned");

            // Remove the client and get their sessions
            let client = state.clients.remove(client_id);

            let mut mappings = Vec::new();

            if let Some(client) = client {
                tracing::info!(client_id = %client_id, "ACP client disconnected");

                // Remove all session mappings owned by this client
                for session_id in client.sessions {
                    if let Some(mapping) = state.mappings.remove(&session_id) {
                        mappings.push(mapping);
                    }
                }
                was_connected = true;
            } else {
                was_connected = false;
            }

            removed_mappings = mappings;
        }

        // Emit AcpClientDisconnected event if client was found
        if was_connected {
            self.emit_event(Event::AcpClientDisconnected {
                client_id: client_id.to_string(),
                disconnected_at: disconnected_at.to_rfc3339(),
                reason: None,
            });
        }

        removed_mappings
    }

    /// Update the last activity timestamp for a client
    ///
    /// # Parameters
    /// - `client_id`: The ID of the client to update
    ///
    /// # Returns
    /// `true` if the client was found and updated, `false` otherwise
    pub fn update_client_activity(&self, client_id: &str) -> bool {
        let mut state = self.state.write().expect("Lock poisoned");

        if let Some(client) = state.clients.get_mut(client_id) {
            client.touch();
            true
        } else {
            false
        }
    }

    /// Update the preferred connector for a client
    ///
    /// # Parameters
    /// - `client_id`: The ID of the client to update
    /// - `preferred_connector`: The preferred connector ID or magic word
    ///
    /// # Returns
    /// `true` if the client was found and updated, `false` otherwise
    pub fn update_client_preferred_connector(
        &self,
        client_id: &str,
        preferred_connector: Option<String>,
    ) -> bool {
        let mut state = self.state.write().expect("Lock poisoned");

        if let Some(client) = state.clients.get_mut(client_id) {
            client.preferred_connector = preferred_connector;
            true
        } else {
            false
        }
    }

    /// List all sessions for a client
    ///
    /// # Parameters
    /// - `client_id`: The ID of the client
    ///
    /// # Returns
    /// A list of client_session_ids owned by the client, or empty vec if client not found
    pub fn list_client_sessions(&self, client_id: &str) -> Vec<String> {
        let state = self.state.read().expect("Lock poisoned");

        state
            .clients
            .get(client_id)
            .map(|c| c.sessions.clone())
            .unwrap_or_default()
    }

    /// Get information about a client
    ///
    /// # Parameters
    /// - `client_id`: The ID of the client
    ///
    /// # Returns
    /// `Some(ClientConnection)` if found, `None` otherwise
    pub fn get_client(&self, client_id: &str) -> Option<ClientConnection> {
        let state = self.state.read().expect("Lock poisoned");
        state.clients.get(client_id).cloned()
    }

    /// Get the number of active session mappings
    pub fn mapping_count(&self) -> usize {
        let state = self.state.read().expect("Lock poisoned");
        state.mappings.len()
    }

    /// Get the number of connected clients
    pub fn client_count(&self) -> usize {
        let state = self.state.read().expect("Lock poisoned");
        state.clients.len()
    }

    /// List all connected clients
    pub fn list_clients(&self) -> Vec<ClientConnection> {
        let state = self.state.read().expect("Lock poisoned");
        state.clients.values().cloned().collect()
    }

    /// List all session mappings
    pub fn list_mappings(&self) -> Vec<SessionMapping> {
        let state = self.state.read().expect("Lock poisoned");
        state.mappings.values().cloned().collect()
    }

    /// Get a mapping by internal session ID
    ///
    /// This is useful when receiving events from a connector that uses internal IDs.
    ///
    /// # Parameters
    /// - `internal_session_id`: The internal session ID to look up
    ///
    /// # Returns
    /// `Some(SessionMapping)` if found, `None` otherwise
    pub fn get_mapping_by_internal_id(&self, internal_session_id: &str) -> Option<SessionMapping> {
        let state = self.state.read().expect("Lock poisoned");
        state
            .mappings
            .values()
            .find(|m| m.internal_session_id == internal_session_id)
            .cloned()
    }

    /// List all connected clients with lightweight info
    ///
    /// Returns a summary of all connected clients suitable for API responses.
    /// This is more efficient than `list_clients()` when full client details
    /// are not needed.
    ///
    /// # Returns
    /// A vector of `ClientInfo` containing summary information about each client
    pub fn list_clients_info(&self) -> Vec<ClientInfo> {
        let state = self.state.read().expect("Lock poisoned");
        state
            .clients
            .values()
            .map(ClientInfo::from_connection)
            .collect()
    }

    /// Force disconnect a client and clean up all associated sessions
    ///
    /// This method forcibly removes a client and all their session mappings.
    /// Use this for administrative disconnection of clients.
    ///
    /// # Parameters
    /// - `client_id`: The ID of the client to disconnect
    ///
    /// # Returns
    /// - `Ok(())` if the client was found and disconnected
    /// - `Err(AcpServerError::ClientNotFound)` if the client does not exist
    ///
    /// # Example
    ///
    /// ```rust
    /// use dirigent_acp_api::session_manager::SessionManager;
    ///
    /// let manager = SessionManager::new();
    /// let client_id = manager.register_client(None);
    ///
    /// // Disconnect the client
    /// assert!(manager.disconnect_client(&client_id).is_ok());
    ///
    /// // Disconnecting again returns an error
    /// assert!(manager.disconnect_client(&client_id).is_err());
    /// ```
    pub fn disconnect_client(&self, client_id: &str) -> Result<(), AcpServerError> {
        let disconnected_at = Utc::now();

        {
            let mut state = self.state.write().expect("Lock poisoned");

            // Check if the client exists
            if !state.clients.contains_key(client_id) {
                return Err(AcpServerError::ClientNotFound(client_id.to_string()));
            }

            // Remove the client and get their sessions
            if let Some(client) = state.clients.remove(client_id) {
                tracing::info!(client_id = %client_id, "ACP client disconnected (force disconnect)");

                // Remove all session mappings owned by this client
                for session_id in client.sessions {
                    state.mappings.remove(&session_id);
                }
            }
        }

        // Emit AcpClientDisconnected event
        self.emit_event(Event::AcpClientDisconnected {
            client_id: client_id.to_string(),
            disconnected_at: disconnected_at.to_rfc3339(),
            reason: Some("Force disconnect".to_string()),
        });

        Ok(())
    }

    /// Update a session mapping to point to a different connector
    ///
    /// Used when a session is transferred from one connector to another.
    /// The client_session_id remains the same, but the internal routing changes.
    ///
    /// # Arguments
    /// * `client_session_id` - The client-facing session ID (unchanged)
    /// * `new_connector_id` - The new connector to route to
    /// * `new_connector_title` - Display title for the new connector
    /// * `new_internal_session_id` - The new internal session ID in the target connector
    ///
    /// # Returns
    /// The old mapping if it existed, None otherwise
    pub fn update_mapping_connector(
        &self,
        client_session_id: &str,
        new_connector_id: String,
        new_connector_title: Option<String>,
        new_internal_session_id: String,
    ) -> Option<SessionMapping> {
        let mut state = self.state.write().expect("Lock poisoned");

        let mapping = state.mappings.get_mut(client_session_id)?;

        // Capture old state for return
        let old_mapping = mapping.clone();

        // Update routing
        let from_session_id = mapping.internal_session_id.clone();
        mapping.connector_id = new_connector_id.clone();
        mapping.internal_session_id = new_internal_session_id.clone();

        // Get client_id before releasing lock
        let client_id = mapping.client_id.clone();

        tracing::info!(
            client_session_id = %client_session_id,
            old_connector = %old_mapping.connector_id,
            new_connector = %mapping.connector_id,
            "Session mapping updated for transfer"
        );

        // Must drop state lock before emitting event (emit_event might do I/O)
        drop(state);

        // Emit AcpClientSessionRouted event
        let connector_title = new_connector_title.unwrap_or_else(|| new_connector_id.clone());
        self.emit_event(Event::AcpClientSessionRouted {
            client_id,
            from_session_id,
            to_session_id: new_internal_session_id,
            connector_id: new_connector_id,
            connector_title,
            connector_kind: None,  // TODO: Populate from connector info
            model: None,           // TODO: Populate from session/connector state
            agent_info: None,      // TODO: Populate from connector capabilities
            timestamp: chrono::Utc::now().to_rfc3339(),
        });

        Some(old_mapping)
    }

    /// Update the ownership model for a session mapping
    ///
    /// # Parameters
    /// * `client_session_id` - The client-facing session ID
    /// * `ownership` - The new ownership model
    ///
    /// # Returns
    /// True if the mapping was found and updated, false otherwise
    pub fn update_mapping_ownership(
        &self,
        client_session_id: &str,
        ownership: SessionOwnership,
    ) -> bool {
        let mut state = self.state.write().expect("Lock poisoned");

        if let Some(mapping) = state.mappings.get_mut(client_session_id) {
            mapping.ownership = ownership;
            tracing::debug!(
                client_session_id = %client_session_id,
                "Updated session ownership model"
            );
            true
        } else {
            false
        }
    }

    /// Update the internal session ID for a mapping
    ///
    /// This is called after session creation to link the mapping to the actual
    /// connector session ID. This is necessary because the mapping is created
    /// before the connector session, and we generate a placeholder internal_session_id.
    ///
    /// # Parameters
    /// * `client_session_id` - The client-facing session ID
    /// * `new_internal_session_id` - The actual session ID from the connector
    ///
    /// # Returns
    /// The old internal_session_id if the mapping was found, None otherwise
    pub fn update_mapping_internal_session(
        &self,
        client_session_id: &str,
        new_internal_session_id: String,
    ) -> Option<String> {
        let mut state = self.state.write().expect("Lock poisoned");

        if let Some(mapping) = state.mappings.get_mut(client_session_id) {
            let old_internal_id = std::mem::replace(&mut mapping.internal_session_id, new_internal_session_id.clone());
            tracing::info!(
                client_session_id = %client_session_id,
                old_internal_id = %old_internal_id,
                new_internal_id = %new_internal_session_id,
                "Updated internal session ID to match connector session"
            );
            Some(old_internal_id)
        } else {
            None
        }
    }

    /// Find a mapping by the Gateway session ID (for transfer correlation)
    ///
    /// Used to find the client mapping when we receive a SessionTransferred event
    /// that references the original Gateway session.
    pub fn get_mapping_by_gateway_session(
        &self,
        gateway_session_id: &str,
    ) -> Option<SessionMapping> {
        let state = self.state.read().expect("Lock poisoned");

        // Look for a mapping where internal_session_id matches the gateway session
        state.mappings.values().find(|m| {
            m.internal_session_id == gateway_session_id
        }).cloned()
    }
}

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

    #[test]
    fn test_session_mapping_creation() {
        let mapping = SessionMapping::new(
            "client-sess-1".to_string(),
            "internal-sess-1".to_string(),
            "connector-1".to_string(),
            "client-1".to_string(),
        );

        assert_eq!(mapping.client_session_id, "client-sess-1");
        assert_eq!(mapping.internal_session_id, "internal-sess-1");
        assert_eq!(mapping.connector_id, "connector-1");
        assert_eq!(mapping.client_id, "client-1");
    }

    #[test]
    fn test_client_connection_creation() {
        let capabilities = serde_json::json!({"version": "1.0"});
        let client = ClientConnection::new("client-1".to_string(), Some(capabilities.clone()), None);

        assert_eq!(client.client_id, "client-1");
        assert!(client.sessions.is_empty());
        assert_eq!(client.capabilities, Some(capabilities));
    }

    #[test]
    fn test_client_connection_sessions() {
        let mut client = ClientConnection::new("client-1".to_string(), None, None);

        // Add sessions
        client.add_session("session-1".to_string());
        client.add_session("session-2".to_string());
        assert_eq!(client.sessions.len(), 2);

        // Adding duplicate should not increase count
        client.add_session("session-1".to_string());
        assert_eq!(client.sessions.len(), 2);

        // Remove session
        assert!(client.remove_session("session-1"));
        assert_eq!(client.sessions.len(), 1);

        // Removing non-existent session returns false
        assert!(!client.remove_session("session-1"));
    }

    #[test]
    fn test_session_manager_new() {
        let manager = SessionManager::new();
        assert_eq!(manager.client_count(), 0);
        assert_eq!(manager.mapping_count(), 0);
    }

    #[test]
    fn test_session_manager_register_client() {
        let manager = SessionManager::new();

        let client_id = manager.register_client(None);
        assert!(!client_id.is_empty());
        assert_eq!(manager.client_count(), 1);

        let client = manager.get_client(&client_id);
        assert!(client.is_some());
        let client = client.unwrap();
        assert_eq!(client.client_id, client_id);
        assert!(client.capabilities.is_none());
    }

    #[test]
    fn test_session_manager_register_client_with_capabilities() {
        let manager = SessionManager::new();
        let capabilities = serde_json::json!({"protocol": "acp/1.0"});

        let client_id = manager.register_client(Some(capabilities.clone()));
        let client = manager.get_client(&client_id).unwrap();
        assert_eq!(client.capabilities, Some(capabilities));
    }

    #[test]
    fn test_session_manager_create_mapping() {
        let manager = SessionManager::new();

        // Register a client first
        let client_id = manager.register_client(None);

        // Create a mapping
        let mapping = manager.create_mapping(&client_id, None, "connector-1".to_string());

        assert!(!mapping.client_session_id.is_empty());
        assert!(!mapping.internal_session_id.is_empty());
        assert_eq!(mapping.connector_id, "connector-1");
        assert_eq!(mapping.client_id, client_id);

        // Verify mapping was stored
        assert_eq!(manager.mapping_count(), 1);

        // Verify session was added to client
        let sessions = manager.list_client_sessions(&client_id);
        assert_eq!(sessions.len(), 1);
        assert_eq!(sessions[0], mapping.client_session_id);
    }

    #[test]
    fn test_session_manager_create_mapping_with_custom_id() {
        let manager = SessionManager::new();
        let client_id = manager.register_client(None);

        let mapping = manager.create_mapping(
            &client_id,
            Some("my-custom-session-id".to_string()),
            "connector-1".to_string(),
        );

        assert_eq!(mapping.client_session_id, "my-custom-session-id");
    }

    #[test]
    fn test_session_manager_get_mapping() {
        let manager = SessionManager::new();
        let client_id = manager.register_client(None);

        let mapping = manager.create_mapping(&client_id, None, "connector-1".to_string());

        // Get existing mapping
        let retrieved = manager.get_mapping(&mapping.client_session_id);
        assert!(retrieved.is_some());
        let retrieved = retrieved.unwrap();
        assert_eq!(retrieved.client_session_id, mapping.client_session_id);

        // Get non-existent mapping
        assert!(manager.get_mapping("non-existent").is_none());
    }

    #[test]
    fn test_session_manager_remove_mapping() {
        let manager = SessionManager::new();
        let client_id = manager.register_client(None);

        let mapping = manager.create_mapping(&client_id, None, "connector-1".to_string());
        let session_id = mapping.client_session_id.clone();

        assert_eq!(manager.mapping_count(), 1);

        // Remove the mapping
        let removed = manager.remove_mapping(&session_id);
        assert!(removed.is_some());
        assert_eq!(removed.unwrap().client_session_id, session_id);
        assert_eq!(manager.mapping_count(), 0);

        // Verify session was removed from client
        let sessions = manager.list_client_sessions(&client_id);
        assert!(sessions.is_empty());

        // Removing again returns None
        assert!(manager.remove_mapping(&session_id).is_none());
    }

    #[test]
    fn test_session_manager_unregister_client() {
        let manager = SessionManager::new();
        let client_id = manager.register_client(None);

        // Create some sessions
        manager.create_mapping(&client_id, None, "connector-1".to_string());
        manager.create_mapping(&client_id, None, "connector-2".to_string());

        assert_eq!(manager.client_count(), 1);
        assert_eq!(manager.mapping_count(), 2);

        // Unregister the client
        let removed_mappings = manager.unregister_client(&client_id);

        assert_eq!(removed_mappings.len(), 2);
        assert_eq!(manager.client_count(), 0);
        assert_eq!(manager.mapping_count(), 0);
    }

    #[test]
    fn test_session_manager_unregister_nonexistent_client() {
        let manager = SessionManager::new();
        let removed = manager.unregister_client("nonexistent");
        assert!(removed.is_empty());
    }

    #[test]
    fn test_session_manager_update_client_activity() {
        let manager = SessionManager::new();
        let client_id = manager.register_client(None);

        let client_before = manager.get_client(&client_id).unwrap();
        let activity_before = client_before.last_activity;

        // Small delay to ensure time difference
        std::thread::sleep(std::time::Duration::from_millis(10));

        assert!(manager.update_client_activity(&client_id));

        let client_after = manager.get_client(&client_id).unwrap();
        assert!(client_after.last_activity > activity_before);
    }

    #[test]
    fn test_session_manager_update_nonexistent_client_activity() {
        let manager = SessionManager::new();
        assert!(!manager.update_client_activity("nonexistent"));
    }

    #[test]
    fn test_session_manager_list_client_sessions() {
        let manager = SessionManager::new();
        let client_id = manager.register_client(None);

        // Empty initially
        assert!(manager.list_client_sessions(&client_id).is_empty());

        // Create sessions
        let m1 = manager.create_mapping(&client_id, None, "connector-1".to_string());
        let m2 = manager.create_mapping(&client_id, None, "connector-2".to_string());

        let sessions = manager.list_client_sessions(&client_id);
        assert_eq!(sessions.len(), 2);
        assert!(sessions.contains(&m1.client_session_id));
        assert!(sessions.contains(&m2.client_session_id));
    }

    #[test]
    fn test_session_manager_list_clients() {
        let manager = SessionManager::new();

        manager.register_client(None);
        manager.register_client(Some(serde_json::json!({"test": true})));

        let clients = manager.list_clients();
        assert_eq!(clients.len(), 2);
    }

    #[test]
    fn test_session_manager_list_mappings() {
        let manager = SessionManager::new();
        let client_id = manager.register_client(None);

        manager.create_mapping(&client_id, None, "connector-1".to_string());
        manager.create_mapping(&client_id, None, "connector-2".to_string());

        let mappings = manager.list_mappings();
        assert_eq!(mappings.len(), 2);
    }

    #[test]
    fn test_session_manager_get_mapping_by_internal_id() {
        let manager = SessionManager::new();
        let client_id = manager.register_client(None);

        let mapping = manager.create_mapping(&client_id, None, "connector-1".to_string());
        let internal_id = mapping.internal_session_id.clone();

        // Find by internal ID
        let found = manager.get_mapping_by_internal_id(&internal_id);
        assert!(found.is_some());
        assert_eq!(found.unwrap().internal_session_id, internal_id);

        // Not found
        assert!(manager.get_mapping_by_internal_id("nonexistent").is_none());
    }

    #[test]
    fn test_session_manager_clone() {
        let manager = SessionManager::new();
        let cloned = manager.clone();

        // Both should share the same state
        let client_id = manager.register_client(None);
        assert_eq!(cloned.client_count(), 1);
        assert!(cloned.get_client(&client_id).is_some());
    }

    #[test]
    fn test_session_manager_default() {
        let manager: SessionManager = Default::default();
        assert_eq!(manager.client_count(), 0);
    }

    #[test]
    fn test_session_mapping_serialization() {
        let mapping = SessionMapping::new(
            "client-sess".to_string(),
            "internal-sess".to_string(),
            "connector-1".to_string(),
            "client-1".to_string(),
        );

        let json = serde_json::to_string(&mapping).unwrap();
        assert!(json.contains("client-sess"));
        assert!(json.contains("internal-sess"));

        let deserialized: SessionMapping = serde_json::from_str(&json).unwrap();
        assert_eq!(deserialized.client_session_id, mapping.client_session_id);
    }

    #[test]
    fn test_client_connection_serialization() {
        let client = ClientConnection::new(
            "client-1".to_string(),
            Some(serde_json::json!({"version": "1.0"})),
            None,
        );

        let json = serde_json::to_string(&client).unwrap();
        assert!(json.contains("client-1"));
        assert!(json.contains("version"));

        let deserialized: ClientConnection = serde_json::from_str(&json).unwrap();
        assert_eq!(deserialized.client_id, client.client_id);
    }

    #[test]
    fn test_client_info_from_connection() {
        let mut client = ClientConnection::new("client-1".to_string(), None, None);
        client.add_session("session-1".to_string());
        client.add_session("session-2".to_string());

        let info = ClientInfo::from_connection(&client);

        assert_eq!(info.client_id, "client-1");
        assert_eq!(info.active_sessions_count, 2);
        // connected_at should be a valid ISO 8601 string
        assert!(info.connected_at.contains("T")); // ISO 8601 format contains 'T'
    }

    #[test]
    fn test_client_info_serialization() {
        let info = ClientInfo {
            client_id: "client-123".to_string(),
            connected_at: "2024-01-01T00:00:00Z".to_string(),
            active_sessions_count: 3,
        };

        let json = serde_json::to_string(&info).unwrap();
        assert!(json.contains("client-123"));
        assert!(json.contains("2024-01-01T00:00:00Z"));
        assert!(json.contains("3"));

        let deserialized: ClientInfo = serde_json::from_str(&json).unwrap();
        assert_eq!(deserialized.client_id, info.client_id);
        assert_eq!(deserialized.connected_at, info.connected_at);
        assert_eq!(deserialized.active_sessions_count, info.active_sessions_count);
    }

    #[test]
    fn test_session_manager_list_clients_info() {
        let manager = SessionManager::new();

        let client1_id = manager.register_client(None);
        let client2_id = manager.register_client(None);

        // Add sessions to client1
        manager.create_mapping(&client1_id, None, "connector-1".to_string());
        manager.create_mapping(&client1_id, None, "connector-2".to_string());

        let clients_info = manager.list_clients_info();
        assert_eq!(clients_info.len(), 2);

        // Find client1 info
        let client1_info = clients_info
            .iter()
            .find(|c| c.client_id == client1_id)
            .expect("Client 1 should be in list");
        assert_eq!(client1_info.active_sessions_count, 2);

        // Find client2 info
        let client2_info = clients_info
            .iter()
            .find(|c| c.client_id == client2_id)
            .expect("Client 2 should be in list");
        assert_eq!(client2_info.active_sessions_count, 0);
    }

    #[test]
    fn test_session_manager_disconnect_client() {
        let manager = SessionManager::new();

        let client_id = manager.register_client(None);
        manager.create_mapping(&client_id, None, "connector-1".to_string());
        manager.create_mapping(&client_id, None, "connector-2".to_string());

        assert_eq!(manager.client_count(), 1);
        assert_eq!(manager.mapping_count(), 2);

        // Disconnect the client
        let result = manager.disconnect_client(&client_id);
        assert!(result.is_ok());

        // Verify client and sessions are removed
        assert_eq!(manager.client_count(), 0);
        assert_eq!(manager.mapping_count(), 0);
    }

    #[test]
    fn test_session_manager_disconnect_nonexistent_client() {
        let manager = SessionManager::new();

        let result = manager.disconnect_client("nonexistent-client");

        assert!(result.is_err());
        match result {
            Err(AcpServerError::ClientNotFound(id)) => {
                assert_eq!(id, "nonexistent-client");
            }
            _ => panic!("Expected ClientNotFound error"),
        }
    }

    #[test]
    fn test_session_manager_disconnect_client_twice() {
        let manager = SessionManager::new();

        let client_id = manager.register_client(None);

        // First disconnect should succeed
        assert!(manager.disconnect_client(&client_id).is_ok());

        // Second disconnect should fail
        assert!(manager.disconnect_client(&client_id).is_err());
    }

    #[test]
    fn test_update_mapping_connector() {
        let manager = SessionManager::new();

        // Create initial mapping
        let client_id = manager.register_client(None);
        let mapping = manager.create_mapping(
            &client_id,
            Some("client-session-1".to_string()),
            "gateway-1".to_string(),
        );

        // Store the original internal session ID
        let original_internal_id = mapping.internal_session_id.clone();

        // Update to new connector
        let old = manager.update_mapping_connector(
            "client-session-1",
            "opencode-1".to_string(),
            Some("OpenCode".to_string()),
            "opencode-internal-1".to_string(),
        );

        assert!(old.is_some());
        let old = old.unwrap();
        assert_eq!(old.connector_id, "gateway-1");
        assert_eq!(old.internal_session_id, original_internal_id);

        // Verify new mapping
        let updated = manager.get_mapping("client-session-1").unwrap();
        assert_eq!(updated.connector_id, "opencode-1");
        assert_eq!(updated.internal_session_id, "opencode-internal-1");
        assert_eq!(updated.client_session_id, "client-session-1"); // Should remain unchanged
    }

    #[test]
    fn test_update_mapping_connector_nonexistent() {
        let manager = SessionManager::new();

        // Try to update a nonexistent mapping
        let result = manager.update_mapping_connector(
            "nonexistent",
            "new-connector".to_string(),
            None,
            "new-session".to_string(),
        );

        assert!(result.is_none());
    }

    #[test]
    fn test_get_mapping_by_gateway_session() {
        let manager = SessionManager::new();

        let client_id = manager.register_client(None);
        let mapping = manager.create_mapping(
            &client_id,
            Some("client-session-1".to_string()),
            "gateway-1".to_string(),
        );

        let gateway_session_id = mapping.internal_session_id.clone();

        let found = manager.get_mapping_by_gateway_session(&gateway_session_id);
        assert!(found.is_some());
        let found = found.unwrap();
        assert_eq!(found.client_session_id, "client-session-1");
        assert_eq!(found.internal_session_id, gateway_session_id);

        let not_found = manager.get_mapping_by_gateway_session("nonexistent");
        assert!(not_found.is_none());
    }

    #[test]
    fn test_get_mapping_by_gateway_session_after_transfer() {
        let manager = SessionManager::new();

        let client_id = manager.register_client(None);
        let mapping = manager.create_mapping(
            &client_id,
            Some("client-session-1".to_string()),
            "gateway-1".to_string(),
        );

        let gateway_session_id = mapping.internal_session_id.clone();

        // Transfer the session to opencode
        manager.update_mapping_connector(
            "client-session-1",
            "opencode-1".to_string(),
            Some("OpenCode".to_string()),
            "opencode-internal-1".to_string(),
        );

        // Should no longer find by old gateway session ID since internal_session_id changed
        let not_found = manager.get_mapping_by_gateway_session(&gateway_session_id);
        assert!(not_found.is_none());

        // Should find by new internal session ID
        let found = manager.get_mapping_by_gateway_session("opencode-internal-1");
        assert!(found.is_some());
        assert_eq!(found.unwrap().client_session_id, "client-session-1");
    }
}
