//! HTTP client wrapper with platform-specific implementations.
//!
//! - **Native**: Full reqwest features including timeout, connection pooling,
//!   and proxy support
//! - **WASM**: Uses browser's Fetch API (timeout, pooling, and proxy not
//!   available)

#[cfg(not(target_arch = "wasm32"))]
use std::time::Duration;

use crate::config::NetworkConfig;
use crate::error::HttpError;

/// HTTP client wrapper around reqwest.
///
/// Handles platform-specific configuration (native vs WASM).
#[derive(Clone, Debug)]
pub struct HttpClient {
    inner: reqwest::Client,
}

impl HttpClient {
    /// Create a new HTTP client with the specified network configuration.
    ///
    /// # Platform Behavior
    ///
    /// - **Native**: Uses full configuration including timeouts, connection
    ///   pooling, and proxy
    /// - **WASM**: Configuration is ignored (browser controls fetch behavior)
    ///
    /// # Proxy Configuration (Native Only)
    ///
    /// - `proxy_url`: HTTP proxy for all requests
    /// - `https_proxy_url`: Separate HTTPS proxy (falls back to `proxy_url` if
    ///   not set)
    /// - `no_proxy`: Comma-separated hostnames to bypass proxy
    ///
    /// # Errors
    ///
    /// Returns `HttpError::Network` if client creation fails (e.g., invalid
    /// proxy URL).
    #[allow(unused_variables)] // config unused in WASM
    pub fn new(config: &NetworkConfig) -> Result<Self, HttpError> {
        let builder = reqwest::Client::builder()
            .user_agent(concat!("lastid-sdk/", env!("CARGO_PKG_VERSION")));

        // Native-only configuration: timeouts, connection pooling, and proxy
        #[cfg(not(target_arch = "wasm32"))]
        let builder = {
            let builder = builder
                // Granular timeout configuration
                .connect_timeout(Duration::from_secs(config.connect_timeout_seconds))
                .read_timeout(Duration::from_secs(config.read_timeout_seconds))
                .timeout(Duration::from_secs(config.request_timeout_seconds))
                // Connection pool settings
                .pool_idle_timeout(Duration::from_secs(config.pool_idle_timeout_seconds))
                .pool_max_idle_per_host(config.pool_max_idle_per_host);

            // Configure proxy if specified
            Self::configure_proxy(builder, config)?
        };

        let inner = builder
            .build()
            .map_err(|e| HttpError::network(format!("Failed to create HTTP client: {e}")))?;

        Ok(Self { inner })
    }

    /// Configure proxy settings for the HTTP client (native only).
    ///
    /// Handles HTTP proxy, HTTPS proxy, and `no_proxy` exclusions.
    #[cfg(not(target_arch = "wasm32"))]
    fn configure_proxy(
        mut builder: reqwest::ClientBuilder,
        config: &NetworkConfig,
    ) -> Result<reqwest::ClientBuilder, HttpError> {
        // Configure HTTP proxy
        if let Some(proxy_url) = &config.proxy_url {
            let mut proxy = reqwest::Proxy::http(proxy_url)
                .map_err(|e| HttpError::network(format!("Invalid HTTP proxy URL: {e}")))?;

            // Apply no_proxy exclusions
            if let Some(no_proxy) = &config.no_proxy {
                proxy = proxy.no_proxy(reqwest::NoProxy::from_string(no_proxy));
            }

            builder = builder.proxy(proxy);
        }

        // Configure HTTPS proxy (separate from HTTP proxy if specified)
        if let Some(https_proxy_url) = &config.https_proxy_url {
            let mut proxy = reqwest::Proxy::https(https_proxy_url)
                .map_err(|e| HttpError::network(format!("Invalid HTTPS proxy URL: {e}")))?;

            // Apply no_proxy exclusions
            if let Some(no_proxy) = &config.no_proxy {
                proxy = proxy.no_proxy(reqwest::NoProxy::from_string(no_proxy));
            }

            builder = builder.proxy(proxy);
        } else if config.proxy_url.is_some() {
            // If only proxy_url is set, use it for HTTPS as well
            let proxy_url = config.proxy_url.as_ref().unwrap();
            let mut proxy = reqwest::Proxy::https(proxy_url)
                .map_err(|e| HttpError::network(format!("Invalid HTTPS proxy URL: {e}")))?;

            // Apply no_proxy exclusions
            if let Some(no_proxy) = &config.no_proxy {
                proxy = proxy.no_proxy(reqwest::NoProxy::from_string(no_proxy));
            }

            builder = builder.proxy(proxy);
        }

        Ok(builder)
    }

