use super::{HetznerClient, HetznerError, Result, Server};
use crate::config::ServerSpec;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use tracing::{debug, info};

pub struct ServerManager {
    client: HetznerClient,
}

#[derive(Debug, Serialize)]
struct CreateServerRequest {
    name: String,
    server_type: String,
    image: String,
    location: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    ssh_keys: Option<Vec<u64>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    user_data: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    labels: Option<HashMap<String, String>>,
    start_after_create: bool,
    #[serde(skip_serializing_if = "Option::is_none")]
    networks: Option<Vec<u64>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    firewalls: Option<Vec<FirewallAttachment>>,
}

#[derive(Debug, Serialize)]
struct FirewallAttachment {
    firewall: u64,
}

#[derive(Debug, Deserialize)]
struct CreateServerResponse {
    server: Server,
    action: super::Action,
    #[serde(default)]
    next_actions: Vec<super::Action>,
}

#[derive(Debug, Deserialize)]
struct ListServersResponse {
    servers: Vec<Server>,
}

#[allow(dead_code)]
#[derive(Debug, Serialize)]
struct AttachToNetworkRequest {
    network: u64,
    #[serde(skip_serializing_if = "Option::is_none")]
    ip: Option<String>,
}

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

    /// List all servers
    pub async fn list_servers(&self) -> Result<Vec<Server>> {
        let response: ListServersResponse = self.client.get("/servers").await?;
        Ok(response.servers)
    }

    /// Get server by name
    pub async fn get_server_by_name(&self, name: &str) -> Result<Option<Server>> {
        let servers = self.list_servers().await?;
        Ok(servers.into_iter().find(|s| s.name == name))
    }

    /// Get server by ID
    pub async fn get_server(&self, id: u64) -> Result<Server> {
        #[derive(Debug, Deserialize)]
        struct Response {
            server: Server,
        }

        let response: Response = self.client.get(&format!("/servers/{}", id)).await?;
        Ok(response.server)
    }

    /// Create a new server
    pub async fn create_server(
        &self,
        spec: &ServerSpec,
        networks: Option<Vec<u64>>,
        firewalls: Option<Vec<u64>>,
        ssh_key_ids: Option<Vec<u64>>,
    ) -> Result<Server> {
        info!("Creating server: {}", spec.name);

        // Cloud-init user data to disable password authentication
        let user_data = r#"#cloud-config
ssh_pwauth: false
disable_root: false
"#;

        let request = CreateServerRequest {
            name: spec.name.clone(),
            server_type: spec.server_type.clone(),
            image: spec.image.clone(),
            location: spec.location.clone(),
            ssh_keys: ssh_key_ids,
            user_data: Some(user_data.to_string()),
            labels: if spec.labels.is_empty() {
                None
            } else {
                Some(spec.labels.clone())
            },
            start_after_create: true,
            networks,
            firewalls: firewalls.map(|fw_ids| {
                fw_ids
                    .into_iter()
                    .map(|id| FirewallAttachment { firewall: id })
                    .collect()
            }),
        };

        let response: CreateServerResponse = self.client.post("/servers", &request).await?;

        info!(
            "Server {} created, waiting for it to be ready...",
            spec.name
        );

        // Wait for server to be ready
        self.client.wait_for_action(response.action.id, 300).await?;

        // Wait for any additional actions
        for action in response.next_actions {
            self.client.wait_for_action(action.id, 300).await?;
        }

        info!("Server {} is ready", spec.name);

        Ok(response.server)
    }

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

    /// Power on a server
    #[allow(dead_code)]
    pub async fn power_on(&self, id: u64) -> Result<()> {
        #[derive(Debug, Serialize)]
        struct PowerOnRequest {}

        #[derive(Debug, Deserialize)]
        struct PowerOnResponse {
            action: super::Action,
        }

        info!("Powering on server {}", id);
        let response: PowerOnResponse = self
            .client
            .post(
                &format!("/servers/{}/actions/poweron", id),
                &PowerOnRequest {},
            )
            .await?;

        self.client.wait_for_action(response.action.id, 120).await?;
        info!("Server {} powered on", id);
        Ok(())
    }

    /// Power off a server
    #[allow(dead_code)]
    pub async fn power_off(&self, id: u64) -> Result<()> {
        #[derive(Debug, Serialize)]
        struct PowerOffRequest {}

        #[derive(Debug, Deserialize)]
        struct PowerOffResponse {
            action: super::Action,
        }

        info!("Powering off server {}", id);
        let response: PowerOffResponse = self
            .client
            .post(
                &format!("/servers/{}/actions/poweroff", id),
                &PowerOffRequest {},
            )
            .await?;

        self.client.wait_for_action(response.action.id, 120).await?;
        info!("Server {} powered off", id);
        Ok(())
    }

    /// Resize a server (change server type)
    pub async fn resize_server(
        &self,
        server_id: u64,
        new_type: &str,
        upgrade_disk: bool,
    ) -> Result<()> {
        #[derive(Debug, Serialize)]
        struct ResizeRequest {
            server_type: String,
            upgrade_disk: bool,
        }

        #[derive(Debug, Deserialize)]
        struct ResizeResponse {
            action: super::Action,
        }

        info!("Resizing server {} to type {}", server_id, new_type);

        // Power off server first
        self.power_off(server_id).await?;

        let request = ResizeRequest {
            server_type: new_type.to_string(),
            upgrade_disk,
        };

        let response: ResizeResponse = self
            .client
            .post(
                &format!("/servers/{}/actions/change_type", server_id),
                &request,
            )
            .await?;

        self.client.wait_for_action(response.action.id, 300).await?;

        // Power back on
        self.power_on(server_id).await?;

        info!("Server resized successfully");
        Ok(())
    }

    /// Attach server to network
    pub async fn attach_to_network(&self, server_id: u64, network_id: u64) -> Result<()> {
        info!("Attaching server {} to network {}", server_id, network_id);

        let request = AttachToNetworkRequest {
            network: network_id,
            ip: None,
        };

        #[derive(Debug, Deserialize)]
        struct AttachResponse {
            action: super::Action,
        }

        let response: AttachResponse = self
            .client
            .post(
                &format!("/servers/{}/actions/attach_to_network", server_id),
                &request,
            )
            .await?;

        self.client.wait_for_action(response.action.id, 120).await?;
        info!("Server attached to network");
        Ok(())
    }

    /// Wait for server to be running
    #[allow(dead_code)]
    pub async fn wait_for_server_running(&self, id: u64, timeout_secs: u64) -> Result<Server> {
        use tokio::time::{Duration, interval};

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

        loop {
            interval.tick().await;

            let server = self.get_server(id).await?;

            debug!("Server {} status: {}", id, server.status);

            if server.status == "running" {
                return Ok(server);
            }

            if start.elapsed().as_secs() > timeout_secs {
                return Err(HetznerError::Timeout {
                    operation: format!("server {} to become running", id),
                    timeout_secs,
                });
            }
        }
    }

    /// Ensure server exists with given spec
    pub async fn ensure_server(
        &self,
        spec: &ServerSpec,
        networks: Option<Vec<u64>>,
        firewalls: Option<Vec<u64>>,
        ssh_key_ids: Option<Vec<u64>>,
    ) -> Result<Server> {
        // Check if server already exists
        if let Some(server) = self.get_server_by_name(&spec.name).await? {
            info!("Server {} already exists", spec.name);

            // Validate it matches our spec
            if server.server_type.name != spec.server_type {
                return Err(HetznerError::InvalidParameter {
                    parameter: "server_type".to_string(),
                    reason: format!(
                        "Server {} exists but has wrong type: expected {}, got {}",
                        spec.name, spec.server_type, server.server_type.name
                    ),
                });
            }

            return Ok(server);
        }

        // Create new server
        self.create_server(spec, networks, firewalls, ssh_key_ids)
            .await
    }
}
