//! Hetzner Cloud infrastructure provisioner
//!
//! This module implements the InfrastructureProvisioner port for Hetzner Cloud,
//! demonstrating the hexagonal architecture pattern.

use crate::domain::*;
use crate::ports::{InfrastructureProvisioner, ProvisionError};
use crate::{FirewallManager, HetznerClient, LoadBalancerManager, NetworkManager, ServerManager};
use async_trait::async_trait;
use tracing::{debug, info, warn};

/// Hetzner Cloud infrastructure provisioner adapter
///
/// This adapter wraps the Hetzner Cloud API client and implements the InfrastructureProvisioner port.
/// It demonstrates the hexagonal architecture pattern by adapting external infrastructure APIs
/// to domain-driven interfaces.
pub struct HetznerProvisioner {
    server_manager: ServerManager,
    network_manager: NetworkManager,
    firewall_manager: FirewallManager,
    loadbalancer_manager: LoadBalancerManager,
}

impl HetznerProvisioner {
    /// Create a new Hetzner provisioner with the given API token
    pub fn new(api_token: String) -> Result<Self, ProvisionError> {
        let client = HetznerClient::builder()
            .api_token(api_token)
            .build()
            .map_err(|e| ProvisionError::ConnectionError(e.to_string()))?;

        let server_manager = ServerManager::new(client.clone());
        let network_manager = NetworkManager::new(client.clone());
        let firewall_manager = FirewallManager::new(client.clone());
        let loadbalancer_manager = LoadBalancerManager::new(client);

        Ok(Self {
            server_manager,
            network_manager,
            firewall_manager,
            loadbalancer_manager,
        })
    }

    /// Convert Hetzner server to domain entity
    fn convert_server(&self, server: crate::Server) -> Server {
        // Extract server type from labels
        let server_type = match server.labels.get("role").map(|s| s.as_str()) {
            Some("master") => ServerType::Master,
            Some("worker") => ServerType::Worker,
            Some("database") => ServerType::Database,
            _ => ServerType::Worker,
        };

        let status = match server.status.as_str() {
            "running" => ServerStatus::Running,
            "stopped" | "off" => ServerStatus::Stopped,
            "starting" => ServerStatus::Starting,
            "stopping" => ServerStatus::Stopping,
            _ => ServerStatus::Unknown,
        };

        // Create server spec from Hetzner server data
        let spec = ServerSpec::new(
            server.name,
            server_type,
            server.server_type.name.clone(),
            server.datacenter.location.name.clone(),
            server
                .image
                .as_ref()
                .map(|i| i.description.clone())
                .unwrap_or_default(),
        );

        // Create server entity with IPs
        Server::new(ServerId::new(server.id.to_string()), spec, status)
            .with_public_ipv4(server.public_net.ipv4.ip.clone())
    }

    /// Convert Hetzner network to domain entity
    fn convert_network(&self, network: crate::Network) -> Network {
        let subnets = network
            .subnets
            .into_iter()
            .map(|s| SubnetSpec {
                network_zone: s.network_zone,
                ip_range: s.ip_range,
            })
            .collect();

        let spec = NetworkSpec::new(network.name, network.ip_range).with_subnets(subnets);
        Network::new(NetworkId::new(network.id.to_string()), spec)
    }

    /// Convert Hetzner firewall to domain entity
    fn convert_firewall(&self, firewall: crate::Firewall) -> Firewall {
        let rules = firewall
            .rules
            .into_iter()
            .map(|r| {
                let mut rule = FirewallRule::new(r.direction, r.protocol);
                rule = rule.with_source_ips(r.source_ips);
                if let Some(port) = r.port {
                    rule = rule.with_port(port);
                }
                rule
            })
            .collect();

        let spec = FirewallSpec::new(firewall.name).with_rules(rules);
        Firewall::new(FirewallId::new(firewall.id.to_string()), spec)
    }

