//! Core Cloudflare API client.

use crate::cache::CacheService;
use crate::dns::DnsService;
use crate::error::{Error, Result};
use crate::zones::ZoneService;
use reqwest::header::{AUTHORIZATION, CONTENT_TYPE, HeaderMap, HeaderValue};

/// The main Cloudflare API client.
///
/// This client provides access to various Cloudflare services through
/// a unified interface. Use the builder pattern to create a client instance.
///
/// # Examples
///
/// ```no_run
/// use lmrc_cloudflare::CloudflareClient;
///
/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
/// let client = CloudflareClient::builder()
///     .api_token("your-api-token")
///     .build()?;
///
/// // Access DNS service
/// let records = client.dns().list_records("zone_id").send().await?;
///
/// // Access zones service
/// let zones = client.zones().list().send().await?;
/// # Ok(())
/// # }
/// ```
#[derive(Clone)]
pub struct CloudflareClient {
    /// Internal HTTP client
    pub(crate) http_client: reqwest::Client,

    /// API token for authentication
    #[allow(dead_code)]
    pub(crate) api_token: String,

    /// Base URL for the API (usually not changed)
    pub base_url: String,

    /// Retry configuration
    pub retry_config: RetryConfig,
}

impl std::fmt::Debug for CloudflareClient {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("CloudflareClient")
            .field("base_url", &self.base_url)
            .field("retry_config", &self.retry_config)
            .finish()
    }
}

/// Configuration for retry behavior.
#[derive(Clone, Debug)]
pub struct RetryConfig {
    /// Maximum number of retry attempts (0 = no retries)
    pub max_retries: u32,

    /// Initial delay between retries
    pub initial_delay: std::time::Duration,

    /// Maximum delay between retries
    pub max_delay: std::time::Duration,

    /// Exponential backoff multiplier
    pub backoff_multiplier: f64,
}

impl Default for RetryConfig {
    fn default() -> Self {
        Self {
            max_retries: 3,
            initial_delay: std::time::Duration::from_millis(500),
            max_delay: std::time::Duration::from_secs(30),
            backoff_multiplier: 2.0,
        }
    }
}

impl RetryConfig {
    /// Create a new retry configuration with no retries.
    pub fn disabled() -> Self {
        Self {
            max_retries: 0,
            initial_delay: std::time::Duration::from_millis(0),
            max_delay: std::time::Duration::from_millis(0),
            backoff_multiplier: 1.0,
        }
    }

    /// Calculate the delay for a given retry attempt.
    pub fn delay_for_attempt(&self, attempt: u32) -> std::time::Duration {
        if attempt == 0 {
            return self.initial_delay;
        }

        let delay_ms =
            (self.initial_delay.as_millis() as f64) * self.backoff_multiplier.powi(attempt as i32);
        let delay = std::time::Duration::from_millis(delay_ms as u64);

        delay.min(self.max_delay)
    }
}

impl CloudflareClient {
    /// Create a new client builder.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use lmrc_cloudflare::CloudflareClient;
    ///
    /// let client = CloudflareClient::builder()
    ///     .api_token("your-api-token")
    ///     .build()?;
    /// # Ok::<(), lmrc_cloudflare::Error>(())
    /// ```
    pub fn builder() -> CloudflareClientBuilder {
        CloudflareClientBuilder::new()
    }

    /// Create a client with just an API token (using default settings).
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use lmrc_cloudflare::CloudflareClient;
    ///
    /// let client = CloudflareClient::new("your-api-token")?;
    /// # Ok::<(), lmrc_cloudflare::Error>(())
    /// ```
    pub fn new(api_token: impl Into<String>) -> Result<Self> {
        Self::builder().api_token(api_token).build()
    }

    /// Get the DNS service for managing DNS records.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use lmrc_cloudflare::CloudflareClient;
    /// # async fn example(client: CloudflareClient) -> Result<(), lmrc_cloudflare::Error> {
    /// let records = client.dns()
    ///     .list_records("zone_id")
    ///     .send()
    ///     .await?;
    /// # Ok(())
    /// # }
    /// ```
    pub fn dns(&self) -> DnsService {
        DnsService::new(self.clone())
    }

    /// Get the zones service for managing zones.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use lmrc_cloudflare::CloudflareClient;
    /// # async fn example(client: CloudflareClient) -> Result<(), lmrc_cloudflare::Error> {
    /// let zones = client.zones()
    ///     .list()
    ///     .send()
    ///     .await?;
    /// # Ok(())
    /// # }
    /// ```
    pub fn zones(&self) -> ZoneService {
        ZoneService::new(self.clone())
    }

