//! Volume management for Hetzner Cloud
//!
//! This module provides functionality for creating, managing, and deleting volumes.

use crate::{HetznerClient, Result};
use serde::{Deserialize, Serialize};
use tracing::info;

/// Volume manager for Hetzner Cloud API
pub struct VolumeManager {
    client: HetznerClient,
}

/// Volume configuration
#[derive(Debug, Clone, Serialize)]
pub struct VolumeConfig {
    pub name: String,
    pub size: u64, // Size in GB
    pub location: Option<String>,
    pub format: Option<String>, // Filesystem format: ext4 or xfs
    #[serde(skip_serializing_if = "Option::is_none")]
    pub labels: Option<std::collections::HashMap<String, String>>,
}

/// Volume resource
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Volume {
    pub id: u64,
    pub name: String,
    pub size: u64,
    pub location: VolumeLocation,
    pub format: Option<String>,
    #[serde(default)]
    pub labels: std::collections::HashMap<String, String>,
    pub created: String,
    pub server: Option<u64>,
    pub linux_device: Option<String>,
}

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

#[derive(Debug, Deserialize)]
struct CreateVolumeResponse {
    volume: Volume,
    action: crate::Action,
}

#[derive(Debug, Deserialize)]
struct ListVolumesResponse {
    volumes: Vec<Volume>,
}

#[derive(Debug, Serialize)]
struct AttachVolumeRequest {
    server: u64,
    #[serde(skip_serializing_if = "Option::is_none")]
    automount: Option<bool>,
}

#[derive(Debug, Serialize)]
struct ResizeVolumeRequest {
    size: u64,
}

impl VolumeManager {
    /// Create a new volume manager
    pub fn new(client: HetznerClient) -> Self {
        Self { client }
    }

    /// List all volumes
    pub async fn list_volumes(&self) -> Result<Vec<Volume>> {
        let response: ListVolumesResponse = self.client.get("/volumes").await?;
        Ok(response.volumes)
    }

    /// Get a specific volume by ID
    pub async fn get_volume(&self, id: u64) -> Result<Volume> {
        #[derive(Debug, Deserialize)]
        struct GetVolumeResponse {
            volume: Volume,
        }

        let response: GetVolumeResponse = self.client.get(&format!("/volumes/{}", id)).await?;
        Ok(response.volume)
    }

    /// Get volume by name
    pub async fn get_volume_by_name(&self, name: &str) -> Result<Option<Volume>> {
        let volumes = self.list_volumes().await?;
        Ok(volumes.into_iter().find(|v| v.name == name))
    }

    /// Create a new volume
    pub async fn create_volume(&self, config: &VolumeConfig) -> Result<Volume> {
        info!("Creating volume: {} ({}GB)", config.name, config.size);

        #[derive(Debug, Serialize)]
        struct CreateRequest {
            name: String,
            size: u64,
            #[serde(skip_serializing_if = "Option::is_none")]
            location: Option<String>,
            #[serde(skip_serializing_if = "Option::is_none")]
            format: Option<String>,
            #[serde(skip_serializing_if = "Option::is_none")]
            labels: Option<std::collections::HashMap<String, String>>,
        }

        let request = CreateRequest {
            name: config.name.clone(),
            size: config.size,
            location: config.location.clone(),
            format: config.format.clone(),
            labels: config.labels.clone(),
        };

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

        // Wait for volume creation to complete
        self.client.wait_for_action(response.action.id, 120).await?;

        info!("Volume {} created successfully", config.name);
        Ok(response.volume)
    }

    /// Attach volume to a server
    pub async fn attach_to_server(
        &self,
        volume_id: u64,
        server_id: u64,
        automount: bool,
    ) -> Result<()> {
        info!("Attaching volume {} to server {}", volume_id, server_id);

        let request = AttachVolumeRequest {
            server: server_id,
            automount: Some(automount),
        };

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

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

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

        info!("Volume attached successfully");
        Ok(())
    }

    /// Detach volume from server
    pub async fn detach_from_server(&self, volume_id: u64) -> Result<()> {
        info!("Detaching volume {}", volume_id);

        #[derive(Debug, Deserialize)]
        struct DetachResponse {
            action: crate::Action,
        }

        let response: DetachResponse = self
            .client
            .post(&format!("/volumes/{}/actions/detach", volume_id), &())
            .await?;

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

        info!("Volume detached successfully");
        Ok(())
    }

    /// Resize a volume
    pub async fn resize_volume(&self, volume_id: u64, new_size: u64) -> Result<()> {
        info!("Resizing volume {} to {}GB", volume_id, new_size);

        let request = ResizeVolumeRequest { size: new_size };

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

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

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

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

    /// Delete a volume
    pub async fn delete_volume(&self, id: u64) -> Result<()> {
        info!("Deleting volume with ID: {}", id);
        self.client.delete(&format!("/volumes/{}", id)).await?;
        info!("Volume deleted successfully");
        Ok(())
    }

    /// Ensure volume exists
    pub async fn ensure_volume(&self, config: &VolumeConfig) -> Result<Volume> {
        if let Some(volume) = self.get_volume_by_name(&config.name).await? {
            info!("Volume {} already exists", config.name);
            return Ok(volume);
        }

        self.create_volume(config).await
    }
}

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

    #[test]
    fn test_volume_config_creation() {
        let config = VolumeConfig {
            name: "test-volume".to_string(),
            size: 10,
            location: Some("nbg1".to_string()),
            format: Some("ext4".to_string()),
            labels: None,
        };

        assert_eq!(config.name, "test-volume");
        assert_eq!(config.size, 10);
        assert_eq!(config.location, Some("nbg1".to_string()));
        assert_eq!(config.format, Some("ext4".to_string()));
    }

    #[test]
    fn test_volume_config_with_labels() {
        let mut labels = std::collections::HashMap::new();
        labels.insert("env".to_string(), "production".to_string());

        let config = VolumeConfig {
            name: "prod-volume".to_string(),
            size: 50,
            location: Some("fsn1".to_string()),
            format: Some("xfs".to_string()),
            labels: Some(labels.clone()),
        };

        assert!(config.labels.is_some());
        assert_eq!(
            config.labels.as_ref().unwrap().get("env"),
            Some(&"production".to_string())
        );
    }
}
