//! ACP Acceptor implementation
//!
//! This module provides the `AcpAcceptor` struct which serves as the entry point
//! for incoming ACP connections. It tracks pending sessions from the ACP Server
//! and routes them to appropriate connectors.
//!
//! # Architecture
//!
//! The AcpAcceptor is a lightweight coordinator that:
//! - Maintains a list of pending incoming sessions
//! - Routes sessions to target connectors
//! - Optionally auto-routes new sessions to a default connector
//!
//! It does NOT:
//! - Process messages directly (delegates to connectors)
//! - Maintain direct connection to ACP clients (ACP Server does this)
//! - Store session history (connectors and archivist handle this)
//!
//! # Usage
//!
//! ```no_run
//! use dirigent_core::connectors::acceptor::AcpAcceptor;
//!
//! // Create acceptor with auto-routing disabled
//! let acceptor = AcpAcceptor::new("acp-acceptor-1".to_string(), "ACP Incoming".to_string());
//!
//! // Or with auto-routing to a default connector
//! let acceptor = AcpAcceptor::builder("acp-acceptor-1".to_string(), "ACP Incoming".to_string())
//!     .with_default_connector("my-connector-id".to_string())
//!     .with_auto_routing(true)
//!     .build();
//! ```

use async_trait::async_trait;
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
use tracing::{debug, info, warn};

use super::{Acceptor, AcceptorError, AcceptorId, IncomingSession, SessionRouting};

/// Configuration for AcpAcceptor
#[derive(Clone, Debug)]
pub struct AcpAcceptorConfig {
    /// Unique identifier for this acceptor
    pub id: AcceptorId,

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

    /// Default connector ID for auto-routing
    pub default_connector_id: Option<String>,

    /// Whether to automatically route new sessions
    pub auto_routing: bool,
}

impl AcpAcceptorConfig {
    /// Create a new config with defaults
    pub fn new(id: AcceptorId, title: String) -> Self {
        Self {
            id,
            title,
            default_connector_id: None,
            auto_routing: false,
        }
    }
}

/// Builder for AcpAcceptor
pub struct AcpAcceptorBuilder {
    config: AcpAcceptorConfig,
}

impl AcpAcceptorBuilder {
    /// Create a new builder
    pub fn new(id: AcceptorId, title: String) -> Self {
        Self {
            config: AcpAcceptorConfig::new(id, title),
        }
    }

    /// Set the default connector for auto-routing
    pub fn with_default_connector(mut self, connector_id: String) -> Self {
        self.config.default_connector_id = Some(connector_id);
        self
    }

    /// Enable or disable auto-routing
    pub fn with_auto_routing(mut self, enabled: bool) -> Self {
        self.config.auto_routing = enabled;
        self
    }

    /// Build the AcpAcceptor
    pub fn build(self) -> AcpAcceptor {
        AcpAcceptor::from_config(self.config)
    }
}

/// ACP Acceptor for handling incoming ACP connections
///
/// This struct manages incoming sessions from the ACP Server and routes them
/// to appropriate connectors for processing.
pub struct AcpAcceptor {
    /// Unique identifier for this acceptor
    id: AcceptorId,

    /// Human-readable title
    title: String,

    /// Pending sessions awaiting routing
    ///
    /// Key: session_id, Value: IncomingSession
    pending_sessions: Arc<RwLock<HashMap<String, IncomingSession>>>,

    /// Session routings (for sessions that have been routed)
    ///
    /// Key: session_id, Value: SessionRouting
    session_routings: Arc<RwLock<HashMap<String, SessionRouting>>>,

    /// Default connector ID for auto-routing and accept operations
    default_connector_id: Arc<RwLock<Option<String>>>,

    /// Whether auto-routing is enabled
    auto_routing: Arc<RwLock<bool>>,
}

impl AcpAcceptor {
    /// Create a new AcpAcceptor with basic settings
    pub fn new(id: AcceptorId, title: String) -> Self {
        Self {
            id,
            title,
            pending_sessions: Arc::new(RwLock::new(HashMap::new())),
            session_routings: Arc::new(RwLock::new(HashMap::new())),
            default_connector_id: Arc::new(RwLock::new(None)),
            auto_routing: Arc::new(RwLock::new(false)),
        }
    }

