use super::{Firewall, HetznerClient, Result};
use crate::config::FirewallConfig;
use serde::{Deserialize, Serialize};
use tracing::info;

pub struct FirewallManager {
    client: HetznerClient,
}

#[derive(Debug, Serialize)]
struct CreateFirewallRequest {
    name: String,
    rules: Vec<FirewallRuleRequest>,
    #[serde(skip_serializing_if = "Option::is_none")]
    apply_to: Option<Vec<ApplyToSpec>>,
}

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

#[derive(Debug, Serialize)]
struct ApplyToSpec {
    #[serde(rename = "type")]
    apply_type: String,
    server: u64,
}

#[derive(Debug, Deserialize)]
struct CreateFirewallResponse {
    firewall: Firewall,
}

#[derive(Debug, Deserialize)]
struct ListFirewallsResponse {
    firewalls: Vec<Firewall>,
}

#[derive(Debug, Serialize)]
struct ApplyFirewallRequest {
    apply_to: Vec<ApplyToSpec>,
}

impl FirewallManager {
    pub fn new(client: HetznerClient) -> Self {
        Self { client }
    }

    /// List all firewalls
    pub async fn list_firewalls(&self) -> Result<Vec<Firewall>> {
        let response: ListFirewallsResponse = self.client.get("/firewalls").await?;
        Ok(response.firewalls)
    }

    /// Get firewall by ID
    pub async fn get_firewall(&self, id: u64) -> Result<Firewall> {
        #[derive(Debug, Deserialize)]
        struct GetFirewallResponse {
            firewall: Firewall,
        }

        let response: GetFirewallResponse = self.client.get(&format!("/firewalls/{}", id)).await?;
        Ok(response.firewall)
    }

    /// Get firewall by name
    pub async fn get_firewall_by_name(&self, name: &str) -> Result<Option<Firewall>> {
        let firewalls = self.list_firewalls().await?;
        Ok(firewalls.into_iter().find(|f| f.name == name))
    }

    /// Create a new firewall
    pub async fn create_firewall(&self, config: &FirewallConfig) -> Result<Firewall> {
        info!("Creating firewall: {}", config.name);

        let rules: Vec<FirewallRuleRequest> = config
            .rules
            .iter()
            .map(|r| FirewallRuleRequest {
                direction: r.direction.clone(),
                protocol: r.protocol.clone(),
                port: r.port.clone(),
                source_ips: r.source_ips.clone(),
                destination_ips: None,
            })
            .collect();

        let request = CreateFirewallRequest {
            name: config.name.clone(),
            rules,
            apply_to: None,
        };

        let response: CreateFirewallResponse = self.client.post("/firewalls", &request).await?;
        info!("Firewall {} created", config.name);

        Ok(response.firewall)
    }

    /// Apply firewall to servers
    pub async fn apply_to_servers(&self, firewall_id: u64, server_ids: &[u64]) -> Result<()> {
        info!(
            "Applying firewall {} to {} servers",
            firewall_id,
            server_ids.len()
        );

        let apply_to: Vec<ApplyToSpec> = server_ids
            .iter()
            .map(|&server| ApplyToSpec {
                apply_type: "server".to_string(),
                server,
            })
            .collect();

        let request = ApplyFirewallRequest { apply_to };

        #[derive(Debug, Deserialize)]
        struct ApplyResponse {
            actions: Vec<super::Action>,
        }

        let response: ApplyResponse = self
            .client
            .post(
                &format!("/firewalls/{}/actions/apply_to_resources", firewall_id),
                &request,
            )
            .await?;

        // Wait for all actions to complete
        for action in response.actions {
            self.client.wait_for_action(action.id, 120).await?;
        }

        info!("Firewall applied to servers");
        Ok(())
    }

    /// Update firewall rules
    pub async fn update_rules(
        &self,
        firewall_id: u64,
        rules: &[crate::config::FirewallRule],
    ) -> Result<()> {
        #[derive(Debug, Serialize)]
        struct UpdateRulesRequest {
            rules: Vec<crate::config::FirewallRule>,
        }

        info!("Updating rules for firewall {}", firewall_id);

        let request = UpdateRulesRequest {
            rules: rules.to_vec(),
        };

        #[derive(Debug, Deserialize)]
        struct UpdateResponse {
            actions: Vec<super::Action>,
        }

        let response: UpdateResponse = self
            .client
            .post(
                &format!("/firewalls/{}/actions/set_rules", firewall_id),
                &request,
            )
            .await?;

        // Wait for all actions
        for action in response.actions {
            self.client.wait_for_action(action.id, 120).await?;
        }

        info!("Firewall rules updated");
        Ok(())
    }

    /// Remove firewall from all servers
    pub async fn remove_from_all_servers(&self, firewall_id: u64) -> Result<()> {
        info!("Removing firewall {} from all servers", firewall_id);

        #[derive(Debug, Serialize)]
        struct RemoveFirewallRequest {
            remove_from: Vec<RemoveFromSpec>,
        }

        #[derive(Debug, Serialize)]
        struct RemoveFromSpec {
            #[serde(rename = "type")]
            remove_type: String,
            server: u64,
        }

        // Get current firewall state
        let firewalls = self.list_firewalls().await?;
        let firewall = firewalls
            .into_iter()
            .find(|f| f.id == firewall_id)
            .ok_or_else(|| super::HetznerError::NotFound {
                resource_type: "firewall".to_string(),
                identifier: firewall_id.to_string(),
            })?;

        let remove_from: Vec<RemoveFromSpec> = firewall
            .applied_to
            .iter()
            .filter_map(|a| {
                a.server.as_ref().map(|s| RemoveFromSpec {
                    remove_type: "server".to_string(),
                    server: s.id,
                })
            })
            .collect();

        if remove_from.is_empty() {
            info!("Firewall not applied to any servers");
            return Ok(());
        }

        let request = RemoveFirewallRequest { remove_from };

        #[derive(Debug, Deserialize)]
        struct RemoveResponse {
            actions: Vec<super::Action>,
        }

        let response: RemoveResponse = self
            .client
            .post(
                &format!("/firewalls/{}/actions/remove_from_resources", firewall_id),
                &request,
            )
            .await?;

        // Wait for all actions to complete
        for action in response.actions {
            self.client.wait_for_action(action.id, 120).await?;
        }

        info!("Firewall removed from all servers");
        Ok(())
    }

    /// Delete a firewall
    pub async fn delete_firewall(&self, id: u64) -> Result<()> {
        info!("Deleting firewall with ID: {}", id);
        self.client.delete(&format!("/firewalls/{}", id)).await?;
        info!("Firewall deleted");
        Ok(())
    }

    /// Ensure firewall exists
    pub async fn ensure_firewall(&self, config: &FirewallConfig) -> Result<Firewall> {
        if let Some(firewall) = self.get_firewall_by_name(&config.name).await? {
            info!("Firewall {} already exists", config.name);
            return Ok(firewall);
        }

        self.create_firewall(config).await
    }
}
