//! Acceptor abstraction layer for incoming connections
//!
//! This module provides the `Acceptor` trait that represents an entry point for incoming
//! ACP connections. Unlike Connectors which initiate outbound connections to agents,
//! Acceptors accept inbound connections from external clients.
//!
//! # Architecture
//!
//! An Acceptor:
//! - Accepts incoming connections from ACP clients
//! - Tracks pending sessions awaiting routing decisions
//! - Routes sessions to target Connectors for processing
//! - Does NOT process messages itself (delegates to Connectors)
//!
//! # Session Lifecycle
//!
//! 1. Client connects to ACP Server and creates a session
//! 2. Session appears as "pending" in the Acceptor
//! 3. User (or auto-routing) assigns session to a Connector
//! 4. Once routed, the Connector handles all message processing
//!
//! # Usage
//!
//! ```no_run
//! use dirigent_core::connectors::acceptor::{Acceptor, IncomingSession};
//!
//! async fn example(acceptor: impl Acceptor) {
//!     // Get pending sessions
//!     let pending = acceptor.incoming_sessions().await;
//!
//!     for session in pending {
//!         // Route to a connector
//!         acceptor.route_to_connector(&session.session_id, "my-connector-id").await?;
//!     }
//! }
//! ```

//! Note: This module requires the "server" feature flag.

use async_trait::async_trait;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use thiserror::Error;

pub mod acp_acceptor;

// Re-export AcpAcceptor for convenience
pub use acp_acceptor::AcpAcceptor;

/// Unique identifier for an acceptor instance
pub type AcceptorId = String;

/// Information about an incoming session awaiting routing
///
/// When an ACP client connects and creates a session through the ACP Server,
/// that session is initially "pending" - it exists but hasn't been assigned
/// to a specific Connector for processing.
///
/// This struct contains the metadata needed to display pending sessions
/// in the UI and make routing decisions.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct IncomingSession {
    /// Unique session identifier (from the ACP Server)
    pub session_id: String,

    /// Client identifier (who created this session)
    pub client_id: String,

    /// Optional client metadata (capabilities, name, version, etc.)
    ///
    /// This is typically the client info provided during the ACP `initialize`
    /// handshake. It can include:
    /// - client_name: Name of the client application
    /// - client_version: Version string
    /// - capabilities: Supported features
    #[serde(skip_serializing_if = "Option::is_none")]
    pub client_info: Option<serde_json::Value>,

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

    /// Optional title for display
    #[serde(skip_serializing_if = "Option::is_none")]
    pub title: Option<String>,
}

impl IncomingSession {
    /// Create a new IncomingSession
    pub fn new(session_id: String, client_id: String) -> Self {
        Self {
            session_id,
            client_id,
            client_info: None,
            created_at: Utc::now(),
            title: None,
        }
    }

    /// Create with client info
    pub fn with_client_info(mut self, info: serde_json::Value) -> Self {
        self.client_info = Some(info);
        self
    }

    /// Create with title
    pub fn with_title(mut self, title: String) -> Self {
        self.title = Some(title);
        self
    }
}

/// Result of successfully routing a session
///
/// When a session is routed to a connector, this struct provides
/// information about the mapping that was created.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SessionRouting {
    /// The incoming session ID (from client)
    pub session_id: String,

    /// The connector this session was routed to
    pub connector_id: String,

    /// When the routing was established
    pub routed_at: DateTime<Utc>,
}

impl SessionRouting {
    /// Create a new SessionRouting
    pub fn new(session_id: String, connector_id: String) -> Self {
        Self {
            session_id,
            connector_id,
            routed_at: Utc::now(),
        }
    }
}

/// Error type for acceptor operations
#[derive(Clone, Debug, Error)]
pub enum AcceptorError {
    /// Session not found in pending list
    #[error("Session not found: {0}")]
    SessionNotFound(String),

    /// Connector not found or not available
    #[error("Connector not available: {0}")]
    ConnectorNotAvailable(String),

    /// Session already routed to a connector
    #[error("Session already routed: {0}")]
    AlreadyRouted(String),

    /// Internal error
    #[error("Internal error: {0}")]
    Internal(String),
}

/// Trait for incoming connection handlers (Acceptors)
///
/// An Acceptor represents an entry point for incoming ACP connections.
/// It tracks pending sessions and routes them to appropriate Connectors.
///
/// # Object Safety
///
/// This trait is designed to be object-safe, allowing for `dyn Acceptor` usage.
/// All async methods use `async_trait` for compatibility.
#[async_trait]
pub trait Acceptor: Send + Sync {
    /// Get the unique identifier for this acceptor
    fn id(&self) -> &AcceptorId;

    /// Get the human-readable title for this acceptor
    fn title(&self) -> &str;

    /// Get all pending incoming sessions
    ///
    /// Returns sessions that have been created but not yet routed to a connector.
    /// Once a session is routed, it no longer appears in this list.
    async fn incoming_sessions(&self) -> Vec<IncomingSession>;

    /// Accept a session and route it to a connector
    ///
    /// This is a convenience method that routes the session to the default
    /// connector (if one is configured) or marks it as accepted for manual routing.
    ///
    /// # Arguments
    ///
    /// * `session_id` - The ID of the pending session to accept
    ///
    /// # Returns
    ///
    /// The routing information if successful, or an error if the session
    /// was not found or routing failed.
    async fn accept_session(&self, session_id: &str) -> Result<SessionRouting, AcceptorError>;

    /// Reject a session and remove it from the pending list
    ///
    /// This notifies the client that their session was rejected and
    /// removes it from the pending sessions list.
    ///
    /// # Arguments
    ///
    /// * `session_id` - The ID of the pending session to reject
    async fn reject_session(&self, session_id: &str) -> Result<(), AcceptorError>;

