use super::{ApiError, HetznerError, Result};
use reqwest::{Client, StatusCode};
use serde::Serialize;
use serde::de::DeserializeOwned;
use std::time::Duration;
use tracing::{debug, warn};

const HETZNER_API_BASE: &str = "https://api.hetzner.cloud/v1";
const DEFAULT_TIMEOUT_SECS: u64 = 30;
const MIN_TIMEOUT_SECS: u64 = 5;
const MAX_TIMEOUT_SECS: u64 = 300;

/// Hetzner Cloud API client.
///
/// This is the main entry point for interacting with the Hetzner Cloud API.
/// Use [`HetznerClientBuilder`] to construct a client with custom configuration.
///
/// # Examples
///
/// ```no_run
/// use lmrc_hetzner::HetznerClient;
/// use std::time::Duration;
///
/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
/// // Simple client with default settings
/// let client = HetznerClient::builder()
///     .api_token("your-api-token".to_string())
///     .build()?;
///
/// // Client with custom timeout
/// let client = HetznerClient::builder()
///     .api_token("your-api-token".to_string())
///     .timeout(Duration::from_secs(60))
///     .build()?;
///
/// // Client from environment variable
/// let client = HetznerClient::from_env()?;
/// # Ok(())
/// # }
/// ```
#[derive(Clone, Debug)]
pub struct HetznerClient {
    client: Client,
    api_token: String,
    base_url: String,
}

/// Builder for [`HetznerClient`].
///
/// Provides a fluent interface for constructing a Hetzner Cloud API client
/// with custom configuration options.
///
/// # Examples
///
/// ```no_run
/// use lmrc_hetzner::HetznerClient;
/// use std::time::Duration;
///
/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
/// let client = HetznerClient::builder()
///     .api_token("your-api-token".to_string())
///     .timeout(Duration::from_secs(60))
///     .user_agent("my-app/1.0".to_string())
///     .build()?;
/// # Ok(())
/// # }
/// ```
#[derive(Default)]
pub struct HetznerClientBuilder {
    api_token: Option<String>,
    timeout: Option<Duration>,
    base_url: Option<String>,
    user_agent: Option<String>,
}

impl HetznerClientBuilder {
    /// Creates a new builder instance.
    pub fn new() -> Self {
        Self::default()
    }

    /// Sets the Hetzner Cloud API token.
    ///
    /// This is required to authenticate with the Hetzner Cloud API.
    ///
    /// # Arguments
    ///
    /// * `token` - Your Hetzner Cloud API token
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use lmrc_hetzner::HetznerClient;
    /// let client = HetznerClient::builder()
    ///     .api_token("your-token-here".to_string())
    ///     .build()?;
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    pub fn api_token(mut self, token: String) -> Self {
        self.api_token = Some(token);
        self
    }

    /// Sets the HTTP request timeout.
    ///
    /// Must be between 5 and 300 seconds. Defaults to 30 seconds.
    ///
    /// # Arguments
    ///
    /// * `timeout` - Request timeout duration
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use lmrc_hetzner::HetznerClient;
    /// # use std::time::Duration;
    /// let client = HetznerClient::builder()
    ///     .api_token("token".to_string())
    ///     .timeout(Duration::from_secs(60))
    ///     .build()?;
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    pub fn timeout(mut self, timeout: Duration) -> Self {
        self.timeout = Some(timeout);
        self
    }

    /// Sets a custom base URL for the Hetzner API.
    ///
    /// This is primarily useful for testing with a mock server.
    /// The default is `https://api.hetzner.cloud/v1`.
    ///
    /// # Arguments
    ///
    /// * `url` - Base URL (without trailing slash)
    pub fn base_url(mut self, url: String) -> Self {
        self.base_url = Some(url);
        self
    }

    /// Sets a custom User-Agent header.
    ///
    /// # Arguments
    ///
    /// * `user_agent` - Custom User-Agent string
    pub fn user_agent(mut self, user_agent: String) -> Self {
        self.user_agent = Some(user_agent);
        self
    }

