//! Configuration structures for Hetzner Cloud resources with builder patterns.
//!
//! This module provides builder patterns for creating validated resource configurations.
//! All builders perform validation before construction to catch configuration errors early.

use crate::validation::{
    validate_direction, validate_ipv4_cidr, validate_name, validate_port, validate_port_range,
    validate_protocol,
};
use crate::{HetznerError, Result};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

/// Server specification for creating Hetzner Cloud servers.
///
/// Use [`ServerSpecBuilder`] to construct instances with validation.
///
/// # Examples
///
/// ```
/// use lmrc_hetzner::config::ServerSpec;
///
/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
/// let server = ServerSpec::builder()
///     .name("web-server-01".to_string())
///     .server_type("cx11".to_string())
///     .image("ubuntu-22.04".to_string())
///     .location("nbg1".to_string())
///     .build()?;
/// # Ok(())
/// # }
/// ```
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerSpec {
    pub name: String,
    #[serde(rename = "type")]
    pub server_type: String,
    pub image: String,
    pub location: String,
    #[serde(default)]
    pub labels: HashMap<String, String>,
    #[serde(default)]
    pub ssh_keys: Vec<String>,
}

/// Builder for [`ServerSpec`].
#[derive(Default)]
pub struct ServerSpecBuilder {
    name: Option<String>,
    server_type: Option<String>,
    image: Option<String>,
    location: Option<String>,
    labels: HashMap<String, String>,
    ssh_keys: Vec<String>,
}

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

    /// Sets the server name.
    ///
    /// Must be 1-63 characters, lowercase letters, numbers, hyphens, and underscores only.
    pub fn name(mut self, name: String) -> Self {
        self.name = Some(name);
        self
    }

    /// Sets the server type (e.g., "cx11", "cx21", "cpx11").
    pub fn server_type(mut self, server_type: String) -> Self {
        self.server_type = Some(server_type);
        self
    }

    /// Sets the image (e.g., "ubuntu-22.04", "debian-11").
    pub fn image(mut self, image: String) -> Self {
        self.image = Some(image);
        self
    }

    /// Sets the location (e.g., "nbg1", "fsn1", "hel1").
    pub fn location(mut self, location: String) -> Self {
        self.location = Some(location);
        self
    }

    /// Adds a label.
    pub fn label(mut self, key: String, value: String) -> Self {
        self.labels.insert(key, value);
        self
    }

    /// Sets all labels at once.
    pub fn labels(mut self, labels: HashMap<String, String>) -> Self {
        self.labels = labels;
        self
    }

    /// Adds an SSH key name.
    pub fn ssh_key(mut self, ssh_key: String) -> Self {
        self.ssh_keys.push(ssh_key);
        self
    }

    /// Sets all SSH keys at once.
    pub fn ssh_keys(mut self, ssh_keys: Vec<String>) -> Self {
        self.ssh_keys = ssh_keys;
        self
    }

    /// Builds the [`ServerSpec`], performing validation.
    ///
    /// # Errors
    ///
    /// Returns an error if required fields are missing or validation fails.
    pub fn build(self) -> Result<ServerSpec> {
        let name = self
            .name
            .ok_or_else(|| HetznerError::BuilderError("Server name is required".to_string()))?;

        validate_name(&name, "server")?;

        let server_type = self
            .server_type
            .ok_or_else(|| HetznerError::BuilderError("Server type is required".to_string()))?;

        let image = self
            .image
            .ok_or_else(|| HetznerError::BuilderError("Server image is required".to_string()))?;

        let location = self
            .location
            .ok_or_else(|| HetznerError::BuilderError("Server location is required".to_string()))?;

        Ok(ServerSpec {
            name,
            server_type,
            image,
            location,
            labels: self.labels,
            ssh_keys: self.ssh_keys,
        })
    }
}

impl ServerSpec {
    /// Creates a new builder for [`ServerSpec`].
    pub fn builder() -> ServerSpecBuilder {
        ServerSpecBuilder::new()
    }
}