    /// Create a builder for more configuration options
    pub fn builder(id: AcceptorId, title: String) -> AcpAcceptorBuilder {
        AcpAcceptorBuilder::new(id, title)
    }

    /// Create from config
    fn from_config(config: AcpAcceptorConfig) -> Self {
        Self {
            id: config.id,
            title: config.title,
            pending_sessions: Arc::new(RwLock::new(HashMap::new())),
            session_routings: Arc::new(RwLock::new(HashMap::new())),
            default_connector_id: Arc::new(RwLock::new(config.default_connector_id)),
            auto_routing: Arc::new(RwLock::new(config.auto_routing)),
        }
    }

    /// Add an incoming session to the pending list
    ///
    /// This is called by the ACP Server when a new session is created.
    /// If auto-routing is enabled and a default connector is configured,
    /// the session will be automatically routed.
    ///
    /// # Returns
    ///
    /// - `Ok(Some(SessionRouting))` if auto-routed
    /// - `Ok(None)` if added to pending list
    pub async fn add_incoming_session(
        &self,
        session: IncomingSession,
    ) -> Result<Option<SessionRouting>, AcceptorError> {
        let session_id = session.session_id.clone();

        // Check if auto-routing should happen
        let auto_routing = *self.auto_routing.read().await;
        let default_connector = self.default_connector_id.read().await.clone();

        if auto_routing {
            if let Some(connector_id) = default_connector {
                info!(
                    session_id = %session_id,
                    connector_id = %connector_id,
                    "Auto-routing incoming session"
                );

                // Create routing directly (skip pending)
                let routing = SessionRouting::new(session_id.clone(), connector_id);
                let mut routings = self.session_routings.write().await;
                routings.insert(session_id, routing.clone());

                return Ok(Some(routing));
            } else {
                warn!(
                    session_id = %session_id,
                    "Auto-routing enabled but no default connector configured"
                );
            }
        }

        // Add to pending sessions
        info!(
            session_id = %session_id,
            client_id = %session.client_id,
            "Added incoming session to pending list"
        );

        let mut pending = self.pending_sessions.write().await;
        pending.insert(session_id, session);

        Ok(None)
    }

    /// Remove a session from tracking entirely
    ///
    /// This is called when a session is closed or the client disconnects.
    pub async fn remove_session(&self, session_id: &str) {
        let mut pending = self.pending_sessions.write().await;
        pending.remove(session_id);

        let mut routings = self.session_routings.write().await;
        routings.remove(session_id);

        debug!(session_id = %session_id, "Removed session from acceptor tracking");
    }

    /// Set the default connector ID
    pub async fn set_default_connector(&self, connector_id: Option<String>) {
        let mut default = self.default_connector_id.write().await;
        *default = connector_id;
    }

    /// Enable or disable auto-routing
    pub async fn set_auto_routing(&self, enabled: bool) {
        let mut auto = self.auto_routing.write().await;
        *auto = enabled;
    }

    /// Get the count of pending sessions
    pub async fn pending_count(&self) -> usize {
        self.pending_sessions.read().await.len()
    }

    /// Get the count of routed sessions
    pub async fn routed_count(&self) -> usize {
        self.session_routings.read().await.len()
    }
}

#[async_trait]
impl Acceptor for AcpAcceptor {
    fn id(&self) -> &AcceptorId {
        &self.id
    }

    fn title(&self) -> &str {
        &self.title
    }

    async fn incoming_sessions(&self) -> Vec<IncomingSession> {
        let pending = self.pending_sessions.read().await;
        let mut sessions: Vec<IncomingSession> = pending.values().cloned().collect();

        // Sort by created_at (newest first)
        sessions.sort_by(|a, b| b.created_at.cmp(&a.created_at));

        sessions
    }

    async fn accept_session(&self, session_id: &str) -> Result<SessionRouting, AcceptorError> {
        // Get the default connector
        let default_connector = self.default_connector_id.read().await.clone();

        match default_connector {
            Some(connector_id) => self.route_to_connector(session_id, &connector_id).await,
            None => Err(AcceptorError::Internal(
                "No default connector configured for accept operation".to_string(),
            )),
        }
    }

