//! Server builder for fluent server construction
//!
//! Provides ergonomic API for building MCP servers with resilience patterns

use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;

use crate::protocol::capabilities::ServerCapabilities;
use crate::registry::resources::ResourceRetryConfig;
use crate::registry::tools::ToolBreakerConfig;
use crate::server::core::Server;
use crate::server::middleware::{MiddlewareFn, RateLimiter, RateLimiterConfig};
use crate::server::multiplexer::ClientRequester;
use crate::server::session_store::SessionStore;
use crate::server::visibility::Environment;

type InitializedHook = Arc<
    dyn Fn(String, Option<ClientRequester>) -> Pin<Box<dyn Future<Output = ()> + Send>>
        + Send
        + Sync,
>;

/// Fluent builder for Server
pub struct ServerBuilder {
    name: String,
    version: String,
    capabilities: Option<ServerCapabilities>,
    middleware: Vec<MiddlewareFn>,
    /// Server instructions for LLMs
    instructions: Option<String>,
    /// Circuit breaker configuration for tools
    breaker_config: Option<ToolBreakerConfig>,
    /// Retry configuration for resources
    retry_config: Option<ResourceRetryConfig>,
    /// Rate limiter configuration
    rate_limiter: Option<Arc<RateLimiter>>,
    /// Optional environment for visibility checks
    environment: Option<Arc<dyn Environment>>,
    /// Hook called after client sends notifications/initialized
    initialized_hook: Option<InitializedHook>,
    /// Optional session store for persistence
    session_store: Option<Arc<dyn SessionStore>>,
}

