//! # Hetzner Cloud Client
//!
//! A comprehensive, async Hetzner Cloud API client with builders, validation, and extensive error handling.
//!
//! ## Features
//!
//! - **Type-safe builders** with compile-time validation
//! - **Comprehensive error handling** with detailed error contexts
//! - **Async/await** support with Tokio
//! - **Input validation** for IP addresses, ports, names, etc.
//! - **Full API coverage** for servers, networks, firewalls, load balancers, and SSH keys
//!
//! ## Quick Start
//!
//! ```no_run
//! use lmrc_hetzner::HetznerClient;
//!
//! #[tokio::main]
//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
//!     // Create a client from environment variable
//!     let client = HetznerClient::from_env()?;
//!
//!     // Or build with custom configuration
//!     let client = HetznerClient::builder()
//!         .api_token("your-api-token".to_string())
//!         .build()?;
//!
//!     Ok(())
//! }
//! ```

pub mod client;
pub mod config;
pub mod differ;
pub mod domain;
pub mod firewalls;
pub mod floating_ips;
pub mod loadbalancers;
pub mod networks;
pub mod ports;
pub mod provisioner;
pub mod retry;
pub mod servers;
pub mod ssh_keys;
pub mod state;
pub mod validation;
pub mod volumes;

pub use client::{HetznerClient, HetznerClientBuilder};
pub use differ::HetznerDiffer;
pub use firewalls::FirewallManager;
pub use floating_ips::FloatingIpManager;
pub use loadbalancers::LoadBalancerManager;
pub use networks::NetworkManager;
pub use provisioner::HetznerProvisioner;
pub use servers::ServerManager;
pub use ssh_keys::SshKeyManager;
pub use state::StateManager;
pub use volumes::VolumeManager;

use serde::{Deserialize, Serialize};
use thiserror::Error;