    async fn reject_session(&self, session_id: &str) -> Result<(), AcceptorError> {
        let mut pending = self.pending_sessions.write().await;

        if pending.remove(session_id).is_some() {
            info!(session_id = %session_id, "Rejected incoming session");
            Ok(())
        } else {
            // Check if it was already routed
            let routings = self.session_routings.read().await;
            if routings.contains_key(session_id) {
                Err(AcceptorError::AlreadyRouted(session_id.to_string()))
            } else {
                Err(AcceptorError::SessionNotFound(session_id.to_string()))
            }
        }
    }

    async fn route_to_connector(
        &self,
        session_id: &str,
        connector_id: &str,
    ) -> Result<SessionRouting, AcceptorError> {
        // Remove from pending
        let mut pending = self.pending_sessions.write().await;

        if pending.remove(session_id).is_none() {
            // Check if already routed
            let routings = self.session_routings.read().await;
            if routings.contains_key(session_id) {
                return Err(AcceptorError::AlreadyRouted(session_id.to_string()));
            }
            return Err(AcceptorError::SessionNotFound(session_id.to_string()));
        }

        // Create and store routing
        let routing = SessionRouting::new(session_id.to_string(), connector_id.to_string());

        let mut routings = self.session_routings.write().await;
        routings.insert(session_id.to_string(), routing.clone());

        info!(
            session_id = %session_id,
            connector_id = %connector_id,
            "Routed session to connector"
        );

        Ok(routing)
    }

    async fn get_session_routing(&self, session_id: &str) -> Option<SessionRouting> {
        let routings = self.session_routings.read().await;
        routings.get(session_id).cloned()
    }

    fn auto_routing_enabled(&self) -> bool {
        // Use try_read for non-blocking access in sync context
        self.auto_routing
            .try_read()
            .map(|guard| *guard)
            .unwrap_or(false)
    }

    fn default_connector_id(&self) -> Option<&str> {
        // This is tricky because we can't return a reference to data behind RwLock
        // In practice, callers should use the async methods or get_session_routing
        // For now, return None - the async version should be preferred
        None
    }
}

/// Extension methods for getting routing info asynchronously
impl AcpAcceptor {
    /// Get the default connector ID asynchronously
    pub async fn default_connector_id_async(&self) -> Option<String> {
        self.default_connector_id.read().await.clone()
    }

    /// Check if auto-routing is enabled asynchronously
    pub async fn auto_routing_enabled_async(&self) -> bool {
        *self.auto_routing.read().await
    }

    /// Transfer a session from one connector to another
    ///
    /// This updates the routing to point to a new connector.
    /// The session must already be routed.
    ///
    /// # Arguments
    ///
    /// * `session_id` - The session to transfer
    /// * `to_connector_id` - The new target connector
    ///
    /// # Returns
    ///
    /// The new routing information
    pub async fn transfer_session(
        &self,
        session_id: &str,
        to_connector_id: &str,
    ) -> Result<SessionRouting, AcceptorError> {
        let mut routings = self.session_routings.write().await;

        // Verify session exists and is routed
        if !routings.contains_key(session_id) {
            // Check if it's pending
            let pending = self.pending_sessions.read().await;
            if pending.contains_key(session_id) {
                return Err(AcceptorError::Internal(
                    "Session is pending, not routed. Use route_to_connector instead.".to_string(),
                ));
            }
            return Err(AcceptorError::SessionNotFound(session_id.to_string()));
        }

        // Update the routing
        let routing = SessionRouting::new(session_id.to_string(), to_connector_id.to_string());
        routings.insert(session_id.to_string(), routing.clone());

        info!(
            session_id = %session_id,
            to_connector_id = %to_connector_id,
            "Transferred session to new connector"
        );

        Ok(routing)
    }

