//! Client configuration.
//!
//! This module defines configuration types used to construct and initialize
//! the Tenbin client, including transport endpoints and runtime parameters.
//!
//! Configuration is validated eagerly during client construction to ensure
//! misconfiguration is detected early.
//!
//! ## Example
//!
//! ```
//! use tenbin_sdk::ClientConfig;
//! use url::Url;
//! use std::time::Duration;
//!
//! let http = Url::parse("https://api.tenbin.io").unwrap();
//! let ws = Url::parse("wss://api.tenbin.io/stream").unwrap();
//!
//! let mut cfg = ClientConfig::new(http, ws);
//! cfg.timeout = Duration::from_secs(30); // Override default
//!
//! // Validation happens automatically when building TenbinClient,
//! // but you can validate early:
//! cfg.validate().expect("config should be valid");
//! ```

use std::time::Duration;
use url::Url;

use crate::ClientError;

/// Configuration for [`crate::TenbinClient`].
///
/// `ClientConfig` defines all user-supplied configuration required to construct
/// a [`crate::TenbinClient`]. This includes REST and WebSocket endpoint URLs and basic
/// client-level timeouts.
///
/// Configuration is validated eagerly via [`ClientConfig::validate`], and
/// invalid configurations result in [`ClientError::Config`] errors.
///
/// ## Defaults
///
/// | Field | Default |
/// |-------|---------|
/// | `timeout` | 10 seconds |
///
/// ## URL Requirements
///
/// | Field | Valid Schemes |
/// |-------|---------------|
/// | `http_base_url` | `http`, `https` |
/// | `ws_base_url` | `ws`, `wss` |
///
/// ## Example
///
/// ```
/// use tenbin_sdk::ClientConfig;
/// use url::Url;
/// use std::time::Duration;
///
/// // Create config with production URLs
/// let http = Url::parse("https://api.tenbin.io").unwrap();
/// let ws = Url::parse("wss://api.tenbin.io/stream").unwrap();
///
/// let mut cfg = ClientConfig::new(http, ws);
///
/// // Customize timeout if needed
/// cfg.timeout = Duration::from_secs(30);
///
/// // Validate before use (also done automatically by TenbinClient::new)
/// cfg.validate().expect("valid config");
/// ```
#[derive(Debug, Clone)]
pub struct ClientConfig {
    /// Base URL for REST (HTTP) requests.
    ///
    /// Must use the `http` or `https` scheme.
    pub http_base_url: Url,

    /// Base URL for WebSocket connections.
    ///
    /// Must use the `ws` or `wss` scheme.
    pub ws_base_url: Url,

    /// Timeout applied to client operations.
    ///
    /// A zero duration is considered invalid.
    pub timeout: Duration,
}

impl ClientConfig {
    /// Construct a new [`ClientConfig`] with default settings.
    ///
    /// The default timeout is **10 seconds**.
    ///
    /// No validation is performed at construction time; callers should invoke
    /// [`ClientConfig::validate`] before using the configuration to construct
    /// a client.
    pub fn new(http: Url, ws: Url) -> Self {
        Self {
            http_base_url: http,
            ws_base_url: ws,
            timeout: Duration::from_secs(10),
        }
    }

    /// Validate the configuration.
    ///
    /// This method checks basic invariants such as:
    /// - timeout must be non-zero
    /// - REST URL scheme must be `http` or `https`
    /// - WebSocket URL scheme must be `ws` or `wss`
    ///
    /// # Errors
    ///
    /// Returns [`ClientError::Config`] if the configuration is invalid.
    pub fn validate(&self) -> Result<(), ClientError> {
        if self.timeout.is_zero() {
            return Err(ClientError::Config("timeout must be > 0".into()));
        }

        match self.http_base_url.scheme() {
            "http" | "https" => {}
            _ => {
                return Err(ClientError::Config(
                    "http_base_url must be http or https".into(),
                ));
            }
        }

        match self.ws_base_url.scheme() {
            "ws" | "wss" => {}
            _ => return Err(ClientError::Config("ws_base_url must be ws or wss".into())),
        }

        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use std::time::Duration;

    use crate::{ClientConfig, ClientError};

    #[test]
    fn client_config_new_sets_defaults() {
        let http = url::Url::parse("http://localhost:8080").unwrap();
        let ws = url::Url::parse("ws://localhost:8080/stream").unwrap();

        let cfg = ClientConfig::new(http.clone(), ws.clone());

        assert_eq!(cfg.http_base_url, http);
        assert_eq!(cfg.ws_base_url, ws);
        assert_eq!(cfg.timeout, std::time::Duration::from_secs(10));
    }

    #[test]
    fn validate_rejects_zero_timeout() {
        let http = url::Url::parse("http://localhost:8080").unwrap();
        let ws = url::Url::parse("ws://localhost:8080/stream").unwrap();

        let mut cfg = ClientConfig::new(http, ws);
        cfg.timeout = Duration::ZERO;

        let err = cfg.validate().unwrap_err();
        assert!(matches!(err, ClientError::Config(_)));
    }

    #[test]
    fn validate_rejects_invalid_http_scheme() {
        let http = url::Url::parse("ftp://localhost:8080").unwrap();
        let ws = url::Url::parse("ws://localhost:8080/stream").unwrap();

        let cfg = ClientConfig::new(http, ws);

        let err = cfg.validate().unwrap_err();
        match err {
            ClientError::Config(msg) => assert!(msg.contains("http")),
            _ => panic!("expected Config error"),
        }
    }

    #[test]
    fn validate_rejects_invalid_ws_scheme() {
        let http = url::Url::parse("http://localhost:8080").unwrap();
        let ws = url::Url::parse("http://localhost:8080/stream").unwrap();

        let cfg = ClientConfig::new(http, ws);

        let err = cfg.validate().unwrap_err();
        match err {
            ClientError::Config(msg) => assert!(msg.contains("ws")),
            _ => panic!("expected Config error"),
        }
    }

    #[test]
    fn validate_accepts_https_and_wss() {
        let http = url::Url::parse("https://localhost:8080").unwrap();
        let ws = url::Url::parse("wss://localhost:8080/stream").unwrap();

        let cfg = ClientConfig::new(http, ws);

        cfg.validate().unwrap();
    }
}