    /// Convert Hetzner load balancer to domain entity
    fn convert_load_balancer(&self, lb: crate::LoadBalancer) -> LoadBalancer {
        let spec = LoadBalancerSpec::new(
            lb.name.clone(),
            lb.load_balancer_type.name.clone(),
            lb.location.name.clone(),
        );

        LoadBalancer::new(LoadBalancerId::new(lb.id.to_string()), spec)
            .with_public_ipv4(lb.public_net.ipv4.ip.clone())
    }
}

#[async_trait]
impl InfrastructureProvisioner for HetznerProvisioner {
    async fn provision(
        &self,
        spec: &InfrastructureSpec,
        dry_run: bool,
    ) -> Result<(), ProvisionError> {
        if dry_run {
            info!("DRY RUN: Would provision infrastructure");
            return Ok(());
        }

        info!("Provisioning infrastructure on Hetzner Cloud");

        // Track created resources for rollback
        let mut created_server_ids: Vec<u64> = Vec::new();
        let mut created_network_id: Option<u64> = None;
        let mut created_firewall_id: Option<u64> = None;
        let created_lb_id: Option<u64> = None;

        // Define rollback closure
        let rollback = |created_servers: Vec<u64>,
                        created_network: Option<u64>,
                        created_firewall: Option<u64>,
                        created_lb: Option<u64>| async move {
            warn!("Rolling back infrastructure changes");

            // Delete load balancer
            if let Some(lb_id) = created_lb {
                info!("Rolling back load balancer {}", lb_id);
                let _ = self.loadbalancer_manager.delete_load_balancer(lb_id).await;
            }

            // Delete servers
            for server_id in created_servers {
                info!("Rolling back server {}", server_id);
                let _ = self.server_manager.delete_server(server_id).await;
            }

            // Delete firewall
            if let Some(firewall_id) = created_firewall {
                info!("Rolling back firewall {}", firewall_id);
                let _ = self.firewall_manager.delete_firewall(firewall_id).await;
            }

            // Delete network
            if let Some(network_id) = created_network {
                info!("Rolling back network {}", network_id);
                let _ = self.network_manager.delete_network(network_id).await;
            }

            warn!("Rollback complete");
        };

        // 1. Provision network first
        let network = match self.provision_network(&spec.network).await {
            Ok(net) => {
                // Only track if newly created
                if let Ok(net_id) = net.id().as_str().parse::<u64>() {
                    created_network_id = Some(net_id);
                }
                net
            }
            Err(e) => {
                rollback(
                    created_server_ids,
                    created_network_id,
                    created_firewall_id,
                    created_lb_id,
                )
                .await;
                return Err(e);
            }
        };

        // 2. Provision firewall
        let firewall = match self.provision_firewall(&spec.firewall).await {
            Ok(fw) => {
                if let Ok(fw_id) = fw.id().as_str().parse::<u64>() {
                    created_firewall_id = Some(fw_id);
                }
                fw
            }
            Err(e) => {
                rollback(
                    created_server_ids,
                    created_network_id,
                    created_firewall_id,
                    created_lb_id,
                )
                .await;
                return Err(e);
            }
        };

        // 3. Provision servers
        let mut server_ids = Vec::new();
        for server_spec in spec.servers() {
            let server = match self.provision_server(server_spec).await {
                Ok(srv) => srv,
                Err(e) => {
                    rollback(
                        created_server_ids,
                        created_network_id,
                        created_firewall_id,
                        created_lb_id,
                    )
                    .await;
                    return Err(e);
                }
            };

            let server_id = match server.id().as_str().parse::<u64>() {
                Ok(id) => id,
                Err(e) => {
                    rollback(
                        created_server_ids,
                        created_network_id,
                        created_firewall_id,
                        created_lb_id,
                    )
                    .await;
                    return Err(ProvisionError::Internal(format!(
                        "Invalid server ID: {}",
                        e
                    )));
                }
            };

            server_ids.push(server_id);
            created_server_ids.push(server_id);

            // Attach server to network if specified
            if let Some(network_name) = server_spec.network()
                && network_name == spec.network.name()
            {
                info!(
                    "Attaching server {} to network {}",
                    server.name(),
                    network_name
                );
                let network_id = match network.id().as_str().parse::<u64>() {
                    Ok(id) => id,
                    Err(e) => {
                        rollback(
                            created_server_ids,
                            created_network_id,
                            created_firewall_id,
                            created_lb_id,
                        )
                        .await;
                        return Err(ProvisionError::Internal(format!(
                            "Invalid network ID: {}",
                            e
                        )));
                    }
                };

                if let Err(e) = self
                    .server_manager
                    .attach_to_network(server_id, network_id)
                    .await
                {
                    rollback(
                        created_server_ids,
                        created_network_id,
                        created_firewall_id,
                        created_lb_id,
                    )
                    .await;
                    return Err(ProvisionError::ProvisionFailed(format!(
                        "Failed to attach server to network: {}",
                        e
                    )));
                }
            }
        }

        // 4. Apply firewall to servers if specified
        if !spec.firewall.apply_to().is_empty() || !server_ids.is_empty() {
            info!("Applying firewall {} to servers", firewall.name());
            let firewall_id = match firewall.id().as_str().parse::<u64>() {
                Ok(id) => id,
                Err(e) => {
                    rollback(
                        created_server_ids,
                        created_network_id,
                        created_firewall_id,
                        created_lb_id,
                    )
                    .await;
                    return Err(ProvisionError::Internal(format!(
                        "Invalid firewall ID: {}",
                        e
                    )));
                }
            };

            if let Err(e) = self
                .firewall_manager
                .apply_to_servers(firewall_id, &server_ids)
                .await
            {
                rollback(
                    created_server_ids,
                    created_network_id,
                    created_firewall_id,
                    created_lb_id,
                )
                .await;
                return Err(ProvisionError::ProvisionFailed(format!(
                    "Failed to apply firewall: {}",
                    e
                )));
            }
        }

        // 5. Provision load balancer
        match self.provision_load_balancer(&spec.loadbalancer).await {
            Ok(_lb) => {
                // Load balancer successfully created
                info!("Infrastructure provisioning complete");
                Ok(())
            }
            Err(e) => {
                rollback(
                    created_server_ids,
                    created_network_id,
                    created_firewall_id,
                    created_lb_id,
                )
                .await;
                Err(e)
            }
        }
    }