    /// Get the cache service for purging cache.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use lmrc_cloudflare::CloudflareClient;
    /// # async fn example(client: CloudflareClient) -> Result<(), lmrc_cloudflare::Error> {
    /// client.cache()
    ///     .purge_everything("zone_id")
    ///     .await?;
    /// # Ok(())
    /// # }
    /// ```
    pub fn cache(&self) -> CacheService {
        CacheService::new(self.clone())
    }

    /// Execute a request with retry logic.
    async fn execute_with_retry<F, Fut>(&self, f: F) -> Result<reqwest::Response>
    where
        F: Fn() -> Fut,
        Fut: std::future::Future<Output = Result<reqwest::Response>>,
    {
        let mut last_error = None;

        for attempt in 0..=self.retry_config.max_retries {
            match f().await {
                Ok(response) => {
                    let status = response.status();

                    // Check for rate limiting
                    if status.as_u16() == 429 {
                        let retry_after = response
                            .headers()
                            .get("retry-after")
                            .and_then(|v| v.to_str().ok())
                            .and_then(|v| v.parse::<u64>().ok())
                            .map(std::time::Duration::from_secs);

                        if attempt < self.retry_config.max_retries {
                            let delay = retry_after
                                .unwrap_or_else(|| self.retry_config.delay_for_attempt(attempt));

                            tokio::time::sleep(delay).await;
                            continue;
                        } else {
                            return Err(Error::RateLimited {
                                retry_after: retry_after.map(|d| d.as_secs()),
                            });
                        }
                    }

                    // For 5xx errors, retry
                    if status.is_server_error() && attempt < self.retry_config.max_retries {
                        let delay = self.retry_config.delay_for_attempt(attempt);
                        tokio::time::sleep(delay).await;
                        continue;
                    }

                    return Ok(response);
                }
                Err(e) => {
                    // For network errors, retry
                    if attempt < self.retry_config.max_retries {
                        let delay = self.retry_config.delay_for_attempt(attempt);
                        tokio::time::sleep(delay).await;
                        last_error = Some(e);
                        continue;
                    } else {
                        return Err(e);
                    }
                }
            }
        }

        Err(last_error.unwrap_or_else(|| Error::InvalidInput("No attempts made".to_string())))
    }

    /// Make a GET request to the Cloudflare API.
    pub(crate) async fn get(&self, path: &str) -> Result<reqwest::Response> {
        let url = format!("{}{}", self.base_url, path);
        self.execute_with_retry(|| async { Ok(self.http_client.get(&url).send().await?) })
            .await
    }

    /// Make a GET request with query parameters.
    pub(crate) async fn get_with_params(
        &self,
        path: &str,
        params: &[(&str, String)],
    ) -> Result<reqwest::Response> {
        let url = format!("{}{}", self.base_url, path);
        let params = params.to_vec();
        self.execute_with_retry(|| async {
            Ok(self.http_client.get(&url).query(&params).send().await?)
        })
        .await
    }

    /// Make a POST request to the Cloudflare API.
    pub(crate) async fn post(
        &self,
        path: &str,
        body: &serde_json::Value,
    ) -> Result<reqwest::Response> {
        let url = format!("{}{}", self.base_url, path);
        let body = body.clone();
        self.execute_with_retry(|| async {
            Ok(self.http_client.post(&url).json(&body).send().await?)
        })
        .await
    }

    /// Make a PUT request to the Cloudflare API.
    pub(crate) async fn put(
        &self,
        path: &str,
        body: &serde_json::Value,
    ) -> Result<reqwest::Response> {
        let url = format!("{}{}", self.base_url, path);
        let body = body.clone();
        self.execute_with_retry(|| async {
            Ok(self.http_client.put(&url).json(&body).send().await?)
        })
        .await
    }

    /// Make a PATCH request to the Cloudflare API.
    #[allow(dead_code)]
    pub(crate) async fn patch(
        &self,
        path: &str,
        body: &serde_json::Value,
    ) -> Result<reqwest::Response> {
        let url = format!("{}{}", self.base_url, path);
        let body = body.clone();
        self.execute_with_retry(|| async {
            Ok(self.http_client.patch(&url).json(&body).send().await?)
        })
        .await
    }

    /// Make a DELETE request to the Cloudflare API.
    pub(crate) async fn delete(&self, path: &str) -> Result<reqwest::Response> {
        let url = format!("{}{}", self.base_url, path);
        self.execute_with_retry(|| async { Ok(self.http_client.delete(&url).send().await?) })
            .await
    }