/// Network configuration for creating Hetzner Cloud private networks.
///
/// Use [`NetworkConfigBuilder`] to construct instances with validation.
///
/// # Examples
///
/// ```
/// use lmrc_hetzner::config::NetworkConfig;
///
/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
/// let network = NetworkConfig::builder()
///     .name("private-network".to_string())
///     .ip_range("10.0.0.0/16".to_string())
///     .zone("eu-central".to_string())
///     .build()?;
/// # Ok(())
/// # }
/// ```
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NetworkConfig {
    pub name: String,
    pub ip_range: String,
    pub zone: String,
}

/// Builder for [`NetworkConfig`].
#[derive(Default)]
pub struct NetworkConfigBuilder {
    name: Option<String>,
    ip_range: Option<String>,
    zone: Option<String>,
}

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

    /// Sets the network name.
    pub fn name(mut self, name: String) -> Self {
        self.name = Some(name);
        self
    }

    /// Sets the IP range in CIDR notation (e.g., "10.0.0.0/16").
    pub fn ip_range(mut self, ip_range: String) -> Self {
        self.ip_range = Some(ip_range);
        self
    }

    /// Sets the network zone (e.g., "eu-central", "us-east").
    pub fn zone(mut self, zone: String) -> Self {
        self.zone = Some(zone);
        self
    }

    /// Builds the [`NetworkConfig`], performing validation.
    pub fn build(self) -> Result<NetworkConfig> {
        let name = self
            .name
            .ok_or_else(|| HetznerError::BuilderError("Network name is required".to_string()))?;

        validate_name(&name, "network")?;

        let ip_range = self
            .ip_range
            .ok_or_else(|| HetznerError::BuilderError("IP range is required".to_string()))?;

        validate_ipv4_cidr(&ip_range, "ip_range")?;

        let zone = self
            .zone
            .ok_or_else(|| HetznerError::BuilderError("Network zone is required".to_string()))?;

        Ok(NetworkConfig {
            name,
            ip_range,
            zone,
        })
    }
}

impl NetworkConfig {
    /// Creates a new builder for [`NetworkConfig`].
    pub fn builder() -> NetworkConfigBuilder {
        NetworkConfigBuilder::new()
    }
}

/// Firewall configuration for creating Hetzner Cloud firewalls.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FirewallConfig {
    pub name: String,
    pub rules: Vec<FirewallRule>,
}

/// Builder for [`FirewallConfig`].
#[derive(Default)]
pub struct FirewallConfigBuilder {
    name: Option<String>,
    rules: Vec<FirewallRule>,
}

impl FirewallConfigBuilder {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn name(mut self, name: String) -> Self {
        self.name = Some(name);
        self
    }

    pub fn rule(mut self, rule: FirewallRule) -> Self {
        self.rules.push(rule);
        self
    }

    pub fn rules(mut self, rules: Vec<FirewallRule>) -> Self {
        self.rules = rules;
        self
    }

    pub fn build(self) -> Result<FirewallConfig> {
        let name = self
            .name
            .ok_or_else(|| HetznerError::BuilderError("Firewall name is required".to_string()))?;

        validate_name(&name, "firewall")?;

        Ok(FirewallConfig {
            name,
            rules: self.rules,
        })
    }
}

impl FirewallConfig {
    pub fn builder() -> FirewallConfigBuilder {
        FirewallConfigBuilder::new()
    }
}

/// Firewall rule for allowing or denying traffic.
#[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 description: Option<String>,
}

/// Builder for [`FirewallRule`].
#[derive(Default)]
pub struct FirewallRuleBuilder {
    direction: Option<String>,
    protocol: Option<String>,
    port: Option<String>,
    source_ips: Vec<String>,
    description: Option<String>,
}

impl FirewallRuleBuilder {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn direction(mut self, direction: String) -> Self {
        self.direction = Some(direction);
        self
    }

    pub fn protocol(mut self, protocol: String) -> Self {
        self.protocol = Some(protocol);
        self
    }

    pub fn port(mut self, port: String) -> Self {
        self.port = Some(port);
        self
    }

    pub fn source_ip(mut self, ip: String) -> Self {
        self.source_ips.push(ip);
        self
    }