    async fn destroy(&self, force: bool) -> Result<(), ProvisionError> {
        info!("Destroying infrastructure");

        if !force {
            warn!("Destroy requires force=true flag. Aborting.");
            return Err(ProvisionError::InvalidSpec(
                "Destroy requires force=true".to_string(),
            ));
        }

        // Get current state
        let state = self.get_state().await?;

        // Destroy in reverse order: LBs -> Servers -> Firewalls -> Networks
        for lb in &state.load_balancers {
            let lb_id = lb
                .id()
                .as_str()
                .parse::<u64>()
                .map_err(|e| ProvisionError::Internal(format!("Invalid LB ID: {}", e)))?;
            self.loadbalancer_manager
                .delete_load_balancer(lb_id)
                .await
                .map_err(|e| ProvisionError::ProvisionFailed(e.to_string()))?;
        }

        for server in &state.servers {
            let server_id = server
                .id()
                .as_str()
                .parse::<u64>()
                .map_err(|e| ProvisionError::Internal(format!("Invalid server ID: {}", e)))?;
            self.server_manager
                .delete_server(server_id)
                .await
                .map_err(|e| ProvisionError::ProvisionFailed(e.to_string()))?;
        }

        for firewall in &state.firewalls {
            let firewall_id = firewall
                .id()
                .as_str()
                .parse::<u64>()
                .map_err(|e| ProvisionError::Internal(format!("Invalid firewall ID: {}", e)))?;
            self.firewall_manager
                .delete_firewall(firewall_id)
                .await
                .map_err(|e| ProvisionError::ProvisionFailed(e.to_string()))?;
        }

        for network in &state.networks {
            let network_id = network
                .id()
                .as_str()
                .parse::<u64>()
                .map_err(|e| ProvisionError::Internal(format!("Invalid network ID: {}", e)))?;
            self.network_manager
                .delete_network(network_id)
                .await
                .map_err(|e| ProvisionError::ProvisionFailed(e.to_string()))?;
        }

        info!("Infrastructure destroyed");
        Ok(())
    }