    /// Get all session routings
    pub async fn all_routings(&self) -> HashMap<String, SessionRouting> {
        self.session_routings.read().await.clone()
    }
}

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

    #[tokio::test]
    async fn test_acp_acceptor_creation() {
        let acceptor = AcpAcceptor::new("acceptor-1".to_string(), "Test Acceptor".to_string());

        assert_eq!(acceptor.id(), "acceptor-1");
        assert_eq!(acceptor.title(), "Test Acceptor");
        assert_eq!(acceptor.pending_count().await, 0);
        assert_eq!(acceptor.routed_count().await, 0);
    }

    #[tokio::test]
    async fn test_add_incoming_session() {
        let acceptor = AcpAcceptor::new("acceptor-1".to_string(), "Test Acceptor".to_string());

        let session =
            IncomingSession::new("session-1".to_string(), "client-1".to_string());

        let result = acceptor.add_incoming_session(session).await;
        assert!(result.is_ok());
        assert!(result.unwrap().is_none()); // No auto-routing

        assert_eq!(acceptor.pending_count().await, 1);

        let pending = acceptor.incoming_sessions().await;
        assert_eq!(pending.len(), 1);
        assert_eq!(pending[0].session_id, "session-1");
    }

    #[tokio::test]
    async fn test_auto_routing() {
        let acceptor = AcpAcceptor::builder("acceptor-1".to_string(), "Test Acceptor".to_string())
            .with_default_connector("default-connector".to_string())
            .with_auto_routing(true)
            .build();

        let session =
            IncomingSession::new("session-1".to_string(), "client-1".to_string());

        let result = acceptor.add_incoming_session(session).await;
        assert!(result.is_ok());

        let routing = result.unwrap();
        assert!(routing.is_some());
        let routing = routing.unwrap();
        assert_eq!(routing.session_id, "session-1");
        assert_eq!(routing.connector_id, "default-connector");

        // Session should not be in pending
        assert_eq!(acceptor.pending_count().await, 0);
        // Session should be in routings
        assert_eq!(acceptor.routed_count().await, 1);
    }

    #[tokio::test]
    async fn test_route_to_connector() {
        let acceptor = AcpAcceptor::new("acceptor-1".to_string(), "Test Acceptor".to_string());

        let session =
            IncomingSession::new("session-1".to_string(), "client-1".to_string());
        acceptor.add_incoming_session(session).await.unwrap();

        let routing = acceptor
            .route_to_connector("session-1", "my-connector")
            .await
            .unwrap();

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

        // Session should be removed from pending
        assert_eq!(acceptor.pending_count().await, 0);
        // Session should be in routings
        assert_eq!(acceptor.routed_count().await, 1);
    }

    #[tokio::test]
    async fn test_reject_session() {
        let acceptor = AcpAcceptor::new("acceptor-1".to_string(), "Test Acceptor".to_string());

        let session =
            IncomingSession::new("session-1".to_string(), "client-1".to_string());
        acceptor.add_incoming_session(session).await.unwrap();

        assert_eq!(acceptor.pending_count().await, 1);

        acceptor.reject_session("session-1").await.unwrap();

        assert_eq!(acceptor.pending_count().await, 0);
    }

    #[tokio::test]
    async fn test_transfer_session() {
        let acceptor = AcpAcceptor::new("acceptor-1".to_string(), "Test Acceptor".to_string());

        // Add and route a session
        let session =
            IncomingSession::new("session-1".to_string(), "client-1".to_string());
        acceptor.add_incoming_session(session).await.unwrap();
        acceptor.route_to_connector("session-1", "connector-1")
            .await
            .unwrap();

        // Transfer to new connector
        let new_routing = acceptor
            .transfer_session("session-1", "connector-2")
            .await
            .unwrap();

        assert_eq!(new_routing.connector_id, "connector-2");

        // Verify the routing was updated
        let routing = acceptor.get_session_routing("session-1").await.unwrap();
        assert_eq!(routing.connector_id, "connector-2");
    }

    #[tokio::test]
    async fn test_error_cases() {
        let acceptor = AcpAcceptor::new("acceptor-1".to_string(), "Test Acceptor".to_string());

        // Route non-existent session
        let result = acceptor.route_to_connector("non-existent", "connector").await;
        assert!(matches!(result, Err(AcceptorError::SessionNotFound(_))));

        // Reject non-existent session
        let result = acceptor.reject_session("non-existent").await;
        assert!(matches!(result, Err(AcceptorError::SessionNotFound(_))));

        // Add session, route it, then try to route again
        let session =
            IncomingSession::new("session-1".to_string(), "client-1".to_string());
        acceptor.add_incoming_session(session).await.unwrap();
        acceptor.route_to_connector("session-1", "connector-1")
            .await
            .unwrap();

        let result = acceptor.route_to_connector("session-1", "connector-2").await;
        assert!(matches!(result, Err(AcceptorError::AlreadyRouted(_))));

        // Reject already routed session
        let result = acceptor.reject_session("session-1").await;
        assert!(matches!(result, Err(AcceptorError::AlreadyRouted(_))));
    }
}