    /// Builds the [`HetznerClient`].
    ///
    /// # Errors
    ///
    /// Returns an error if:
    /// - API token is not set or is empty
    /// - Timeout is outside the valid range (5-300 seconds)
    /// - HTTP client construction fails
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use lmrc_hetzner::HetznerClient;
    /// let client = HetznerClient::builder()
    ///     .api_token("token".to_string())
    ///     .build()?;
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    pub fn build(self) -> Result<HetznerClient> {
        let api_token = self
            .api_token
            .ok_or_else(|| HetznerError::BuilderError("API token is required".to_string()))?;

        if api_token.trim().is_empty() {
            return Err(HetznerError::InvalidToken(
                "API token cannot be empty".to_string(),
            ));
        }

        let timeout = self
            .timeout
            .unwrap_or(Duration::from_secs(DEFAULT_TIMEOUT_SECS));
        let timeout_secs = timeout.as_secs();

        if !(MIN_TIMEOUT_SECS..=MAX_TIMEOUT_SECS).contains(&timeout_secs) {
            return Err(HetznerError::InvalidParameter {
                parameter: "timeout".to_string(),
                reason: format!(
                    "Timeout must be between {} and {} seconds, got {}",
                    MIN_TIMEOUT_SECS, MAX_TIMEOUT_SECS, timeout_secs
                ),
            });
        }

        let mut client_builder = Client::builder().timeout(timeout);

        if let Some(user_agent) = self.user_agent {
            client_builder = client_builder.user_agent(user_agent);
        }

        let client = client_builder.build()?;

        Ok(HetznerClient {
            client,
            api_token,
            base_url: self
                .base_url
                .unwrap_or_else(|| HETZNER_API_BASE.to_string()),
        })
    }
}

impl HetznerClient {
    /// Creates a new builder for constructing a [`HetznerClient`].
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use lmrc_hetzner::HetznerClient;
    /// let client = HetznerClient::builder()
    ///     .api_token("your-token".to_string())
    ///     .build()?;
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    pub fn builder() -> HetznerClientBuilder {
        HetznerClientBuilder::new()
    }

    /// Creates a new Hetzner API client with default settings.
    ///
    /// **Deprecated:** Use [`HetznerClient::builder()`] instead for better validation and flexibility.
    ///
    /// # Arguments
    ///
    /// * `api_token` - Your Hetzner Cloud API token
    #[deprecated(since = "0.1.0", note = "Use HetznerClient::builder() instead")]
    pub fn new(api_token: String) -> Result<Self> {
        Self::builder().api_token(api_token).build()
    }

    /// Creates a client from the `HETZNER_API_TOKEN` environment variable.
    ///
    /// # Errors
    ///
    /// Returns an error if the environment variable is not set or is empty.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use lmrc_hetzner::HetznerClient;
    /// unsafe { std::env::set_var("HETZNER_API_TOKEN", "your-token"); }
    /// let client = HetznerClient::from_env()?;
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    pub fn from_env() -> Result<Self> {
        let api_token =
            std::env::var("HETZNER_API_TOKEN").map_err(|_| HetznerError::InvalidParameter {
                parameter: "HETZNER_API_TOKEN".to_string(),
                reason: "Environment variable not set".to_string(),
            })?;

        Self::builder().api_token(api_token).build()
    }

    /// GET request
    pub async fn get<T: DeserializeOwned>(&self, endpoint: &str) -> Result<T> {
        let url = format!("{}{}", self.base_url, endpoint);
        debug!("GET {}", url);

        let response = self
            .client
            .get(&url)
            .header("Authorization", format!("Bearer {}", self.api_token))
            .send()
            .await?;

        self.handle_response(response).await
    }

    /// POST request
    pub async fn post<B: Serialize, T: DeserializeOwned>(
        &self,
        endpoint: &str,
        body: &B,
    ) -> Result<T> {
        let url = format!("{}{}", self.base_url, endpoint);
        debug!("POST {}", url);

        let response = self
            .client
            .post(&url)
            .header("Authorization", format!("Bearer {}", self.api_token))
            .json(body)
            .send()
            .await?;

        self.handle_response(response).await
    }