    /// Route a session to a specific connector
    ///
    /// Once routed, the connector will handle all message processing for this
    /// session. The session is removed from the pending list.
    ///
    /// # Arguments
    ///
    /// * `session_id` - The ID of the pending session
    /// * `connector_id` - The ID of the connector to route to
    ///
    /// # Returns
    ///
    /// The routing information if successful, or an error if:
    /// - The session was not found
    /// - The connector was not found or not available
    /// - The session was already routed
    async fn route_to_connector(
        &self,
        session_id: &str,
        connector_id: &str,
    ) -> Result<SessionRouting, AcceptorError>;

    /// Get the current routing for a session (if routed)
    ///
    /// Returns None if the session is still pending, or the routing
    /// information if it has been assigned to a connector.
    async fn get_session_routing(&self, session_id: &str) -> Option<SessionRouting>;

    /// Check if auto-routing is enabled
    ///
    /// When auto-routing is enabled, new incoming sessions are automatically
    /// routed to the default connector without manual intervention.
    fn auto_routing_enabled(&self) -> bool;

    /// Get the default connector ID for auto-routing
    ///
    /// Returns the connector ID that new sessions should be automatically
    /// routed to, if auto-routing is enabled.
    fn default_connector_id(&self) -> Option<&str>;
}

/// Summary information about an acceptor for UI display
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct AcceptorSummary {
    /// Unique acceptor identifier
    pub id: AcceptorId,

    /// Human-readable title for display
    pub title: String,

    /// Number of pending incoming sessions
    pub pending_sessions_count: usize,

    /// Whether auto-routing is enabled
    pub auto_routing_enabled: bool,

    /// Default connector ID (if configured)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub default_connector_id: Option<String>,
}

/// Handle to an acceptor instance
///
/// This provides a cloneable reference to an acceptor that can be shared
/// across async tasks.
#[derive(Clone)]
pub struct AcceptorHandle {
    /// The underlying acceptor implementation
    acceptor: Arc<dyn Acceptor>,
}

impl AcceptorHandle {
    /// Create a new AcceptorHandle wrapping an acceptor implementation
    pub fn new(acceptor: impl Acceptor + 'static) -> Self {
        Self {
            acceptor: Arc::new(acceptor),
        }
    }

    /// Create from an Arc-wrapped acceptor
    pub fn from_arc(acceptor: Arc<dyn Acceptor>) -> Self {
        Self { acceptor }
    }

    /// Get a reference to the underlying acceptor
    pub fn as_ref(&self) -> &dyn Acceptor {
        self.acceptor.as_ref()
    }

    /// Get the acceptor ID
    pub fn id(&self) -> &AcceptorId {
        self.acceptor.id()
    }

    /// Get the acceptor title
    pub fn title(&self) -> &str {
        self.acceptor.title()
    }

    /// Get pending incoming sessions
    pub async fn incoming_sessions(&self) -> Vec<IncomingSession> {
        self.acceptor.incoming_sessions().await
    }

    /// Accept a session
    pub async fn accept_session(&self, session_id: &str) -> Result<SessionRouting, AcceptorError> {
        self.acceptor.accept_session(session_id).await
    }

    /// Reject a session
    pub async fn reject_session(&self, session_id: &str) -> Result<(), AcceptorError> {
        self.acceptor.reject_session(session_id).await
    }

    /// Route a session to a connector
    pub async fn route_to_connector(
        &self,
        session_id: &str,
        connector_id: &str,
    ) -> Result<SessionRouting, AcceptorError> {
        self.acceptor.route_to_connector(session_id, connector_id).await
    }

    /// Get session routing
    pub async fn get_session_routing(&self, session_id: &str) -> Option<SessionRouting> {
        self.acceptor.get_session_routing(session_id).await
    }

    /// Check if auto-routing is enabled
    pub fn auto_routing_enabled(&self) -> bool {
        self.acceptor.auto_routing_enabled()
    }

    /// Get default connector ID
    pub fn default_connector_id(&self) -> Option<&str> {
        self.acceptor.default_connector_id()
    }

    /// Get a summary of this acceptor for UI display
    pub async fn summary(&self) -> AcceptorSummary {
        let pending = self.incoming_sessions().await;
        AcceptorSummary {
            id: self.id().clone(),
            title: self.title().to_string(),
            pending_sessions_count: pending.len(),
            auto_routing_enabled: self.auto_routing_enabled(),
            default_connector_id: self.default_connector_id().map(String::from),
        }
    }
}

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

    #[test]
    fn test_incoming_session_creation() {
        let session = IncomingSession::new("session-1".to_string(), "client-1".to_string());

        assert_eq!(session.session_id, "session-1");
        assert_eq!(session.client_id, "client-1");
        assert!(session.client_info.is_none());
        assert!(session.title.is_none());
    }

    #[test]
    fn test_incoming_session_with_info() {
        let session = IncomingSession::new("session-1".to_string(), "client-1".to_string())
            .with_client_info(serde_json::json!({
                "client_name": "test-client",
                "version": "1.0.0"
            }))
            .with_title("Test Session".to_string());

        assert!(session.client_info.is_some());
        assert_eq!(session.title, Some("Test Session".to_string()));
    }

    #[test]
    fn test_session_routing() {
        let routing = SessionRouting::new("session-1".to_string(), "connector-1".to_string());

        assert_eq!(routing.session_id, "session-1");
        assert_eq!(routing.connector_id, "connector-1");
    }

    #[test]
    fn test_acceptor_error_display() {
        let error = AcceptorError::SessionNotFound("session-1".to_string());
        assert!(error.to_string().contains("session-1"));

        let error = AcceptorError::ConnectorNotAvailable("connector-1".to_string());
        assert!(error.to_string().contains("connector-1"));
    }
}