/// Comprehensive error type for Hetzner Cloud API operations.
///
/// This error type provides detailed context about what went wrong, including
/// HTTP status codes, API error details, and contextual information.
///
/// # Examples
///
/// ```no_run
/// use lmrc_hetzner::{HetznerClient, HetznerError};
///
/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
/// let client = HetznerClient::builder()
///     .api_token("your-token".to_string())
///     .build()?;
///
/// match client.get::<serde_json::Value>("/servers").await {
///     Ok(servers) => println!("Got servers: {:?}", servers),
///     Err(HetznerError::Unauthorized) => {
///         eprintln!("Invalid API token");
///     }
///     Err(HetznerError::RateLimited { retry_after }) => {
///         eprintln!("Rate limited, retry after: {:?}", retry_after);
///     }
///     Err(e) => eprintln!("Other error: {}", e),
/// }
/// # Ok(())
/// # }
/// ```
#[derive(Error, Debug)]
pub enum HetznerError {
    /// HTTP request failed due to network or connection issues.
    ///
    /// This typically indicates network problems, DNS failures, or connection timeouts.
    #[error("HTTP request failed: {0}")]
    Http(#[from] reqwest::Error),

    /// Hetzner API returned an error response.
    ///
    /// Contains the error code and message from the API.
    #[error("API error [{code}]: {message}")]
    Api {
        /// Error code from Hetzner API (e.g., "invalid_input", "rate_limit_exceeded")
        code: String,
        /// Human-readable error message
        message: String,
    },

    /// The requested resource was not found (HTTP 404).
    ///
    /// This could mean the resource doesn't exist or was already deleted.
    #[error("Resource not found: {resource_type} with identifier '{identifier}'")]
    NotFound {
        /// Type of resource (e.g., "server", "network", "firewall")
        resource_type: String,
        /// The identifier used to lookup the resource
        identifier: String,
    },

    /// Resource already exists with the same name (HTTP 409).
    #[error("Resource already exists: {resource_type} named '{name}'")]
    AlreadyExists {
        /// Type of resource that already exists
        resource_type: String,
        /// Name of the existing resource
        name: String,
    },

    /// Invalid input parameter or configuration.
    ///
    /// # Examples
    ///
    /// ```
    /// use lmrc_hetzner::HetznerError;
    ///
    /// let err = HetznerError::InvalidParameter {
    ///     parameter: "ip_range".to_string(),
    ///     reason: "Must be a valid CIDR notation (e.g., 10.0.0.0/16)".to_string(),
    /// };
    /// ```
    #[error("Invalid parameter '{parameter}': {reason}")]
    InvalidParameter {
        /// Name of the invalid parameter
        parameter: String,
        /// Explanation of why it's invalid
        reason: String,
    },

    /// Validation failed for input data.
    #[error("Validation failed: {0}")]
    Validation(String),

    /// Operation timed out waiting for completion.
    #[error("Operation timeout: {operation} exceeded {timeout_secs}s")]
    Timeout {
        /// Description of the operation that timed out
        operation: String,
        /// Timeout duration in seconds
        timeout_secs: u64,
    },

    /// JSON serialization or deserialization failed.
    #[error("Serialization error: {0}")]
    Serialization(#[from] serde_json::Error),

    /// Authentication failed (HTTP 401).
    ///
    /// Usually indicates an invalid or expired API token.
    #[error("Authentication failed: invalid or missing API token")]
    Unauthorized,

    /// Permission denied (HTTP 403).
    ///
    /// The API token is valid but lacks permission for this operation.
    #[error("Permission denied: {details}")]
    Forbidden {
        /// Details about what permission is lacking
        details: String,
    },

    /// Rate limit exceeded (HTTP 429).
    ///
    /// The API has rate limits. Wait before retrying.
    #[error("Rate limit exceeded, retry after {retry_after:?}")]
    RateLimited {
        /// Suggested time to wait before retrying
        retry_after: Option<u64>,
    },

    /// Server error from Hetzner API (HTTP 5xx).
    #[error("Server error (HTTP {status_code}): {details}")]
    ServerError {
        /// HTTP status code
        status_code: u16,
        /// Error details if available
        details: String,
    },

    /// Invalid API token format.
    #[error("Invalid API token format: {0}")]
    InvalidToken(String),

    /// Builder configuration error.
    #[error("Builder error: {0}")]
    BuilderError(String),
}

/// Result type alias for operations that may return a [`HetznerError`].
pub type Result<T> = std::result::Result<T, HetznerError>;

/// Common response wrapper from Hetzner API
#[allow(dead_code)]
#[derive(Debug, Serialize, Deserialize)]
pub struct ApiResponse<T> {
    #[serde(flatten)]
    pub data: T,
}

/// Error response from Hetzner API
#[derive(Debug, Serialize, Deserialize)]
pub struct ApiError {
    pub error: ErrorDetails,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct ErrorDetails {
    pub code: String,
    pub message: String,
}

/// Server resource
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Server {
    pub id: u64,
    pub name: String,
    pub status: String,
    pub public_net: PublicNet,
    pub private_net: Vec<PrivateNet>,
    pub server_type: ServerType,
    pub datacenter: Datacenter,
    pub image: Option<Image>,
    #[serde(default)]
    pub labels: std::collections::HashMap<String, String>,
    pub created: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PublicNet {
    pub ipv4: IpInfo,
    pub ipv6: Option<IpInfo>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IpInfo {
    pub ip: String,
    #[serde(default)]
    pub blocked: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PrivateNet {
    pub network: u64,
    pub ip: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerType {
    pub id: u64,
    pub name: String,
    pub description: String,
    pub cores: u32,
    pub memory: f64,
    pub disk: u64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Datacenter {
    pub id: u64,
    pub name: String,
    pub description: String,
    pub location: Location,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Location {
    pub id: u64,
    pub name: String,
    pub description: String,
    pub country: String,
    pub city: String,
    pub latitude: f64,
    pub longitude: f64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Image {
    pub id: Option<u64>,
    pub name: Option<String>,
    pub description: String,
    #[serde(rename = "type")]
    pub image_type: String,
}

/// Network resource
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Network {
    pub id: u64,
    pub name: String,
    pub ip_range: String,
    pub subnets: Vec<Subnet>,
    #[serde(default)]
    pub labels: std::collections::HashMap<String, String>,
    pub created: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Subnet {
    pub ip_range: String,
    pub network_zone: String,
    #[serde(rename = "type")]
    pub subnet_type: String,
}

/// Firewall resource
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Firewall {
    pub id: u64,
    pub name: String,
    pub rules: Vec<FirewallRule>,
    pub applied_to: Vec<FirewallAppliedTo>,
    #[serde(default)]
    pub labels: std::collections::HashMap<String, String>,
    pub created: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FirewallRule {
    pub direction: String,
    pub protocol: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub port: Option<String>,
    pub source_ips: Vec<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub destination_ips: Option<Vec<String>>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FirewallAppliedTo {
    #[serde(rename = "type")]
    pub apply_type: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub server: Option<FirewallServerRef>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FirewallServerRef {
    pub id: u64,
}

/// Load Balancer resource
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LoadBalancer {
    pub id: u64,
    pub name: String,
    pub public_net: LbPublicNet,
    pub private_net: Vec<LbPrivateNet>,
    pub load_balancer_type: LoadBalancerType,
    pub location: Location,
    pub algorithm: Algorithm,
    pub services: Vec<LbService>,
    pub targets: Vec<LbTarget>,
    #[serde(default)]
    pub labels: std::collections::HashMap<String, String>,
    pub created: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LbPublicNet {
    pub ipv4: IpInfo,
    pub ipv6: Option<IpInfo>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LbPrivateNet {
    pub network: u64,
    pub ip: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LoadBalancerType {
    pub id: u64,
    pub name: String,
    pub description: String,
    pub max_connections: u64,
    pub max_targets: u64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Algorithm {
    #[serde(rename = "type")]
    pub algorithm_type: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LbService {
    pub protocol: String,
    pub listen_port: u16,
    pub destination_port: u16,
    pub proxyprotocol: bool,
    pub http: Option<HttpConfig>,
    pub health_check: HealthCheck,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HttpConfig {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub certificates: Option<Vec<u64>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub sticky_sessions: Option<bool>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HealthCheck {
    pub protocol: String,
    pub port: u16,
    pub interval: u64,
    pub timeout: u64,
    pub retries: u64,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub http: Option<HttpHealthCheck>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HttpHealthCheck {
    pub domain: Option<String>,
    pub path: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub response: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub status_codes: Option<Vec<String>>,
    pub tls: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LbTarget {
    #[serde(rename = "type")]
    pub target_type: String,
    pub server: Option<LbTargetServer>,
    pub use_private_ip: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LbTargetServer {
    pub id: u64,
}

/// SSH Key resource
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SshKey {
    pub id: u64,
    pub name: String,
    pub fingerprint: String,
    pub public_key: String,
    #[serde(default)]
    pub labels: std::collections::HashMap<String, String>,
    pub created: String,
}

/// Action resource (for async operations)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Action {
    pub id: u64,
    pub command: String,
    pub status: String,
    pub progress: u8,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub error: Option<ActionError>,
    pub started: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub finished: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ActionError {
    pub code: String,
    pub message: String,
}

impl Action {
    #[allow(dead_code)]
    pub fn is_finished(&self) -> bool {
        self.status == "success" || self.status == "error"
    }

    #[allow(dead_code)]
    pub fn is_success(&self) -> bool {
        self.status == "success"
    }

    #[allow(dead_code)]
    pub fn is_error(&self) -> bool {
        self.status == "error"
    }
}