    /// PUT request
    pub async fn put<B: Serialize, T: DeserializeOwned>(
        &self,
        endpoint: &str,
        body: &B,
    ) -> Result<T> {
        let url = format!("{}{}", self.base_url, endpoint);
        debug!("PUT {}", url);

        let response = self
            .client
            .put(&url)
            .header("Authorization", format!("Bearer {}", self.api_token))
            .json(body)
            .send()
            .await?;

        self.handle_response(response).await
    }

    /// DELETE request
    pub async fn delete(&self, endpoint: &str) -> Result<()> {
        let url = format!("{}{}", self.base_url, endpoint);
        debug!("DELETE {}", url);

        let response = self
            .client
            .delete(&url)
            .header("Authorization", format!("Bearer {}", self.api_token))
            .send()
            .await?;

        match response.status() {
            StatusCode::NO_CONTENT | StatusCode::OK => Ok(()),
            status => {
                let error_text = response.text().await.unwrap_or_default();
                warn!("DELETE failed with status {}: {}", status, error_text);

                if let Ok(api_error) = serde_json::from_str::<ApiError>(&error_text) {
                    Err(HetznerError::Api {
                        code: api_error.error.code,
                        message: api_error.error.message,
                    })
                } else {
                    Err(HetznerError::Api {
                        code: format!("http_{}", status.as_u16()),
                        message: format!("Request failed with status {}", status),
                    })
                }
            }
        }
    }

    /// Handle HTTP response
    async fn handle_response<T: DeserializeOwned>(&self, response: reqwest::Response) -> Result<T> {
        let status = response.status();

        match status {
            StatusCode::OK | StatusCode::CREATED => {
                let body = response.text().await?;
                debug!("Response body: {}", body);

                serde_json::from_str::<T>(&body).map_err(|e| {
                    warn!("Failed to parse response: {}", e);
                    HetznerError::Serialization(e)
                })
            }
            StatusCode::UNAUTHORIZED => Err(HetznerError::Unauthorized),
            StatusCode::FORBIDDEN => {
                let error_text = response.text().await.unwrap_or_default();
                if let Ok(api_error) = serde_json::from_str::<ApiError>(&error_text) {
                    Err(HetznerError::Forbidden {
                        details: api_error.error.message,
                    })
                } else {
                    Err(HetznerError::Forbidden {
                        details: "Permission denied".to_string(),
                    })
                }
            }
            StatusCode::NOT_FOUND => Err(HetznerError::NotFound {
                resource_type: "resource".to_string(),
                identifier: "unknown".to_string(),
            }),
            StatusCode::CONFLICT => {
                let error_text = response.text().await.unwrap_or_default();
                if let Ok(api_error) = serde_json::from_str::<ApiError>(&error_text) {
                    Err(HetznerError::AlreadyExists {
                        resource_type: "resource".to_string(),
                        name: api_error.error.message,
                    })
                } else {
                    Err(HetznerError::AlreadyExists {
                        resource_type: "resource".to_string(),
                        name: "unknown".to_string(),
                    })
                }
            }
            StatusCode::TOO_MANY_REQUESTS => {
                let retry_after = response
                    .headers()
                    .get("retry-after")
                    .and_then(|v| v.to_str().ok())
                    .and_then(|v| v.parse().ok());

                Err(HetznerError::RateLimited { retry_after })
            }
            status if status.is_server_error() => {
                let error_text = response.text().await.unwrap_or_default();
                Err(HetznerError::ServerError {
                    status_code: status.as_u16(),
                    details: error_text,
                })
            }
            _ => {
                let error_text = response.text().await.unwrap_or_default();
                warn!("Request failed with status {}: {}", status, error_text);

                if let Ok(api_error) = serde_json::from_str::<ApiError>(&error_text) {
                    Err(HetznerError::Api {
                        code: api_error.error.code,
                        message: api_error.error.message,
                    })
                } else {
                    Err(HetznerError::Api {
                        code: format!("http_{}", status.as_u16()),
                        message: format!("Request failed with status {}", status),
                    })
                }
            }
        }
    }