    async fn get_state(&self) -> Result<InfrastructureState, ProvisionError> {
        debug!("Fetching current infrastructure state");

        let servers = self
            .server_manager
            .list_servers()
            .await
            .map_err(|e| ProvisionError::ConnectionError(e.to_string()))?
            .into_iter()
            .map(|s| self.convert_server(s))
            .collect();

        let networks = self
            .network_manager
            .list_networks()
            .await
            .map_err(|e| ProvisionError::ConnectionError(e.to_string()))?
            .into_iter()
            .map(|n| self.convert_network(n))
            .collect();

        let firewalls = self
            .firewall_manager
            .list_firewalls()
            .await
            .map_err(|e| ProvisionError::ConnectionError(e.to_string()))?
            .into_iter()
            .map(|f| self.convert_firewall(f))
            .collect();

        let load_balancers = self
            .loadbalancer_manager
            .list_load_balancers()
            .await
            .map_err(|e| ProvisionError::ConnectionError(e.to_string()))?
            .into_iter()
            .map(|lb| self.convert_load_balancer(lb))
            .collect();

        Ok(InfrastructureState {
            servers,
            networks,
            firewalls,
            load_balancers,
        })
    }

    async fn validate(&self, spec: &InfrastructureSpec) -> Result<Vec<String>, ProvisionError> {
        let mut warnings = Vec::new();

        // Validate server specs
        for server_spec in spec.servers() {
            if server_spec.name().is_empty() {
                warnings.push("Server name cannot be empty".to_string());
            }
            if server_spec.size().is_empty() {
                warnings.push(format!(
                    "Server '{}' has no size specified",
                    server_spec.name()
                ));
            }
        }

        // Validate network spec
        if spec.network.name().is_empty() {
            warnings.push("Network name cannot be empty".to_string());
        }
        if spec.network.ip_range().is_empty() {
            warnings.push(format!(
                "Network '{}' has no IP range specified",
                spec.network.name()
            ));
        }

        // Validate firewall spec
        if spec.firewall.name().is_empty() {
            warnings.push("Firewall name cannot be empty".to_string());
        }

        // Validate load balancer spec
        if spec.loadbalancer.name().is_empty() {
            warnings.push("Load balancer name cannot be empty".to_string());
        }

        Ok(warnings)
    }

    async fn provision_server(&self, spec: &ServerSpec) -> Result<Server, ProvisionError> {
        info!("Provisioning server: {}", spec.name());

        // Check if server already exists
        if let Some(existing) = self
            .server_manager
            .get_server_by_name(spec.name())
            .await
            .map_err(|e| ProvisionError::ConnectionError(e.to_string()))?
        {
            info!("Server {} already exists", spec.name());
            return Ok(self.convert_server(existing));
        }

        // Convert domain spec to hetzner-client spec
        let mut labels = std::collections::HashMap::new();
        labels.insert("role".to_string(), spec.server_type().to_string());

        let hetzner_spec = crate::config::ServerSpec {
            name: spec.name().to_string(),
            server_type: spec.size().to_string(),
            image: spec.image().to_string(),
            location: spec.location().to_string(),
            labels,
            ssh_keys: spec.ssh_keys().to_vec(),
        };

        // Create server
        let server = self
            .server_manager
            .create_server(&hetzner_spec, None, None, None)
            .await
            .map_err(|e| ProvisionError::ProvisionFailed(e.to_string()))?;

        info!("Server {} provisioned successfully", spec.name());
        Ok(self.convert_server(server))
    }