impl ServerBuilder {
    /// Create new server builder
    pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            version: version.into(),
            capabilities: None,
            middleware: Vec::new(),
            instructions: None,
            breaker_config: None,
            retry_config: None,
            rate_limiter: None,
            environment: None,
            initialized_hook: None,
            session_store: None,
        }
    }

    /// Set server capabilities
    pub fn with_capabilities(mut self, capabilities: ServerCapabilities) -> Self {
        self.capabilities = Some(capabilities);
        self
    }

    /// Set server instructions for LLMs
    ///
    /// Instructions help LLMs understand how to use the server's tools, resources,
    /// and prompts. They are sent during initialization and can be thought of as
    /// hints added to the system prompt.
    ///
    /// # Example
    ///
    /// ```rust
    /// use mcp_host::prelude::*;
    ///
    /// let server = server("my-server", "1.0.0")
    ///     .with_instructions("Use the data tools for queries. Verify IDs before updates.")
    ///     .build();
    /// ```
    pub fn with_instructions(mut self, instructions: impl Into<String>) -> Self {
        self.instructions = Some(instructions.into());
        self
    }

    /// Enable tools capability
    pub fn with_tools(mut self, list_changed: bool) -> Self {
        let mut caps = self.capabilities.take().unwrap_or_default();
        caps.tools = Some(crate::protocol::capabilities::ToolsCapability {
            list_changed: Some(list_changed),
        });
        self.capabilities = Some(caps);
        self
    }

    /// Enable resources capability
    pub fn with_resources(mut self, list_changed: bool, subscribe: bool) -> Self {
        let mut caps = self.capabilities.take().unwrap_or_default();
        caps.resources = Some(crate::protocol::capabilities::ResourcesCapability {
            list_changed: Some(list_changed),
            subscribe: Some(subscribe),
            list_templates: None,
        });
        self.capabilities = Some(caps);
        self
    }

    /// Enable resource templates capability
    pub fn with_resource_templates(mut self) -> Self {
        let mut caps = self.capabilities.take().unwrap_or_default();
        if let Some(ref mut res) = caps.resources {
            res.list_templates = Some(true);
        } else {
            caps.resources = Some(crate::protocol::capabilities::ResourcesCapability {
                list_changed: None,
                subscribe: None,
                list_templates: Some(true),
            });
        }
        self.capabilities = Some(caps);
        self
    }

    /// Enable prompts capability
    pub fn with_prompts(mut self, list_changed: bool) -> Self {
        let mut caps = self.capabilities.take().unwrap_or_default();
        caps.prompts = Some(crate::protocol::capabilities::PromptsCapability {
            list_changed: Some(list_changed),
        });
        self.capabilities = Some(caps);
        self
    }

    /// Enable logging capability
    pub fn with_logging(mut self) -> Self {
        let mut caps = self.capabilities.take().unwrap_or_default();
        caps.logging = Some(crate::protocol::capabilities::LoggingCapability {});
        self.capabilities = Some(caps);
        self
    }

    /// Enable completion capability for argument value suggestions
    ///
    /// When enabled, clients can request autocomplete suggestions for
    /// prompt arguments and resource URIs via `completion/complete`.
    ///
    /// # Example
    ///
    /// ```rust
    /// use mcp_host::prelude::*;
    ///
    /// let server = server("my-server", "1.0.0")
    ///     .with_completion()
    ///     .build();
    /// ```
    pub fn with_completion(mut self) -> Self {
        let mut caps = self.capabilities.take().unwrap_or_default();
        caps.completion = Some(crate::protocol::capabilities::CompletionCapability {});
        self.capabilities = Some(caps);
        self
    }

    /// Enable tasks capability
    pub fn with_tasks(mut self, list: bool, cancel: bool) -> Self {
        let mut caps = self.capabilities.take().unwrap_or_default();
        caps.tasks = Some(crate::protocol::capabilities::TasksCapability {
            list: if list {
                Some(crate::protocol::capabilities::EmptyObject {})
            } else {
                None
            },
            cancel: if cancel {
                Some(crate::protocol::capabilities::EmptyObject {})
            } else {
                None
            },
            requests: Some(crate::protocol::capabilities::TasksRequestsCapability {
                tools: Some(crate::protocol::capabilities::TasksToolsCapability {
                    call: Some(crate::protocol::capabilities::EmptyObject {}),
                }),
                ..Default::default()
            }),
        });
        self.capabilities = Some(caps);
        self
    }

    /// Add middleware to the server
    pub fn add_middleware(mut self, middleware: MiddlewareFn) -> Self {
        self.middleware.push(middleware);
        self
    }

    /// Add logging middleware
    pub fn with_logging_middleware(self) -> Self {
        self.add_middleware(crate::server::middleware::logging_middleware())
    }

    /// Add validation middleware
    pub fn with_validation_middleware(self) -> Self {
        self.add_middleware(crate::server::middleware::validation_middleware())
    }

    /// Configure circuit breakers for tools
    ///
    /// Circuit breakers protect against cascading failures by temporarily
    /// disabling tools that are failing repeatedly.
    ///
    /// # Example
    ///
    /// ```rust
    /// use mcp_host::prelude::*;
    ///
    /// let server = server("my-server", "1.0.0")
    ///     .with_circuit_breaker(ToolBreakerConfig {
    ///         failure_threshold: 3,
    ///         failure_window_secs: 30.0,
    ///         half_open_timeout_secs: 15.0,
    ///         success_threshold: 1,
    ///     })
    ///     .build();
    /// ```
    pub fn with_circuit_breaker(mut self, config: ToolBreakerConfig) -> Self {
        self.breaker_config = Some(config);
        self
    }

    /// Configure retry behavior for resources
    ///
    /// Resources will retry failed reads using exponential backoff with jitter.
    ///
    /// # Example
    ///
    /// ```rust
    /// use mcp_host::prelude::*;
    ///
    /// let server = server("my-server", "1.0.0")
    ///     .with_retry(ResourceRetryConfig {
    ///         max_attempts: 5,
    ///         base_delay_ms: 200,
    ///         multiplier: 2.0,
    ///         max_delay_ms: 5000,
    ///         jitter_factor: 1.0,
    ///     })
    ///     .build();
    /// ```
    pub fn with_retry(mut self, config: ResourceRetryConfig) -> Self {
        self.retry_config = Some(config);
        self
    }

    /// Add rate limiting middleware
    ///
    /// Rate limits requests using the GCRA (Generic Cell Rate Algorithm).
    ///
    /// # Arguments
    ///
    /// * `requests_per_second` - Maximum requests allowed per second
    /// * `burst_capacity` - Number of requests that can burst instantly
    ///
    /// # Example
    ///
    /// ```rust
    /// use mcp_host::prelude::*;
    ///
    /// let server = server("my-server", "1.0.0")
    ///     .with_rate_limit(100.0, 20)  // 100 req/s with burst of 20
    ///     .build();
    /// ```
    pub fn with_rate_limit(mut self, requests_per_second: f64, burst_capacity: usize) -> Self {
        let limiter = Arc::new(RateLimiter::new(RateLimiterConfig::new(
            requests_per_second,
            burst_capacity,
        )));
        self.rate_limiter = Some(limiter);
        self
    }

    /// Add rate limiting with custom configuration
    pub fn with_rate_limiter(mut self, limiter: Arc<RateLimiter>) -> Self {
        self.rate_limiter = Some(limiter);
        self
    }

    /// Set environment for visibility checks
    pub fn with_environment<E>(mut self, environment: E) -> Self
    where
        E: Environment + 'static,
    {
        self.environment = Some(Arc::new(environment));
        self
    }

    /// Register a hook called after the client sends `notifications/initialized`.
    ///
    /// The client is fully ready at this point — use the `ClientRequester` to make
    /// server→client requests such as `roots/list` or `sampling/createMessage`.
    ///
    /// # Example
    /// ```rust,ignore
    /// server_builder.on_initialized(|session_id, requester| async move {
    ///     if let Some(req) = requester {
    ///         if let Ok(roots) = req.request_roots(None).await {
    ///             tracing::info!(session = %session_id, roots = ?roots, "Client roots");
    ///         }
    ///     }
    /// });
    /// ```
    pub fn on_initialized<F, Fut>(mut self, hook: F) -> Self
    where
        F: Fn(String, Option<ClientRequester>) -> Fut + Send + Sync + 'static,
        Fut: Future<Output = ()> + Send + 'static,
    {
        self.initialized_hook = Some(Arc::new(move |session_id, requester| {
            Box::pin(hook(session_id, requester))
        }));
        self
    }

    /// Set a session store for persistence across restarts.
    pub fn with_session_store(mut self, store: Arc<dyn SessionStore>) -> Self {
        self.session_store = Some(store);
        self
    }

    /// Build the server
    pub fn build(self) -> Server {
        let mut server = Server::new(self.name, self.version);

        // Set capabilities if provided
        if let Some(capabilities) = self.capabilities {
            server.set_capabilities(capabilities);
        }

        // Set instructions if provided
        if let Some(instructions) = self.instructions {
            server.set_instructions(Some(instructions));
        }

        // Configure circuit breakers for tools
        if let Some(breaker_config) = self.breaker_config {
            server.tool_registry().set_breaker_config(breaker_config);
        }

        // Configure retry for resources
        if let Some(retry_config) = self.retry_config {
            server.resource_manager().set_retry_config(retry_config);
        }

        // Add rate limiter middleware if configured
        if let Some(limiter) = self.rate_limiter {
            server.add_middleware(crate::server::middleware::rate_limiter_middleware(limiter));
        }

        // Add user-provided middleware
        for middleware in self.middleware {
            server.add_middleware(middleware);
        }

        if let Some(environment) = self.environment {
            server.set_environment(environment);
        }

        if let Some(hook) = self.initialized_hook {
            server.set_initialized_hook(hook);
        }

        if let Some(store) = self.session_store {
            server.set_session_store(store);
        }

        server
    }
}