    /// Waits for an asynchronous action to complete.
    ///
    /// Polls the action status at regular intervals until it succeeds, fails, or times out.
    ///
    /// # Arguments
    ///
    /// * `action_id` - The ID of the action to wait for
    /// * `timeout_secs` - Maximum time to wait in seconds
    ///
    /// # Errors
    ///
    /// Returns an error if:
    /// - The action fails
    /// - The timeout is exceeded
    /// - API requests fail
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use lmrc_hetzner::HetznerClient;
    /// # async fn example(client: HetznerClient, action_id: u64) -> Result<(), Box<dyn std::error::Error>> {
    /// // Wait up to 5 minutes for the action to complete
    /// client.wait_for_action(action_id, 300).await?;
    /// # Ok(())
    /// # }
    /// ```
    pub async fn wait_for_action(&self, action_id: u64, timeout_secs: u64) -> Result<()> {
        use tokio::time::{Duration, interval};

        let mut interval = interval(Duration::from_secs(2));
        let start = std::time::Instant::now();

        loop {
            interval.tick().await;

            let response: serde_json::Value = self.get(&format!("/actions/{}", action_id)).await?;

            let action = &response["action"];
            let status = action["status"].as_str().unwrap_or("unknown");

            debug!("Action {} status: {}", action_id, status);

            match status {
                "success" => return Ok(()),
                "error" => {
                    let error_msg = action["error"]["message"]
                        .as_str()
                        .unwrap_or("Unknown error");
                    let error_code = action["error"]["code"].as_str().unwrap_or("action_failed");
                    return Err(HetznerError::Api {
                        code: error_code.to_string(),
                        message: format!("Action failed: {}", error_msg),
                    });
                }
                "running" => {
                    if start.elapsed().as_secs() > timeout_secs {
                        return Err(HetznerError::Timeout {
                            operation: format!("action {}", action_id),
                            timeout_secs,
                        });
                    }
                    // Continue waiting
                }
                _ => {
                    warn!("Unknown action status: {}", status);
                }
            }
        }
    }
}

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

    #[test]
    fn test_client_creation() {
        #[allow(deprecated)]
        let client = HetznerClient::new("test_token".to_string());
        assert!(client.is_ok());
    }

    #[test]
    fn test_builder_basic() {
        let client = HetznerClient::builder()
            .api_token("test_token".to_string())
            .build();
        assert!(client.is_ok());
    }

    #[test]
    fn test_builder_missing_token() {
        let result = HetznerClient::builder().build();
        assert!(result.is_err());
        assert!(matches!(result.unwrap_err(), HetznerError::BuilderError(_)));
    }

    #[test]
    fn test_builder_empty_token() {
        let result = HetznerClient::builder()
            .api_token("   ".to_string())
            .build();
        assert!(result.is_err());
        assert!(matches!(result.unwrap_err(), HetznerError::InvalidToken(_)));
    }

    #[test]
    fn test_builder_invalid_timeout() {
        let result = HetznerClient::builder()
            .api_token("token".to_string())
            .timeout(Duration::from_secs(1))
            .build();
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            HetznerError::InvalidParameter { .. }
        ));
    }

    #[test]
    fn test_builder_custom_timeout() {
        let client = HetznerClient::builder()
            .api_token("token".to_string())
            .timeout(Duration::from_secs(60))
            .build();
        assert!(client.is_ok());
    }

    #[test]
    fn test_builder_custom_base_url() {
        let client = HetznerClient::builder()
            .api_token("token".to_string())
            .base_url("https://mock.api.test".to_string())
            .build();
        assert!(client.is_ok());
        let client = client.unwrap();
        assert_eq!(client.base_url, "https://mock.api.test");
    }

    #[test]
    fn test_builder_custom_user_agent() {
        let client = HetznerClient::builder()
            .api_token("token".to_string())
            .user_agent("my-app/1.0".to_string())
            .build();
        assert!(client.is_ok());
    }
}