    async fn provision_network(&self, spec: &NetworkSpec) -> Result<Network, ProvisionError> {
        info!("Provisioning network: {}", spec.name());

        // Check if network already exists
        if let Some(existing) = self
            .network_manager
            .get_network_by_name(spec.name())
            .await
            .map_err(|e| ProvisionError::ConnectionError(e.to_string()))?
        {
            info!("Network {} already exists", spec.name());
            return Ok(self.convert_network(existing));
        }

        // Convert domain spec to hetzner-client config
        let hetzner_config = crate::config::NetworkConfig {
            name: spec.name().to_string(),
            ip_range: spec.ip_range().to_string(),
            zone: spec
                .subnets()
                .first()
                .map(|s| s.network_zone.clone())
                .unwrap_or_else(|| "eu-central".to_string()),
        };

        // Create network
        let network = self
            .network_manager
            .create_network(&hetzner_config)
            .await
            .map_err(|e| ProvisionError::ProvisionFailed(e.to_string()))?;

        info!("Network {} provisioned successfully", spec.name());
        Ok(self.convert_network(network))
    }

    async fn provision_firewall(&self, spec: &FirewallSpec) -> Result<Firewall, ProvisionError> {
        info!("Provisioning firewall: {}", spec.name());

        // Check if firewall already exists
        if let Some(existing) = self
            .firewall_manager
            .get_firewall_by_name(spec.name())
            .await
            .map_err(|e| ProvisionError::ConnectionError(e.to_string()))?
        {
            info!("Firewall {} already exists", spec.name());
            return Ok(self.convert_firewall(existing));
        }

        // Convert domain rules to hetzner-client rules
        let hetzner_rules: Vec<crate::config::FirewallRule> = spec
            .rules()
            .iter()
            .map(|r| crate::config::FirewallRule {
                direction: r.direction.clone(),
                protocol: r.protocol.clone(),
                port: r.port.clone(),
                source_ips: r.source_ips.clone(),
                description: r.description.clone(),
            })
            .collect();

        let firewall_config = crate::config::FirewallConfig {
            name: spec.name().to_string(),
            rules: hetzner_rules,
        };

        // Create firewall
        let firewall = self
            .firewall_manager
            .create_firewall(&firewall_config)
            .await
            .map_err(|e| ProvisionError::ProvisionFailed(e.to_string()))?;

        info!("Firewall {} provisioned successfully", spec.name());
        Ok(self.convert_firewall(firewall))
    }

    async fn provision_load_balancer(
        &self,
        spec: &LoadBalancerSpec,
    ) -> Result<LoadBalancer, ProvisionError> {
        info!("Provisioning load balancer: {}", spec.name());

        // Check if load balancer already exists
        if let Some(existing) = self
            .loadbalancer_manager
            .get_load_balancer_by_name(spec.name())
            .await
            .map_err(|e| ProvisionError::ConnectionError(e.to_string()))?
        {
            info!("Load balancer {} already exists", spec.name());
            return Ok(self.convert_load_balancer(existing));
        }

        // Convert spec to hetzner config
        let hetzner_config = crate::config::LoadBalancerConfig {
            name: spec.name().to_string(),
            lb_type: spec.load_balancer_type().to_string(),
            algorithm: spec.algorithm().to_string(),
            health_check_protocol: "tcp".to_string(),
            health_check_port: 80,
            health_check_path: "/health".to_string(),
            health_check_interval: 15,
            health_check_timeout: 10,
            services: vec![],
        };

        // Create load balancer
        let lb = self
            .loadbalancer_manager
            .create_load_balancer(&hetzner_config, spec.location(), None)
            .await
            .map_err(|e| ProvisionError::ProvisionFailed(e.to_string()))?;

        info!("Load balancer {} provisioned successfully", spec.name());
        Ok(self.convert_load_balancer(lb))
    }

    async fn update_server(&self, spec: &ServerSpec) -> Result<Server, ProvisionError> {
        info!("Updating server: {}", spec.name());

        // Get existing server
        let existing = self
            .server_manager
            .get_server_by_name(spec.name())
            .await
            .map_err(|e| ProvisionError::ConnectionError(e.to_string()))?
            .ok_or_else(|| ProvisionError::NotFound(format!("Server {} not found", spec.name())))?;

        let server_id = existing.id;

        // Check if size changed - requires resize
        if existing.server_type.name != spec.size() {
            info!(
                "Resizing server {} from {} to {}",
                spec.name(),
                existing.server_type.name,
                spec.size()
            );

            self.server_manager
                .resize_server(server_id, spec.size(), false)
                .await
                .map_err(|e| {
                    ProvisionError::ProvisionFailed(format!("Failed to resize server: {}", e))
                })?;
        }

        // Get updated server
        let updated = self
            .server_manager
            .get_server(server_id)
            .await
            .map_err(|e| ProvisionError::ConnectionError(e.to_string()))?;

        info!("Server {} updated successfully", spec.name());
        Ok(self.convert_server(updated))
    }