/// Convenience function to create a server builder
pub fn server(name: impl Into<String>, version: impl Into<String>) -> ServerBuilder {
    ServerBuilder::new(name, version)
}

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

    #[test]
    fn test_builder_basic() {
        let server = ServerBuilder::new("test-server", "1.0.0").build();

        assert_eq!(server.name(), "test-server");
        assert_eq!(server.version(), "1.0.0");
    }

    #[test]
    fn test_builder_with_tools() {
        let server = ServerBuilder::new("test-server", "1.0.0")
            .with_tools(true)
            .build();

        let caps = server.capabilities();
        assert!(caps.tools.is_some());
        assert_eq!(caps.tools.as_ref().unwrap().list_changed, Some(true));
    }

    #[test]
    fn test_builder_with_resources() {
        let server = ServerBuilder::new("test-server", "1.0.0")
            .with_resources(true, false)
            .build();

        let caps = server.capabilities();
        assert!(caps.resources.is_some());
        let res = caps.resources.as_ref().unwrap();
        assert_eq!(res.list_changed, Some(true));
        assert_eq!(res.subscribe, Some(false));
        assert_eq!(res.list_templates, None);
    }

    #[test]
    fn test_builder_with_resource_templates() {
        let server = ServerBuilder::new("test-server", "1.0.0")
            .with_resources(true, false)
            .with_resource_templates()
            .build();

        let caps = server.capabilities();
        assert!(caps.resources.is_some());
        let res = caps.resources.as_ref().unwrap();
        assert_eq!(res.list_changed, Some(true));
        assert_eq!(res.subscribe, Some(false));
        assert_eq!(res.list_templates, Some(true));
    }

    #[test]
    fn test_builder_with_prompts() {
        let server = ServerBuilder::new("test-server", "1.0.0")
            .with_prompts(true)
            .build();

        let caps = server.capabilities();
        assert!(caps.prompts.is_some());
        assert_eq!(caps.prompts.as_ref().unwrap().list_changed, Some(true));
    }

    #[test]
    fn test_builder_with_logging() {
        let server = ServerBuilder::new("test-server", "1.0.0")
            .with_logging()
            .build();

        let caps = server.capabilities();
        assert!(caps.logging.is_some());
    }

    #[test]
    fn test_builder_with_all_capabilities() {
        let server = ServerBuilder::new("test-server", "1.0.0")
            .with_tools(true)
            .with_resources(true, true)
            .with_prompts(true)
            .with_logging()
            .build();

        let caps = server.capabilities();
        assert!(caps.tools.is_some());
        assert!(caps.resources.is_some());
        assert!(caps.prompts.is_some());
        assert!(caps.logging.is_some());
    }

    #[test]
    fn test_builder_with_middleware() {
        let server = ServerBuilder::new("test-server", "1.0.0")
            .with_logging_middleware()
            .with_validation_middleware()
            .build();

        assert_eq!(server.name(), "test-server");
    }

    #[test]
    fn test_server_convenience_function() {
        let server = server("test-server", "1.0.0").with_tools(true).build();

        assert_eq!(server.name(), "test-server");
    }

    #[test]
    fn test_builder_with_instructions() {
        let server = ServerBuilder::new("test-server", "1.0.0")
            .with_instructions("Use data tools for queries. Verify IDs before updates.")
            .build();

        assert_eq!(server.name(), "test-server");
        assert_eq!(
            server.instructions(),
            Some("Use data tools for queries. Verify IDs before updates.".to_string())
        );
    }

    #[test]
    fn test_builder_without_instructions() {
        let server = ServerBuilder::new("test-server", "1.0.0").build();

        assert!(server.instructions().is_none());
    }
}