    /// Handle API response and check for errors.
    pub(crate) async fn handle_response<T>(response: reqwest::Response) -> Result<T>
    where
        T: serde::de::DeserializeOwned,
    {
        let status = response.status();

        // Check for rate limiting
        if status.as_u16() == 429 {
            let retry_after = response
                .headers()
                .get("retry-after")
                .and_then(|v| v.to_str().ok())
                .and_then(|v| v.parse().ok());

            return Err(Error::RateLimited { retry_after });
        }

        // Check for authentication errors
        if status.as_u16() == 401 || status.as_u16() == 403 {
            let body = response.text().await?;
            return Err(Error::Unauthorized(format!(
                "Authentication failed: {}",
                body
            )));
        }

        let body = response.text().await?;

        // Check for non-success status
        if !status.is_success() {
            return Err(Error::Api(crate::error::ApiError::from_response(
                status.as_u16(),
                &body,
            )));
        }

        // Parse the response
        let api_response: crate::types::ApiResponse<T> = serde_json::from_str(&body)?;

        // Check if API reported success
        if !api_response.success {
            let error_msg = api_response
                .errors
                .first()
                .map(|e| e.message.clone())
                .unwrap_or_else(|| "Unknown error".to_string());

            return Err(Error::Api(crate::error::ApiError::new(
                status.as_u16(),
                error_msg,
                body,
            )));
        }

        // Return the result
        api_response
            .result
            .ok_or_else(|| Error::InvalidInput("No result in API response".to_string()))
    }
}

/// Builder for creating a CloudflareClient.
pub struct CloudflareClientBuilder {
    api_token: Option<String>,
    base_url: Option<String>,
    timeout: Option<std::time::Duration>,
    retry_config: Option<RetryConfig>,
}

impl CloudflareClientBuilder {
    /// Create a new builder.
    pub fn new() -> Self {
        Self {
            api_token: None,
            base_url: None,
            timeout: None,
            retry_config: None,
        }
    }

    /// Set the API token for authentication.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use lmrc_cloudflare::CloudflareClient;
    ///
    /// let client = CloudflareClient::builder()
    ///     .api_token("your-api-token")
    ///     .build()?;
    /// # Ok::<(), lmrc_cloudflare::Error>(())
    /// ```
    pub fn api_token(mut self, token: impl Into<String>) -> Self {
        self.api_token = Some(token.into());
        self
    }

    /// Set a custom base URL (advanced usage, usually not needed).
    ///
    /// Defaults to `https://api.cloudflare.com/client/v4`.
    pub fn base_url(mut self, url: impl Into<String>) -> Self {
        self.base_url = Some(url.into());
        self
    }

    /// Set the request timeout.
    ///
    /// Defaults to 30 seconds.
    pub fn timeout(mut self, timeout: std::time::Duration) -> Self {
        self.timeout = Some(timeout);
        self
    }

    /// Set the retry configuration.
    ///
    /// By default, the client will retry failed requests up to 3 times with exponential backoff.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use lmrc_cloudflare::{CloudflareClient, RetryConfig};
    ///
    /// // Disable retries
    /// let client = CloudflareClient::builder()
    ///     .api_token("your-api-token")
    ///     .retry_config(RetryConfig::disabled())
    ///     .build()?;
    ///
    /// // Custom retry configuration
    /// let config = RetryConfig {
    ///     max_retries: 5,
    ///     initial_delay: std::time::Duration::from_millis(1000),
    ///     max_delay: std::time::Duration::from_secs(60),
    ///     backoff_multiplier: 2.0,
    /// };
    /// let client = CloudflareClient::builder()
    ///     .api_token("your-api-token")
    ///     .retry_config(config)
    ///     .build()?;
    /// # Ok::<(), lmrc_cloudflare::Error>(())
    /// ```
    pub fn retry_config(mut self, config: RetryConfig) -> Self {
        self.retry_config = Some(config);
        self
    }

    /// Build the CloudflareClient.
    ///
    /// # Errors
    ///
    /// Returns an error if the API token is not set or if the HTTP client
    /// cannot be created.
    pub fn build(self) -> Result<CloudflareClient> {
        let api_token = self
            .api_token
            .ok_or_else(|| Error::InvalidInput("API token is required".to_string()))?;

        let base_url = self
            .base_url
            .unwrap_or_else(|| "https://api.cloudflare.com/client/v4".to_string());

        let timeout = self
            .timeout
            .unwrap_or_else(|| std::time::Duration::from_secs(30));

        let retry_config = self.retry_config.unwrap_or_default();

        // Build headers
        let mut headers = HeaderMap::new();
        headers.insert(
            AUTHORIZATION,
            HeaderValue::from_str(&format!("Bearer {}", api_token))
                .map_err(|_| Error::InvalidInput("Invalid API token format".to_string()))?,
        );
        headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));

        // Build HTTP client
        let http_client = reqwest::Client::builder()
            .default_headers(headers)
            .timeout(timeout)
            .build()?;

        Ok(CloudflareClient {
            http_client,
            api_token,
            base_url,
            retry_config,
        })
    }
}

impl Default for CloudflareClientBuilder {
    fn default() -> Self {
        Self::new()
    }
}
