//! HTTP Client implementation

use crate::{config::ClientConfig, error::Result, retry::RetryConfig};
use reqwest::{Method, Response};

/// HTTP Client with connection pooling and retry logic
pub struct HttpClient {
    client: reqwest::Client,
    #[allow(dead_code)]
    config: ClientConfig,
    retry_config: RetryConfig,
}

impl HttpClient {
    /// Create a new HTTP client with the given configuration
    ///
    /// **Note:** This will inherit violations from ClientConfig and RetryConfig
    pub fn new(config: ClientConfig) -> Result<Self> {
        let retry_config = RetryConfig::default();
        let client = Self::build_reqwest_client(&config)?;
        Ok(Self {
            client,
            config,
            retry_config,
        })
    }

    /// Create a production-safe HTTP client (no violations)
    pub fn production() -> Result<Self> {
        let config = ClientConfig::production();
        let retry_config = RetryConfig::production();
        let client = Self::build_reqwest_client(&config)?;
        Ok(Self {
            client,
            config,
            retry_config,
        })
    }

    fn build_reqwest_client(config: &ClientConfig) -> Result<reqwest::Client> {
        let mut builder = reqwest::Client::builder()
            .user_agent(&config.user_agent)
            .timeout(config.request_timeout)
            .connect_timeout(config.connect_timeout)
            .pool_max_idle_per_host(config.pool_size);

        // VIOLATION 1: Unbounded redirects (if max_redirects is None)
        if let Some(max_redirects) = config.max_redirects {
            builder = builder.redirect(reqwest::redirect::Policy::limited(max_redirects));
        }
        // If None, reqwest uses default (no limit check on our end)

        // VIOLATION 4: Missing idle timeout (if idle_timeout is None)
        if let Some(idle_timeout) = config.idle_timeout {
            builder = builder.pool_idle_timeout(idle_timeout);
        }
        // If None, connections never expire (violation)

        // VIOLATION 5: TLS verification disabled
        builder = builder.danger_accept_invalid_certs(!config.verify_tls);

        // VIOLATION 6: TLS version too low
        // Note: reqwest doesn't expose direct TLS version control easily,
        // but we document the violation in config

        Ok(builder.build()?)
    }

    /// Perform a GET request
    pub async fn get(&self, url: &str) -> Result<Response> {
        self.request(Method::GET, url, None).await
    }

    /// Perform a POST request with JSON body
    pub async fn post(&self, url: &str, body: serde_json::Value) -> Result<Response> {
        self.request(Method::POST, url, Some(body)).await
    }

    async fn request(
        &self,
        method: Method,
        url: &str,
        body: Option<serde_json::Value>,
    ) -> Result<Response> {
        let mut request = self.client.request(method.clone(), url);

        if let Some(body) = body {
            request = request.json(&body);
        }

        // VIOLATION 7: Unbounded retries (if max_retries is None)
        let max_attempts = self.retry_config.max_retries.unwrap_or(u32::MAX);

        for attempt in 0..max_attempts {
            match request.try_clone().unwrap().send().await {
                Ok(response) => {
                    // Check if we should retry based on status code
                    if self.should_retry(&response, &method) && attempt < max_attempts - 1 {
                        continue;
                    }
                    return Ok(response);
                }
                Err(e) if attempt < max_attempts - 1 && self.is_retryable_error(&e, &method) => {
                    // Retry on network errors
                    continue;
                }
                Err(e) => return Err(e.into()),
            }
        }

        unreachable!("Retry loop should return or error");
    }

    fn should_retry(&self, response: &Response, method: &Method) -> bool {
        // Check if method is idempotent
        if self.retry_config.idempotent_only && !is_idempotent(method) {
            return false;
        }

        // Check if status code is in retry list
        self.retry_config
            .retry_status_codes
            .contains(&response.status().as_u16())
    }

    fn is_retryable_error(&self, _error: &reqwest::Error, method: &Method) -> bool {
        // Only retry idempotent methods on network errors
        if self.retry_config.idempotent_only {
            is_idempotent(method)
        } else {
            true
        }
    }
}

fn is_idempotent(method: &Method) -> bool {
    matches!(
        *method,
        Method::GET | Method::HEAD | Method::PUT | Method::DELETE | Method::OPTIONS
    )
}

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

    #[test]
    fn default_client_has_violations() {
        let config = ClientConfig::default();
        assert!(config.validate().is_err(), "Default client config should have violations");

        let retry_config = RetryConfig::default();
        assert!(retry_config.validate().is_err(), "Default retry config should have violations");
    }

    #[test]
    fn idempotent_methods() {
        assert!(is_idempotent(&Method::GET));
        assert!(is_idempotent(&Method::PUT));
        assert!(is_idempotent(&Method::DELETE));
        assert!(!is_idempotent(&Method::POST));
    }
}