    async fn update_firewall(&self, spec: &FirewallSpec) -> Result<Firewall, ProvisionError> {
        info!("Updating firewall: {}", spec.name());

        // Get existing firewall
        let existing = self
            .firewall_manager
            .get_firewall_by_name(spec.name())
            .await
            .map_err(|e| ProvisionError::ConnectionError(e.to_string()))?
            .ok_or_else(|| {
                ProvisionError::NotFound(format!("Firewall {} not found", spec.name()))
            })?;

        let firewall_id = existing.id;

        // Convert domain rules to hetzner-client rules
        let hetzner_rules: Vec<crate::config::FirewallRule> = spec
            .rules()
            .iter()
            .map(|r| crate::config::FirewallRule {
                direction: r.direction.clone(),
                protocol: r.protocol.clone(),
                port: r.port.clone(),
                source_ips: r.source_ips.clone(),
                description: r.description.clone(),
            })
            .collect();

        // Update rules
        self.firewall_manager
            .update_rules(firewall_id, &hetzner_rules)
            .await
            .map_err(|e| {
                ProvisionError::ProvisionFailed(format!("Failed to update firewall: {}", e))
            })?;

        // Get updated firewall
        let updated = self
            .firewall_manager
            .get_firewall(firewall_id)
            .await
            .map_err(|e| ProvisionError::ConnectionError(e.to_string()))?;

        info!("Firewall {} updated successfully", spec.name());
        Ok(self.convert_firewall(updated))
    }

    async fn apply(
        &self,
        spec: &InfrastructureSpec,
        diff: &InfrastructureDiff,
    ) -> Result<(), ProvisionError> {
        info!("Applying infrastructure changes");

        // Process creates, updates, and deletes based on diff
        for item in diff.items() {
            match item.diff_type() {
                DiffType::Create => {
                    info!(
                        "Creating {}: {}",
                        item.resource_type(),
                        item.resource_name()
                    );
                    match item.resource_type() {
                        "server" => {
                            if let Some(server_spec) = spec
                                .servers()
                                .iter()
                                .find(|s| s.name() == item.resource_name())
                            {
                                self.provision_server(server_spec).await?;
                            }
                        }
                        "network" => {
                            self.provision_network(&spec.network).await?;
                        }
                        "firewall" => {
                            self.provision_firewall(&spec.firewall).await?;
                        }
                        "loadbalancer" => {
                            self.provision_load_balancer(&spec.loadbalancer).await?;
                        }
                        _ => warn!("Unknown resource type: {}", item.resource_type()),
                    }
                }
                DiffType::Update => {
                    info!(
                        "Updating {}: {}",
                        item.resource_type(),
                        item.resource_name()
                    );
                    match item.resource_type() {
                        "server" => {
                            if let Some(server_spec) = spec
                                .servers()
                                .iter()
                                .find(|s| s.name() == item.resource_name())
                            {
                                self.update_server(server_spec).await?;
                            }
                        }
                        "firewall" => {
                            self.update_firewall(&spec.firewall).await?;
                        }
                        _ => warn!("Update not supported for {}", item.resource_type()),
                    }
                }
                DiffType::Delete => {
                    warn!(
                        "Delete operation for {}: {} - manual deletion required",
                        item.resource_type(),
                        item.resource_name()
                    );
                }
                DiffType::NoChange => {
                    debug!(
                        "No change for {}: {}",
                        item.resource_type(),
                        item.resource_name()
                    );
                }
            }
        }

        info!("Infrastructure changes applied");
        Ok(())
    }
}