    pub fn source_ips(mut self, ips: Vec<String>) -> Self {
        self.source_ips = ips;
        self
    }

    pub fn description(mut self, description: String) -> Self {
        self.description = Some(description);
        self
    }

    pub fn build(self) -> Result<FirewallRule> {
        let direction = self.direction.ok_or_else(|| {
            HetznerError::BuilderError("Firewall rule direction is required".to_string())
        })?;

        validate_direction(&direction, "direction")?;

        let protocol = self.protocol.ok_or_else(|| {
            HetznerError::BuilderError("Firewall rule protocol is required".to_string())
        })?;

        validate_protocol(&protocol, "protocol")?;

        if let Some(ref port) = self.port {
            validate_port_range(port, "port")?;
        }

        if self.source_ips.is_empty() {
            return Err(HetznerError::BuilderError(
                "At least one source IP is required".to_string(),
            ));
        }

        Ok(FirewallRule {
            direction,
            protocol,
            port: self.port,
            source_ips: self.source_ips,
            description: self.description,
        })
    }
}

impl FirewallRule {
    pub fn builder() -> FirewallRuleBuilder {
        FirewallRuleBuilder::new()
    }
}

/// Load balancer configuration.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LoadBalancerConfig {
    pub name: String,
    #[serde(rename = "type")]
    pub lb_type: String,
    pub algorithm: String,
    pub health_check_protocol: String,
    pub health_check_port: u16,
    pub health_check_path: String,
    pub health_check_interval: u64,
    pub health_check_timeout: u64,
    pub services: Vec<LbService>,
}

/// Builder for [`LoadBalancerConfig`].
#[derive(Default)]
pub struct LoadBalancerConfigBuilder {
    name: Option<String>,
    lb_type: Option<String>,
    algorithm: Option<String>,
    health_check_protocol: Option<String>,
    health_check_port: Option<u16>,
    health_check_path: String,
    health_check_interval: u64,
    health_check_timeout: u64,
    services: Vec<LbService>,
}

impl LoadBalancerConfigBuilder {
    pub fn new() -> Self {
        Self {
            health_check_path: "/".to_string(),
            health_check_interval: 15,
            health_check_timeout: 10,
            ..Default::default()
        }
    }

    pub fn name(mut self, name: String) -> Self {
        self.name = Some(name);
        self
    }

    pub fn lb_type(mut self, lb_type: String) -> Self {
        self.lb_type = Some(lb_type);
        self
    }

    pub fn algorithm(mut self, algorithm: String) -> Self {
        self.algorithm = Some(algorithm);
        self
    }

    pub fn health_check_protocol(mut self, protocol: String) -> Self {
        self.health_check_protocol = Some(protocol);
        self
    }

    pub fn health_check_port(mut self, port: u16) -> Self {
        self.health_check_port = Some(port);
        self
    }

    pub fn health_check_path(mut self, path: String) -> Self {
        self.health_check_path = path;
        self
    }

    pub fn health_check_interval(mut self, interval: u64) -> Self {
        self.health_check_interval = interval;
        self
    }

    pub fn health_check_timeout(mut self, timeout: u64) -> Self {
        self.health_check_timeout = timeout;
        self
    }

    pub fn service(mut self, service: LbService) -> Self {
        self.services.push(service);
        self
    }

    pub fn services(mut self, services: Vec<LbService>) -> Self {
        self.services = services;
        self
    }