    /// Create a new HTTP client with a simple timeout (convenience method).
    ///
    /// This creates a client with default network configuration except for the
    /// request timeout. For full control over proxy and timeout settings,
    /// use [`HttpClient::new`] with a [`NetworkConfig`].
    ///
    /// # Errors
    ///
    /// Returns `HttpError::Network` if client creation fails.
    pub fn with_timeout(timeout_seconds: u64) -> Result<Self, HttpError> {
        let config = NetworkConfig {
            request_timeout_seconds: timeout_seconds,
            ..Default::default()
        };
        Self::new(&config)
    }

    /// Get the underlying reqwest client.
    #[must_use]
    pub const fn inner(&self) -> &reqwest::Client {
        &self.inner
    }

    /// Create a GET request.
    pub fn get(&self, url: &str) -> reqwest::RequestBuilder {
        self.inner.get(url)
    }

    /// Create a POST request.
    pub fn post(&self, url: &str) -> reqwest::RequestBuilder {
        self.inner.post(url)
    }
}

impl Default for HttpClient {
    fn default() -> Self {
        Self::new(&NetworkConfig::default()).expect("Failed to create default HTTP client")
    }
}

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

    #[test]
    fn test_create_client_with_default_config() {
        let config = NetworkConfig::default();
        let client = HttpClient::new(&config);
        assert!(client.is_ok());
    }

    #[test]
    fn test_create_client_with_timeout() {
        let client = HttpClient::with_timeout(DEFAULT_TIMEOUT_SECONDS);
        assert!(client.is_ok());
    }

    #[test]
    fn test_default_client() {
        let client = HttpClient::default();
        // Verify the inner reqwest client is accessible and functional
        let inner = client.inner();
        // reqwest::Client is Clone, verify we can clone it (proves it's valid)
        let _cloned = inner.clone();
        // Verify we can create request builders
        let _get_builder = client.get("https://example.com");
        let _post_builder = client.post("https://example.com");
    }

    #[test]
    fn test_create_client_with_custom_timeouts() {
        let config = NetworkConfig {
            connect_timeout_seconds: 5,
            read_timeout_seconds: 15,
            request_timeout_seconds: 20,
            pool_idle_timeout_seconds: 60,
            pool_max_idle_per_host: 10,
            ..Default::default()
        };
        let client = HttpClient::new(&config);
        assert!(client.is_ok());
    }

    #[cfg(not(target_arch = "wasm32"))]
    #[test]
    fn test_create_client_with_proxy() {
        let config = NetworkConfig {
            proxy_url: Some("http://proxy.example.com:8080".to_string()),
            ..Default::default()
        };
        let client = HttpClient::new(&config);
        assert!(client.is_ok());
    }

    #[cfg(not(target_arch = "wasm32"))]
    #[test]
    fn test_create_client_with_https_proxy() {
        let config = NetworkConfig {
            proxy_url: Some("http://proxy.example.com:8080".to_string()),
            https_proxy_url: Some("http://secure-proxy.example.com:8443".to_string()),
            ..Default::default()
        };
        let client = HttpClient::new(&config);
        assert!(client.is_ok());
    }

    #[cfg(not(target_arch = "wasm32"))]
    #[test]
    fn test_create_client_with_no_proxy() {
        let config = NetworkConfig {
            proxy_url: Some("http://proxy.example.com:8080".to_string()),
            no_proxy: Some("localhost,127.0.0.1,.internal.corp".to_string()),
            ..Default::default()
        };
        let client = HttpClient::new(&config);
        assert!(client.is_ok());
    }

    #[cfg(not(target_arch = "wasm32"))]
    #[test]
    fn test_create_client_with_invalid_proxy_url() {
        // Note: reqwest's Proxy::http/https validates URLs at construction time.
        // Empty URLs or URLs with invalid schemes should fail.
        let config = NetworkConfig {
            proxy_url: Some(String::new()), // Empty URL is invalid
            ..Default::default()
        };
        let client = HttpClient::new(&config);
        assert!(client.is_err());
        let err = client.unwrap_err();
        assert!(matches!(err, HttpError::Network(_)));
    }

    #[cfg(not(target_arch = "wasm32"))]
    #[test]
    fn test_create_client_with_malformed_proxy_url() {
        // URLs without scheme may or may not fail depending on reqwest version.
        // This test verifies that a valid-looking proxy URL is accepted.
        let config = NetworkConfig {
            proxy_url: Some("http://proxy.example.com:8080".to_string()),
            ..Default::default()
        };
        let client = HttpClient::new(&config);
        assert!(client.is_ok());
    }
}