    pub fn build(self) -> Result<LoadBalancerConfig> {
        let name = self.name.ok_or_else(|| {
            HetznerError::BuilderError("Load balancer name is required".to_string())
        })?;

        validate_name(&name, "loadbalancer")?;

        let lb_type = self.lb_type.ok_or_else(|| {
            HetznerError::BuilderError("Load balancer type is required".to_string())
        })?;

        let algorithm = self.algorithm.unwrap_or_else(|| "round_robin".to_string());

        let health_check_protocol = self.health_check_protocol.ok_or_else(|| {
            HetznerError::BuilderError("Health check protocol is required".to_string())
        })?;

        let health_check_port = self.health_check_port.ok_or_else(|| {
            HetznerError::BuilderError("Health check port is required".to_string())
        })?;

        validate_port(health_check_port, "health_check_port")?;

        if self.services.is_empty() {
            return Err(HetznerError::BuilderError(
                "At least one service is required".to_string(),
            ));
        }

        Ok(LoadBalancerConfig {
            name,
            lb_type,
            algorithm,
            health_check_protocol,
            health_check_port,
            health_check_path: self.health_check_path,
            health_check_interval: self.health_check_interval,
            health_check_timeout: self.health_check_timeout,
            services: self.services,
        })
    }
}

impl LoadBalancerConfig {
    pub fn builder() -> LoadBalancerConfigBuilder {
        LoadBalancerConfigBuilder::new()
    }
}

/// Load balancer service definition.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LbService {
    pub protocol: String,
    pub listen_port: u16,
    pub destination_port: u16,
}

/// Builder for [`LbService`].
#[derive(Default)]
pub struct LbServiceBuilder {
    protocol: Option<String>,
    listen_port: Option<u16>,
    destination_port: Option<u16>,
}

impl LbServiceBuilder {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn protocol(mut self, protocol: String) -> Self {
        self.protocol = Some(protocol);
        self
    }

    pub fn listen_port(mut self, port: u16) -> Self {
        self.listen_port = Some(port);
        self
    }

    pub fn destination_port(mut self, port: u16) -> Self {
        self.destination_port = Some(port);
        self
    }

    pub fn build(self) -> Result<LbService> {
        let protocol = self.protocol.ok_or_else(|| {
            HetznerError::BuilderError("Service protocol is required".to_string())
        })?;

        let listen_port = self
            .listen_port
            .ok_or_else(|| HetznerError::BuilderError("Listen port is required".to_string()))?;

        validate_port(listen_port, "listen_port")?;

        let destination_port = self.destination_port.ok_or_else(|| {
            HetznerError::BuilderError("Destination port is required".to_string())
        })?;

        validate_port(destination_port, "destination_port")?;

        Ok(LbService {
            protocol,
            listen_port,
            destination_port,
        })
    }
}

impl LbService {
    pub fn builder() -> LbServiceBuilder {
        LbServiceBuilder::new()
    }
}

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

    #[test]
    fn test_server_spec_builder() {
        let server = ServerSpec::builder()
            .name("test-server".to_string())
            .server_type("cx11".to_string())
            .image("ubuntu-22.04".to_string())
            .location("nbg1".to_string())
            .build();

        assert!(server.is_ok());
        let server = server.unwrap();
        assert_eq!(server.name, "test-server");
    }

    #[test]
    fn test_server_spec_invalid_name() {
        let result = ServerSpec::builder()
            .name("Invalid-Name".to_string())
            .server_type("cx11".to_string())
            .image("ubuntu-22.04".to_string())
            .location("nbg1".to_string())
            .build();

        assert!(result.is_err());
    }

    #[test]
    fn test_network_config_builder() {
        let network = NetworkConfig::builder()
            .name("test-network".to_string())
            .ip_range("10.0.0.0/16".to_string())
            .zone("eu-central".to_string())
            .build();

        assert!(network.is_ok());
    }

    #[test]
    fn test_network_config_invalid_cidr() {
        let result = NetworkConfig::builder()
            .name("test-network".to_string())
            .ip_range("10.0.0.0".to_string())
            .zone("eu-central".to_string())
            .build();

        assert!(result.is_err());
    }

    #[test]
    fn test_firewall_rule_builder() {
        let rule = FirewallRule::builder()
            .direction("in".to_string())
            .protocol("tcp".to_string())
            .port("80".to_string())
            .source_ip("0.0.0.0/0".to_string())
            .build();

        assert!(rule.is_ok());
    }

    #[test]
    fn test_lb_service_builder() {
        let service = LbService::builder()
            .protocol("http".to_string())
            .listen_port(80)
            .destination_port(8080)
            .build();

        assert!(service.is_ok());
    }
}
