use crate::artifact_store::ArtifactStore;
use crate::config::{CephConfig, CoronaFsConfig, VolumeRuntimeConfig};
use crate::storage::VmStore;
use plasmavmc_types::{
    AttachedDisk, DiskAttachment, DiskSource, DiskSpec, VirtualMachine, Volume, VolumeBacking,
    VolumeDriverKind, VolumeFormat, VolumeStatus,
};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::net::IpAddr;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use tokio::process::Command;
use tonic::Status;
use uuid::Uuid;

const CORONAFS_IMAGE_CONVERT_PARALLELISM: &str = "16";
const AUTO_DELETE_VOLUME_METADATA_KEY: &str = "plasmavmc.auto_delete";
const AUTO_DELETE_VOLUME_SOURCE_METADATA_KEY: &str = "plasmavmc.auto_delete_source";
const CORONAFS_IMAGE_SOURCE_ID_METADATA_KEY: &str = "plasmavmc.coronafs_image_source_id";
const CORONAFS_IMAGE_SEED_PENDING_METADATA_KEY: &str = "plasmavmc.coronafs_image_seed_pending";
const VOLUME_METADATA_CAS_RETRIES: usize = 16;
const CEPH_IDENTIFIER_MAX_LEN: usize = 128;

#[derive(Clone, Debug)]
struct CephClusterConfig {
    cluster_id: String,
    monitors: Vec<String>,
    user: String,
    secret: Option<String>,
}

#[derive(Clone, Debug)]
struct CoronaFsClient {
    endpoint: String,
    endpoints: Vec<String>,
    http: reqwest::Client,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
struct CoronaFsVolumeResponse {
    id: String,
    size_bytes: u64,
    #[serde(default)]
    format: Option<CoronaFsVolumeFormat>,
    #[serde(default)]
    node_local: bool,
    #[serde(default)]
    materialized_from: Option<String>,
    path: String,
    export: Option<CoronaFsExport>,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
struct CoronaFsExport {
    uri: String,
    port: u16,
    pid: Option<u32>,
}

#[derive(Debug, Serialize)]
struct CoronaFsCreateRequest {
    size_bytes: u64,
    #[serde(skip_serializing_if = "Option::is_none")]
    format: Option<CoronaFsVolumeFormat>,
    #[serde(skip_serializing_if = "Option::is_none")]
    backing_file: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    backing_format: Option<CoronaFsVolumeFormat>,
}

#[derive(Debug, Serialize)]
struct CoronaFsResizeRequest {
    size_bytes: u64,
}

#[derive(Debug, Serialize)]
struct CoronaFsMaterializeRequest {
    source_uri: String,
    size_bytes: u64,
    #[serde(skip_serializing_if = "Option::is_none")]
    format: Option<CoronaFsVolumeFormat>,
    #[serde(default)]
    lazy: bool,
}

#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
enum CoronaFsVolumeFormat {
    Raw,
    Qcow2,
}

impl CoronaFsVolumeFormat {
    fn as_qemu_arg(self) -> &'static str {
        match self {
            Self::Raw => "raw",
            Self::Qcow2 => "qcow2",
        }
    }
}

#[derive(Debug, Deserialize)]
struct QemuImageInfo {
    format: String,
    #[serde(rename = "virtual-size")]
    virtual_size: u64,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct CoronaFsProvisionOutcome {
    format: VolumeFormat,
    deferred_image_seed: bool,
}

#[derive(Clone)]
pub struct VolumeManager {
    store: Arc<dyn VmStore>,
    artifact_store: Option<Arc<ArtifactStore>>,
    managed_root: PathBuf,
    supported_storage_classes: Vec<String>,
    qemu_img_path: PathBuf,
    ceph_cluster: Option<CephClusterConfig>,
    coronafs_controller: Option<CoronaFsClient>,
    coronafs_node: Option<CoronaFsClient>,
    coronafs_node_local_attach: bool,
    local_node_id: Option<String>,
}

impl VolumeManager {
    pub fn new_with_config(
        store: Arc<dyn VmStore>,
        artifact_store: Option<Arc<ArtifactStore>>,
        config: &VolumeRuntimeConfig,
        local_node_id: Option<String>,
    ) -> Result<Self, Box<dyn std::error::Error>> {
        let managed_root = config.managed_volume_root.clone();
        let ceph_cluster = ceph_cluster_from_config(&config.ceph);
        let (coronafs_controller, coronafs_node) =
            resolve_coronafs_clients_with_config(&config.coronafs);
        let coronafs_node_local_attach = config.coronafs.node_local_attach;
        let qemu_img_path = resolve_binary_path(config.qemu_img_path.as_deref(), "qemu-img")?;

        Ok(Self::build(
            store,
            artifact_store,
            managed_root,
            qemu_img_path,
            ceph_cluster,
            coronafs_controller,
            coronafs_node,
            coronafs_node_local_attach,
            local_node_id,
        ))
    }

    fn build(
        store: Arc<dyn VmStore>,
        artifact_store: Option<Arc<ArtifactStore>>,
        managed_root: PathBuf,
        qemu_img_path: PathBuf,
        ceph_cluster: Option<CephClusterConfig>,
        coronafs_controller: Option<CoronaFsClient>,
        coronafs_node: Option<CoronaFsClient>,
        coronafs_node_local_attach: bool,
        local_node_id: Option<String>,
    ) -> Self {
        Self {
            store,
            artifact_store,
            managed_root,
            supported_storage_classes: {
                let mut classes = vec!["managed-default".to_string()];
                if coronafs_controller.is_some() || coronafs_node.is_some() {
                    classes.push("coronafs-managed".to_string());
                }
                classes.push("ceph-rbd".to_string());
                classes
            },
            qemu_img_path,
            ceph_cluster,
            coronafs_controller,
            coronafs_node,
            coronafs_node_local_attach,
            local_node_id,
        }
    }

    pub fn supported_volume_drivers(&self) -> Vec<VolumeDriverKind> {
        let mut drivers = vec![VolumeDriverKind::Managed];
        if self.ceph_cluster.is_some() {
            drivers.push(VolumeDriverKind::CephRbd);
        }
        drivers
    }

    pub fn supported_storage_classes(&self) -> Vec<String> {
        self.supported_storage_classes.clone()
    }

    fn coronafs_provisioner(&self) -> Option<&CoronaFsClient> {
        self.coronafs_controller
            .as_ref()
            .or(self.coronafs_node.as_ref())
    }

    fn coronafs_attachment_backend(&self) -> Option<&CoronaFsClient> {
        self.coronafs_node
            .as_ref()
            .or(self.coronafs_controller.as_ref())
    }

    async fn load_coronafs_volume_for_attachment(
        &self,
        volume: &Volume,
    ) -> Result<(CoronaFsVolumeResponse, CoronaFsClient), Status> {
        let volume_id = volume.id.as_str();
        if let Some(node_client) = &self.coronafs_node {
            if let Some(controller) = self
                .coronafs_controller
                .as_ref()
                .filter(|controller| !controller.shares_endpoint_with(node_client))
            {
                if !self.coronafs_node_local_attach {
                    if let Some(local_volume) = node_client.get_volume_optional(volume_id).await? {
                        tracing::warn!(
                            volume_id,
                            node_endpoint = %node_client.endpoint,
                            local_path = %local_volume.path,
                            "Discarding stale node-local CoronaFS volume because experimental writable local attach is disabled"
                        );
                        if let Err(error) = node_client.delete_volume(volume_id).await {
                            tracing::warn!(
                                volume_id,
                                endpoint = %node_client.endpoint,
                                error = %error,
                                "Failed to delete stale node-local CoronaFS volume while falling back to controller-backed attachment"
                            );
                        }
                    }
                    let volume = controller
                        .get_volume_optional(volume_id)
                        .await?
                        .ok_or_else(|| {
                            Status::not_found(format!("CoronaFS volume {volume_id} not found"))
                        })?;
                    return Ok((volume, controller.clone()));
                }

                match node_client.get_volume_optional(volume_id).await? {
                    Some(volume) => return Ok((volume, node_client.clone())),
                    None => {
                        if volume_has_pending_coronafs_image_seed(volume) {
                            if let Some(local_seeded_volume) = self
                                .materialize_pending_coronafs_image_seed_on_node(
                                    volume,
                                    node_client,
                                )
                                .await?
                            {
                                return Ok((local_seeded_volume, node_client.clone()));
                            }
                            let image_id =
                                volume_coronafs_image_source_id(volume).ok_or_else(|| {
                                    Status::failed_precondition(format!(
                                    "volume {volume_id} is missing CoronaFS image seed metadata"
                                ))
                                })?;
                            tracing::warn!(
                                volume_id,
                                image_id,
                                controller_endpoint = %controller.endpoint,
                                node_endpoint = %node_client.endpoint,
                                "Falling back to eager controller-side CoronaFS image seed"
                            );
                            self.clone_image_into_coronafs(
                                volume_id,
                                &volume.org_id,
                                &volume.project_id,
                                image_id,
                                volume.size_gib,
                                false,
                            )
                            .await?;
                            self.clear_pending_coronafs_image_seed(volume).await?;
                        }
                        let controller_volume = controller
                            .get_volume_optional(volume_id)
                            .await?
                            .ok_or_else(|| {
                                Status::not_found(format!("CoronaFS volume {volume_id} not found"))
                            })?;
                        let local_volume = self
                            .materialize_coronafs_volume_on_node(
                                volume_id,
                                node_client,
                                controller,
                                &controller_volume,
                            )
                            .await?;
                        return Ok((local_volume, node_client.clone()));
                    }
                }
            }

            if let Some(volume) = node_client.get_volume_optional(volume_id).await? {
                return Ok((volume, node_client.clone()));
            }
        }

        let controller = self
            .coronafs_controller
            .as_ref()
            .or(self.coronafs_node.as_ref())
            .ok_or_else(|| Status::failed_precondition("coronafs backend is not configured"))?;
        let volume = controller
            .get_volume_optional(volume_id)
            .await?
            .ok_or_else(|| Status::not_found(format!("CoronaFS volume {volume_id} not found")))?;
        Ok((volume, controller.clone()))
    }

    async fn materialize_coronafs_volume_on_node(
        &self,
        volume_id: &str,
        node_client: &CoronaFsClient,
        controller: &CoronaFsClient,
        controller_volume: &CoronaFsVolumeResponse,
    ) -> Result<CoronaFsVolumeResponse, Status> {
        tracing::info!(
            volume_id,
            node_endpoint = %node_client.endpoint,
            controller_endpoint = %controller.endpoint,
            "Materializing CoronaFS volume on node-local endpoint"
        );
        let export = match controller_volume.export.clone() {
            Some(export) => export,
            None => controller.ensure_export_read_only(volume_id).await?,
        };
        validate_coronafs_export(&export)?;
        node_client
            .materialize_from_export(
                volume_id,
                &export.uri,
                controller_volume.size_bytes,
                Some(CoronaFsVolumeFormat::Qcow2),
                true,
            )
            .await
    }

    pub async fn create_managed_volume(
        &self,
        org_id: &str,
        project_id: &str,
        name: &str,
        size_gib: u64,
        storage_class: Option<&str>,
        image_id: Option<&str>,
        metadata: std::collections::HashMap<String, String>,
        labels: std::collections::HashMap<String, String>,
    ) -> Result<Volume, Status> {
        let volume_id = Uuid::new_v4().to_string();
        self.create_managed_volume_with_id(
            &volume_id,
            org_id,
            project_id,
            name,
            size_gib,
            storage_class,
            image_id,
            metadata,
            labels,
        )
        .await
    }

    pub async fn create_managed_volume_with_id(
        &self,
        volume_id: &str,
        org_id: &str,
        project_id: &str,
        name: &str,
        size_gib: u64,
        storage_class: Option<&str>,
        image_id: Option<&str>,
        metadata: std::collections::HashMap<String, String>,
        labels: std::collections::HashMap<String, String>,
    ) -> Result<Volume, Status> {
        if let Some(existing) = self
            .store
            .load_volume(org_id, project_id, volume_id)
            .await
            .map_err(to_status)?
        {
            return Ok(existing);
        }
        if let Some(image_id) = image_id {
            validate_uuid(image_id, "image_id")?;
        }

        let path = self.managed_volume_path(volume_id);
        let mut metadata = metadata;
        let provision_result: Result<CoronaFsProvisionOutcome, Status> =
            if let Some(image_id) = image_id {
                if self.coronafs_provisioner().is_some() {
                    self.clone_image_into_coronafs(
                        volume_id, org_id, project_id, image_id, size_gib, true,
                    )
                    .await
                } else {
                    self.clone_image_into_managed(org_id, project_id, image_id, &path)
                        .await
                        .map(|format| CoronaFsProvisionOutcome {
                            format,
                            deferred_image_seed: false,
                        })
                }
            } else {
                if let Some(coronafs) = self.coronafs_provisioner() {
                    coronafs
                        .create_blank(volume_id, gib_to_bytes(size_gib))
                        .await
                        .map(|_| CoronaFsProvisionOutcome {
                            format: VolumeFormat::Raw,
                            deferred_image_seed: false,
                        })
                } else {
                    self.create_blank_managed(&path, size_gib, VolumeFormat::Raw)
                        .await
                        .map(|format| CoronaFsProvisionOutcome {
                            format,
                            deferred_image_seed: false,
                        })
                }
            };
        let provisioned = match provision_result {
            Ok(outcome) => outcome,
            Err(error) => {
                self.cleanup_partial_managed_volume(volume_id, &path).await;
                return Err(error);
            }
        };
        if let Some(image_id) = image_id.filter(|_| provisioned.deferred_image_seed) {
            metadata.insert(
                CORONAFS_IMAGE_SOURCE_ID_METADATA_KEY.to_string(),
                image_id.to_string(),
            );
            metadata.insert(
                CORONAFS_IMAGE_SEED_PENDING_METADATA_KEY.to_string(),
                "true".to_string(),
            );
        }

        let mut volume = Volume::new(
            volume_id.to_string(),
            name.to_string(),
            org_id,
            project_id,
            size_gib,
        );
        volume.driver = VolumeDriverKind::Managed;
        volume.storage_class = storage_class
            .map(ToOwned::to_owned)
            .unwrap_or_else(|| self.default_managed_storage_class());
        volume.format = provisioned.format;
        volume.status = VolumeStatus::Available;
        volume.metadata = metadata;
        volume.labels = labels;
        self.store.save_volume(&volume).await.map_err(to_status)?;
        Ok(volume)
    }

    async fn cleanup_partial_managed_volume(&self, volume_id: &str, path: &Path) {
        if let Some(coronafs) = self.coronafs_provisioner() {
            if let Err(error) = coronafs.delete_volume(volume_id).await {
                tracing::warn!(
                    volume_id,
                    error = %error,
                    "Failed to clean up partial CoronaFS volume after provisioning error"
                );
            }
            return;
        }

        match tokio::fs::try_exists(path).await {
            Ok(true) => {
                if let Err(error) = tokio::fs::remove_file(path).await {
                    tracing::warn!(
                        volume_id,
                        path = %path.display(),
                        error = %error,
                        "Failed to remove partial managed volume after provisioning error"
                    );
                }
            }
            Ok(false) => {}
            Err(error) => {
                tracing::warn!(
                    volume_id,
                    path = %path.display(),
                    error = %error,
                    "Failed to inspect partial managed volume after provisioning error"
                );
            }
        }
    }

    pub async fn register_external_ceph_volume(
        &self,
        org_id: &str,
        project_id: &str,
        name: &str,
        size_gib: u64,
        storage_class: Option<&str>,
        cluster_id: &str,
        pool: &str,
        image: &str,
        metadata: std::collections::HashMap<String, String>,
        labels: std::collections::HashMap<String, String>,
    ) -> Result<Volume, Status> {
        if self.ceph_cluster.is_none() {
            return Err(Status::failed_precondition(
                "Ceph RBD support is not configured on this node",
            ));
        }
        validate_ceph_identifier("ceph_rbd.cluster_id", cluster_id)?;
        validate_ceph_identifier("ceph_rbd.pool", pool)?;
        validate_ceph_identifier("ceph_rbd.image", image)?;
        let volume_id = Uuid::new_v4().to_string();
        let mut volume = Volume::new(volume_id, name.to_string(), org_id, project_id, size_gib);
        volume.driver = VolumeDriverKind::CephRbd;
        volume.storage_class = storage_class.unwrap_or("ceph-rbd").to_string();
        volume.format = VolumeFormat::Raw;
        volume.status = VolumeStatus::Available;
        volume.backing = VolumeBacking::CephRbd {
            cluster_id: cluster_id.to_string(),
            pool: pool.to_string(),
            image: image.to_string(),
        };
        volume.metadata = metadata;
        volume.labels = labels;
        self.store.save_volume(&volume).await.map_err(to_status)?;
        Ok(volume)
    }

    pub async fn get_volume(
        &self,
        org_id: &str,
        project_id: &str,
        volume_id: &str,
    ) -> Result<Option<Volume>, Status> {
        self.store
            .load_volume(org_id, project_id, volume_id)
            .await
            .map_err(to_status)
    }

    pub async fn list_volumes(
        &self,
        org_id: &str,
        project_id: &str,
    ) -> Result<Vec<Volume>, Status> {
        self.store
            .list_volumes(org_id, project_id)
            .await
            .map_err(to_status)
    }

    pub async fn delete_volume(
        &self,
        org_id: &str,
        project_id: &str,
        volume_id: &str,
    ) -> Result<(), Status> {
        let Some(volume) = self.get_volume(org_id, project_id, volume_id).await? else {
            return Ok(());
        };
        if volume.attached_to_vm.is_some() {
            return Err(Status::failed_precondition(
                "volume is still attached to a VM",
            ));
        }
        if matches!(volume.backing, VolumeBacking::Managed) {
            if let Some(coronafs) = self.coronafs_provisioner() {
                coronafs.delete_volume(volume_id).await?;
                if let Some(node_client) = self
                    .coronafs_node
                    .as_ref()
                    .filter(|node_client| !node_client.shares_endpoint_with(coronafs))
                {
                    if let Err(error) = node_client.delete_volume(volume_id).await {
                        tracing::warn!(
                            volume_id,
                            endpoint = %node_client.endpoint,
                            error = %error,
                            "Failed to remove node-local CoronaFS replica while deleting managed volume"
                        );
                    }
                }
            } else {
                let path = self.managed_volume_path(volume_id);
                if tokio::fs::try_exists(&path).await.unwrap_or(false) {
                    tokio::fs::remove_file(&path).await.map_err(|e| {
                        Status::internal(format!("failed to remove volume data: {e}"))
                    })?;
                }
            }
        }
        self.store
            .delete_volume(org_id, project_id, volume_id)
            .await
            .map_err(to_status)
    }

    pub async fn resize_volume(
        &self,
        org_id: &str,
        project_id: &str,
        volume_id: &str,
        size_gib: u64,
    ) -> Result<Volume, Status> {
        let mut volume = self
            .get_volume(org_id, project_id, volume_id)
            .await?
            .ok_or_else(|| Status::not_found("volume not found"))?;
        if matches!(volume.backing, VolumeBacking::Managed) {
            if let Some(coronafs) = self.coronafs_provisioner() {
                coronafs
                    .resize_volume(volume_id, gib_to_bytes(size_gib))
                    .await?;
            } else {
                self.resize_managed(
                    &self.managed_volume_path(volume_id),
                    volume.format,
                    size_gib,
                )
                .await?;
            }
        }
        volume.size_gib = size_gib;
        volume.updated_at = now_epoch();
        self.store.save_volume(&volume).await.map_err(to_status)?;
        Ok(volume)
    }

    pub async fn prepare_vm_volumes(
        &self,
        vm: &mut VirtualMachine,
    ) -> Result<Vec<AttachedDisk>, Status> {
        let original_disks = vm.spec.disks.clone();
        let vm_id = vm.id.to_string();
        let result: Result<Vec<AttachedDisk>, Status> = async {
            let mut attached = Vec::with_capacity(vm.spec.disks.len());
            for disk in &mut vm.spec.disks {
                match &disk.source {
                    DiskSource::Image { image_id } => {
                        let volume_id = derived_volume_id(&vm_id, &disk.id);
                        let mut metadata = std::collections::HashMap::new();
                        metadata.insert(
                            AUTO_DELETE_VOLUME_METADATA_KEY.to_string(),
                            "true".to_string(),
                        );
                        metadata.insert(
                            AUTO_DELETE_VOLUME_SOURCE_METADATA_KEY.to_string(),
                            "image".to_string(),
                        );
                        let volume = self
                            .create_managed_volume_with_id(
                                &volume_id,
                                &vm.org_id,
                                &vm.project_id,
                                &format!("{}-{}", vm.name, disk.id),
                                disk.size_gib,
                                Some("managed-default"),
                                Some(image_id),
                                metadata,
                                std::collections::HashMap::new(),
                            )
                            .await?;
                        disk.source = DiskSource::Volume {
                            volume_id: volume.id.clone(),
                        };
                        attached.push(self.attach_volume_to_vm(&volume, &vm_id, disk).await?);
                    }
                    DiskSource::Blank => {
                        let volume_id = derived_volume_id(&vm_id, &disk.id);
                        let mut metadata = std::collections::HashMap::new();
                        metadata.insert(
                            AUTO_DELETE_VOLUME_METADATA_KEY.to_string(),
                            "true".to_string(),
                        );
                        metadata.insert(
                            AUTO_DELETE_VOLUME_SOURCE_METADATA_KEY.to_string(),
                            "blank".to_string(),
                        );
                        let volume = self
                            .create_managed_volume_with_id(
                                &volume_id,
                                &vm.org_id,
                                &vm.project_id,
                                &format!("{}-{}", vm.name, disk.id),
                                disk.size_gib,
                                Some("managed-default"),
                                None,
                                metadata,
                                std::collections::HashMap::new(),
                            )
                            .await?;
                        disk.source = DiskSource::Volume {
                            volume_id: volume.id.clone(),
                        };
                        attached.push(self.attach_volume_to_vm(&volume, &vm_id, disk).await?);
                    }
                    DiskSource::Volume { volume_id } => {
                        let volume = self
                            .get_volume(&vm.org_id, &vm.project_id, volume_id)
                            .await?
                            .ok_or_else(|| {
                                Status::not_found(format!("volume {volume_id} not found"))
                            })?;
                        attached.push(self.attach_volume_to_vm(&volume, &vm_id, disk).await?);
                    }
                }
            }
            Ok(attached)
        }
        .await;

        if let Err(error) = result {
            if let Err(rollback_error) = self.rollback_vm_volumes(vm, true).await {
                tracing::warn!(
                    vm_id = %vm.id,
                    error = %rollback_error,
                    "Failed to roll back partial volume preparation"
                );
            }
            vm.spec.disks = original_disks;
            return Err(error);
        }

        result
    }

    pub async fn delete_vm_managed_volumes(&self, vm: &VirtualMachine) -> Result<(), Status> {
        for disk in &vm.spec.disks {
            let DiskSource::Volume { volume_id } = &disk.source else {
                continue;
            };
            let Some(volume) = self
                .get_volume(&vm.org_id, &vm.project_id, volume_id)
                .await?
            else {
                continue;
            };
            if !volume_is_auto_delete(&volume) {
                continue;
            }
            self.delete_volume(&vm.org_id, &vm.project_id, volume_id)
                .await?;
        }
        Ok(())
    }

    pub async fn rollback_vm_volumes(
        &self,
        vm: &VirtualMachine,
        delete_auto_delete: bool,
    ) -> Result<(), Status> {
        self.release_vm_volumes(vm).await?;
        if delete_auto_delete {
            self.delete_vm_managed_volumes(vm).await?;
        }
        Ok(())
    }

    pub async fn release_vm_volumes(&self, vm: &VirtualMachine) -> Result<(), Status> {
        for disk in &vm.spec.disks {
            let DiskSource::Volume { volume_id } = &disk.source else {
                continue;
            };
            self.release_volume_attachment(
                &vm.org_id,
                &vm.project_id,
                volume_id,
                &vm.id.to_string(),
            )
            .await?;
        }
        Ok(())
    }

    pub async fn flush_vm_volumes(&self, vm: &VirtualMachine) -> Result<(), Status> {
        for disk in &vm.spec.disks {
            let DiskSource::Volume { volume_id } = &disk.source else {
                continue;
            };
            self.flush_volume_attachment(&vm.org_id, &vm.project_id, volume_id, &vm.id.to_string())
                .await?;
        }
        Ok(())
    }

    pub async fn flush_volume_attachment(
        &self,
        org_id: &str,
        project_id: &str,
        volume_id: &str,
        vm_id: &str,
    ) -> Result<(), Status> {
        let Some(mut volume) = self
            .store
            .load_volume(org_id, project_id, volume_id)
            .await
            .map_err(to_status)?
        else {
            return Ok(());
        };
        if volume.attached_to_vm.as_deref() != Some(vm_id) {
            return Ok(());
        }
        if !matches!(volume.backing, VolumeBacking::Managed) {
            return Ok(());
        }
        self.sync_node_local_coronafs_volume_to_controller(volume_id)
            .await?;
        if volume.last_flushed_attachment_generation != volume.attachment_generation
            || volume_has_pending_coronafs_image_seed(&volume)
        {
            for _ in 0..VOLUME_METADATA_CAS_RETRIES {
                let mut updated = volume.clone();
                updated.last_flushed_attachment_generation = updated.attachment_generation;
                updated
                    .metadata
                    .remove(CORONAFS_IMAGE_SEED_PENDING_METADATA_KEY);
                updated.updated_at = now_epoch();
                if self
                    .store
                    .compare_and_swap_volume(Some(&volume), &updated)
                    .await
                    .map_err(to_status)?
                {
                    return Ok(());
                }
                let Some(current) = self
                    .store
                    .load_volume(org_id, project_id, volume_id)
                    .await
                    .map_err(to_status)?
                else {
                    return Ok(());
                };
                if current.attached_to_vm.as_deref() != Some(vm_id)
                    || !matches!(current.backing, VolumeBacking::Managed)
                    || current.last_flushed_attachment_generation == current.attachment_generation
                        && !volume_has_pending_coronafs_image_seed(&current)
                {
                    return Ok(());
                }
                volume = current;
            }
            return Err(Status::aborted(format!(
                "volume {} metadata was modified concurrently while recording flush state",
                volume_id
            )));
        }
        Ok(())
    }

    pub async fn release_volume_attachment(
        &self,
        org_id: &str,
        project_id: &str,
        volume_id: &str,
        vm_id: &str,
    ) -> Result<(), Status> {
        let Some(mut volume) = self
            .store
            .load_volume(org_id, project_id, volume_id)
            .await
            .map_err(to_status)?
        else {
            return Ok(());
        };
        if volume.attached_to_vm.as_deref() == Some(vm_id) {
            if matches!(volume.backing, VolumeBacking::Managed)
                && volume.last_flushed_attachment_generation != volume.attachment_generation
            {
                self.sync_node_local_coronafs_volume_to_controller(volume_id)
                    .await?;
                volume.last_flushed_attachment_generation = volume.attachment_generation;
            }
            let mut detached = volume.clone();
            detached.attached_to_vm = None;
            detached.attached_to_node = None;
            detached.status = VolumeStatus::Available;
            detached
                .metadata
                .remove(CORONAFS_IMAGE_SEED_PENDING_METADATA_KEY);
            detached.updated_at = now_epoch();
            let mut detached_saved = false;
            for _ in 0..VOLUME_METADATA_CAS_RETRIES {
                if self
                    .store
                    .compare_and_swap_volume(Some(&volume), &detached)
                    .await
                    .map_err(to_status)?
                {
                    detached_saved = true;
                    break;
                }
                let Some(current) = self
                    .store
                    .load_volume(org_id, project_id, volume_id)
                    .await
                    .map_err(to_status)?
                else {
                    return Ok(());
                };
                if current.attached_to_vm.as_deref() != Some(vm_id) {
                    return Ok(());
                }
                volume = current.clone();
                detached = current;
                detached.attached_to_vm = None;
                detached.attached_to_node = None;
                detached.status = VolumeStatus::Available;
                detached.updated_at = now_epoch();
            }
            if !detached_saved {
                return Err(Status::aborted(format!(
                    "volume {} metadata was modified concurrently while releasing attachment",
                    volume_id
                )));
            }
            if let (Some(node_client), Some(controller)) =
                (&self.coronafs_node, &self.coronafs_controller)
            {
                if !controller.shares_endpoint_with(node_client) {
                    if let Err(error) = node_client.delete_volume(volume_id).await {
                        tracing::warn!(
                            volume_id,
                            endpoint = %node_client.endpoint,
                            error = %error,
                            "Failed to remove node-local CoronaFS volume while releasing attachment"
                        );
                    }
                }
            }
        }
        Ok(())
    }

    async fn sync_node_local_coronafs_volume_to_controller(
        &self,
        volume_id: &str,
    ) -> Result<(), Status> {
        let (Some(node_client), Some(controller)) =
            (&self.coronafs_node, &self.coronafs_controller)
        else {
            return Ok(());
        };
        if controller.shares_endpoint_with(node_client) {
            return Ok(());
        }

        let Some(local_volume) = node_client.get_volume_optional(volume_id).await? else {
            return Ok(());
        };
        validate_coronafs_volume_response(&local_volume)?;
        if local_volume.export.is_some() {
            node_client.release_export(volume_id).await?;
        }

        let controller_volume = match controller.get_volume_optional(volume_id).await? {
            Some(volume) => {
                if volume.size_bytes < local_volume.size_bytes {
                    controller
                        .resize_volume(volume_id, local_volume.size_bytes)
                        .await?;
                    controller
                        .get_volume_optional(volume_id)
                        .await?
                        .ok_or_else(|| {
                            Status::not_found(format!(
                                "CoronaFS controller volume {volume_id} disappeared after resize"
                            ))
                        })?
                } else {
                    volume
                }
            }
            None => {
                controller
                    .create_blank(volume_id, local_volume.size_bytes)
                    .await?
            }
        };
        let export = match controller_volume.export {
            Some(export) => export,
            None => controller.ensure_export(volume_id).await?,
        };
        validate_coronafs_export(&export)?;
        tracing::info!(
            volume_id,
            node_endpoint = %node_client.endpoint,
            controller_endpoint = %controller.endpoint,
            local_path = %local_volume.path,
            export_uri = %export.uri,
            "Syncing node-local CoronaFS volume back to controller"
        );
        sync_local_coronafs_volume_to_export(&self.qemu_img_path, &local_volume, &export.uri).await
    }

    async fn materialize_pending_coronafs_image_seed_on_node(
        &self,
        volume: &Volume,
        node_client: &CoronaFsClient,
    ) -> Result<Option<CoronaFsVolumeResponse>, Status> {
        if !volume_has_pending_coronafs_image_seed(volume) {
            return Ok(None);
        }
        let Some(image_id) = volume_coronafs_image_source_id(volume) else {
            return Ok(None);
        };
        if !node_client.supports_local_backing_file().await {
            return Ok(None);
        }
        let Some(artifact_store) = &self.artifact_store else {
            tracing::warn!(
                volume_id = volume.id,
                image_id,
                "Cannot materialize pending CoronaFS image seed on node because artifact storage is unavailable"
            );
            return Ok(None);
        };
        let raw_image_path = artifact_store
            .materialize_raw_image_cache(&volume.org_id, &volume.project_id, image_id)
            .await?;
        let requested_size = gib_to_bytes(volume.size_gib);
        match node_client
            .create_image_backed(
                &volume.id,
                requested_size,
                &raw_image_path,
                CoronaFsVolumeFormat::Raw,
            )
            .await
        {
            Ok(local_volume) => {
                tracing::info!(
                    volume_id = volume.id,
                    image_id,
                    node_endpoint = %node_client.endpoint,
                    raw_image_path = %raw_image_path.display(),
                    requested_size,
                    volume_path = %local_volume.path,
                    "Provisioned pending CoronaFS image seed directly on node-local endpoint"
                );
                Ok(Some(local_volume))
            }
            Err(error) => {
                tracing::warn!(
                    volume_id = volume.id,
                    image_id,
                    node_endpoint = %node_client.endpoint,
                    raw_image_path = %raw_image_path.display(),
                    error = %error,
                    "Failed to provision pending CoronaFS image seed on node-local endpoint"
                );
                Ok(None)
            }
        }
    }

    async fn clear_pending_coronafs_image_seed(&self, volume: &Volume) -> Result<(), Status> {
        if !volume_has_pending_coronafs_image_seed(volume) {
            return Ok(());
        }
        let mut current = volume.clone();
        for _ in 0..VOLUME_METADATA_CAS_RETRIES {
            if !volume_has_pending_coronafs_image_seed(&current) {
                return Ok(());
            }
            let mut updated = current.clone();
            updated
                .metadata
                .remove(CORONAFS_IMAGE_SEED_PENDING_METADATA_KEY);
            updated.updated_at = now_epoch();
            if self
                .store
                .compare_and_swap_volume(Some(&current), &updated)
                .await
                .map_err(to_status)?
            {
                return Ok(());
            }
            let Some(next) = self
                .store
                .load_volume(&current.org_id, &current.project_id, &current.id)
                .await
                .map_err(to_status)?
            else {
                return Ok(());
            };
            current = next;
        }
        Err(Status::aborted(format!(
            "volume {} metadata was modified concurrently while clearing pending CoronaFS image seed",
            volume.id
        )))
    }

    pub async fn supports_cold_relocation_for_vm(
        &self,
        vm: &VirtualMachine,
    ) -> Result<bool, Status> {
        for disk in &vm.spec.disks {
            match &disk.source {
                DiskSource::Volume { volume_id } => {
                    let volume = self
                        .get_volume(&vm.org_id, &vm.project_id, volume_id)
                        .await?
                        .ok_or_else(|| {
                            Status::not_found(format!("volume {volume_id} not found"))
                        })?;
                    match &volume.backing {
                        VolumeBacking::Managed => {
                            if self.coronafs_provisioner().is_none() {
                                return Ok(false);
                            }
                        }
                        VolumeBacking::CephRbd { .. } => {}
                    }
                }
                DiskSource::Image { .. } | DiskSource::Blank => {
                    if self.coronafs_provisioner().is_none() {
                        return Ok(false);
                    }
                }
            }
        }
        Ok(true)
    }

    async fn attach_volume_to_vm(
        &self,
        volume: &Volume,
        vm_id: &str,
        disk: &DiskSpec,
    ) -> Result<AttachedDisk, Status> {
        let mut current = volume.clone();
        for _ in 0..VOLUME_METADATA_CAS_RETRIES {
            if let Some(attached_to_vm) = current.attached_to_vm.as_deref() {
                if attached_to_vm != vm_id {
                    return Err(Status::failed_precondition(format!(
                        "volume {} is already attached to VM {}",
                        current.id, attached_to_vm
                    )));
                }
            }
            if current.attached_to_vm.as_deref() == Some(vm_id)
                && current.attached_to_node.is_some()
                && current.attached_to_node != self.local_node_id
            {
                return Err(Status::failed_precondition(format!(
                    "volume {} writer lease is still held by node {}",
                    current.id,
                    current.attached_to_node.as_deref().unwrap_or("unknown")
                )));
            }

            let mut updated = current.clone();
            let attachment_changed = current.attached_to_vm.as_deref() != Some(vm_id)
                || current.attached_to_node != self.local_node_id;
            updated.attached_to_vm = Some(vm_id.to_string());
            updated.attached_to_node = self.local_node_id.clone();
            if attachment_changed {
                updated.attachment_generation = updated.attachment_generation.saturating_add(1);
            }
            updated.status = VolumeStatus::InUse;
            updated.updated_at = now_epoch();
            if self
                .store
                .compare_and_swap_volume(Some(&current), &updated)
                .await
                .map_err(to_status)?
            {
                return self.attachment_from_volume(&updated, disk).await;
            }
            current = self
                .store
                .load_volume(&current.org_id, &current.project_id, &current.id)
                .await
                .map_err(to_status)?
                .ok_or_else(|| Status::not_found(format!("volume {} not found", current.id)))?;
        }
        Err(Status::aborted(format!(
            "volume {} metadata was modified concurrently while attaching to VM {}",
            volume.id, vm_id
        )))
    }

    async fn attachment_from_volume(
        &self,
        volume: &Volume,
        disk: &DiskSpec,
    ) -> Result<AttachedDisk, Status> {
        let attachment = match &volume.backing {
            VolumeBacking::Managed => {
                if self.coronafs_attachment_backend().is_some() {
                    let (coronafs_volume, coronafs) =
                        self.load_coronafs_volume_for_attachment(volume).await?;
                    validate_coronafs_volume_response(&coronafs_volume)?;
                    if coronafs.supports_local_backing_file().await
                        && !should_prefer_coronafs_export_attachment(&coronafs_volume)
                        && coronafs_local_target_ready(&coronafs_volume.path).await
                    {
                        DiskAttachment::File {
                            path: coronafs_volume.path,
                            format: volume.format,
                        }
                    } else {
                        let export = match coronafs_volume.export {
                            Some(export) => export,
                            None => coronafs.ensure_export(&volume.id).await?,
                        };
                        DiskAttachment::Nbd {
                            uri: export.uri,
                            // CoronaFS exports present a block device regardless of the
                            // underlying on-disk format of the node-local backing file.
                            format: VolumeFormat::Raw,
                        }
                    }
                } else {
                    DiskAttachment::File {
                        path: self.managed_volume_path(&volume.id).display().to_string(),
                        format: volume.format,
                    }
                }
            }
            VolumeBacking::CephRbd {
                cluster_id,
                pool,
                image,
            } => {
                let ceph = self.ceph_cluster.as_ref().ok_or_else(|| {
                    Status::failed_precondition("Ceph RBD backend is not configured")
                })?;
                if ceph.cluster_id != *cluster_id {
                    return Err(Status::failed_precondition(format!(
                        "Ceph cluster {} is not configured on this node",
                        cluster_id
                    )));
                }
                validate_ceph_identifier("ceph cluster_id", cluster_id)?;
                validate_ceph_identifier("ceph pool", pool)?;
                validate_ceph_identifier("ceph image", image)?;
                DiskAttachment::CephRbd {
                    pool: pool.clone(),
                    image: image.clone(),
                    monitors: ceph.monitors.clone(),
                    user: ceph.user.clone(),
                    secret: ceph.secret.clone(),
                }
            }
        };
        Ok(AttachedDisk {
            id: disk.id.clone(),
            attachment,
            bus: disk.bus,
            cache: disk.cache,
            boot_index: disk.boot_index,
            read_only: false,
        })
    }

    async fn clone_image_into_managed(
        &self,
        org_id: &str,
        project_id: &str,
        image_id: &str,
        target: &Path,
    ) -> Result<VolumeFormat, Status> {
        if tokio::fs::try_exists(target).await.unwrap_or(false) {
            return Ok(VolumeFormat::Raw);
        }
        let artifact_store = self.artifact_store.as_ref().ok_or_else(|| {
            Status::failed_precondition("image-backed volumes require artifact storage")
        })?;
        let image_path = artifact_store
            .materialize_image_cache(org_id, project_id, image_id)
            .await?;
        if let Some(parent) = target.parent() {
            tokio::fs::create_dir_all(parent)
                .await
                .map_err(|e| Status::internal(format!("failed to create volume dir: {e}")))?;
        }
        let status = Command::new(&self.qemu_img_path)
            .args([
                "convert",
                "-O",
                "raw",
                image_path.to_string_lossy().as_ref(),
                target.to_string_lossy().as_ref(),
            ])
            .status()
            .await
            .map_err(|e| Status::internal(format!("failed to spawn qemu-img convert: {e}")))?;
        if status.success() {
            Ok(VolumeFormat::Raw)
        } else {
            Err(Status::internal(format!(
                "qemu-img convert failed for {} with status {status}",
                image_path.display()
            )))
        }
    }

    async fn clone_image_into_coronafs(
        &self,
        volume_id: &str,
        org_id: &str,
        project_id: &str,
        image_id: &str,
        size_gib: u64,
        allow_deferred_image_seed: bool,
    ) -> Result<CoronaFsProvisionOutcome, Status> {
        let artifact_store = self.artifact_store.as_ref().ok_or_else(|| {
            Status::failed_precondition("image-backed volumes require artifact storage")
        })?;
        let coronafs = self
            .coronafs_provisioner()
            .ok_or_else(|| Status::failed_precondition("coronafs backend is not configured"))?;
        let attachment_backend = self.coronafs_attachment_backend().unwrap_or(coronafs);
        let image_path = artifact_store
            .materialize_image_cache(org_id, project_id, image_id)
            .await?;
        let requested_size = gib_to_bytes(size_gib);
        let image_info = inspect_qemu_image(&self.qemu_img_path, &image_path).await?;
        if requested_size < image_info.virtual_size {
            return Err(Status::failed_precondition(format!(
                "requested volume {} GiB is smaller than image virtual size {} bytes",
                size_gib, image_info.virtual_size
            )));
        }

        if allow_deferred_image_seed
            && self.coronafs_node_local_attach
            && !coronafs.shares_endpoint_with(attachment_backend)
            && attachment_backend.supports_local_backing_file().await
        {
            let volume = coronafs.create_blank(volume_id, requested_size).await?;
            tracing::info!(
                volume_id,
                image_id,
                controller_endpoint = %coronafs.endpoint,
                attachment_endpoint = %attachment_backend.endpoint,
                requested_size,
                volume_path = %volume.path,
                "Provisioned blank CoronaFS controller volume and deferred image seed to node-local attach"
            );
            return Ok(CoronaFsProvisionOutcome {
                format: VolumeFormat::Qcow2,
                deferred_image_seed: true,
            });
        }

        let mut raw_image_path: Option<PathBuf> = None;
        let supports_local_coronafs_path = coronafs.shares_endpoint_with(attachment_backend)
            && coronafs.supports_local_backing_file().await;
        if supports_local_coronafs_path {
            let materialized_raw = artifact_store
                .materialize_raw_image_cache(org_id, project_id, image_id)
                .await?;
            match coronafs
                .create_image_backed(
                    volume_id,
                    requested_size,
                    &materialized_raw,
                    CoronaFsVolumeFormat::Raw,
                )
                .await
            {
                Ok(volume) => {
                    tracing::info!(
                        volume_id,
                        image_id,
                        raw_image_path = %materialized_raw.display(),
                        requested_size,
                        volume_path = %volume.path,
                        format = ?volume.format,
                        "Provisioned CoronaFS-backed VM volume via qcow2 thin clone"
                    );
                    return Ok(CoronaFsProvisionOutcome {
                        format: VolumeFormat::Qcow2,
                        deferred_image_seed: false,
                    });
                }
                Err(error) => {
                    tracing::warn!(
                        volume_id,
                        image_id,
                        raw_image_path = %materialized_raw.display(),
                        requested_size,
                        error = %error,
                        "Falling back to eager CoronaFS image clone"
                    );
                    raw_image_path = Some(materialized_raw);
                }
            }
        } else {
            tracing::info!(
                volume_id,
                image_id,
                coronafs_endpoint = %coronafs.endpoint,
                "Skipping CoronaFS image-backed thin clone because the CoronaFS endpoint is remote"
            );
        }

        let volume = coronafs.create_blank(volume_id, requested_size).await?;
        if supports_local_coronafs_path && coronafs_local_target_ready(&volume.path).await {
            let raw_image_path = match raw_image_path {
                Some(path) => path,
                None => {
                    artifact_store
                        .materialize_raw_image_cache(org_id, project_id, image_id)
                        .await?
                }
            };
            tracing::info!(
                volume_id,
                image_id,
                image_path = %image_path.display(),
                raw_image_path = %raw_image_path.display(),
                image_format = %image_info.format,
                image_virtual_size = image_info.virtual_size,
                requested_size,
                volume_path = %volume.path,
                "Populating CoronaFS-backed VM volume via local raw clone"
            );
            clone_local_raw_into_coronafs_volume(
                &raw_image_path,
                Path::new(&volume.path),
                requested_size,
                &self.qemu_img_path,
            )
            .await?;
            return Ok(CoronaFsProvisionOutcome {
                format: VolumeFormat::Raw,
                deferred_image_seed: false,
            });
        } else {
            let export = coronafs.ensure_export(volume_id).await?;
            tracing::info!(
                volume_id,
                image_id,
                image_path = %image_path.display(),
                image_format = %image_info.format,
                image_virtual_size = image_info.virtual_size,
                requested_size,
                export_uri = %export.uri,
                "Populating CoronaFS-backed VM volume over NBD from image cache"
            );
            let status = Command::new(&self.qemu_img_path)
                .args([
                    "convert",
                    "-t",
                    "none",
                    "-T",
                    "none",
                    "-m",
                    CORONAFS_IMAGE_CONVERT_PARALLELISM,
                    "-n",
                    "-W",
                    "--target-is-zero",
                    "-f",
                    image_info.format.as_str(),
                    "-O",
                    "raw",
                    image_path.to_string_lossy().as_ref(),
                    export.uri.as_str(),
                ])
                .status()
                .await
                .map_err(|e| Status::internal(format!("failed to spawn qemu-img convert: {e}")))?;
            if !status.success() {
                return Err(Status::internal(format!(
                    "qemu-img convert into CoronaFS volume {} failed for {} with status {status}",
                    volume_id,
                    image_path.display(),
                )));
            }
        }
        Ok(CoronaFsProvisionOutcome {
            format: VolumeFormat::Raw,
            deferred_image_seed: false,
        })
    }

    async fn create_blank_managed(
        &self,
        path: &Path,
        size_gib: u64,
        format: VolumeFormat,
    ) -> Result<VolumeFormat, Status> {
        if tokio::fs::try_exists(path).await.unwrap_or(false) {
            return Ok(format);
        }
        if let Some(parent) = path.parent() {
            tokio::fs::create_dir_all(parent)
                .await
                .map_err(|e| Status::internal(format!("failed to create volume dir: {e}")))?;
        }
        let status = Command::new(&self.qemu_img_path)
            .args([
                "create",
                "-f",
                volume_format_name(format),
                path.to_string_lossy().as_ref(),
                &format!("{size_gib}G"),
            ])
            .status()
            .await
            .map_err(|e| Status::internal(format!("failed to spawn qemu-img create: {e}")))?;
        if status.success() {
            Ok(format)
        } else {
            Err(Status::internal(format!(
                "qemu-img create failed for {} with status {status}",
                path.display()
            )))
        }
    }

    async fn resize_managed(
        &self,
        path: &Path,
        format: VolumeFormat,
        size_gib: u64,
    ) -> Result<(), Status> {
        let status = Command::new(&self.qemu_img_path)
            .args([
                "resize",
                "-f",
                volume_format_name(format),
                path.to_string_lossy().as_ref(),
                &format!("{size_gib}G"),
            ])
            .status()
            .await
            .map_err(|e| Status::internal(format!("failed to spawn qemu-img resize: {e}")))?;
        if status.success() {
            Ok(())
        } else {
            Err(Status::internal(format!(
                "qemu-img resize failed for {} with status {status}",
                path.display()
            )))
        }
    }

    fn managed_volume_path(&self, volume_id: &str) -> PathBuf {
        self.managed_root.join(format!("{volume_id}.raw"))
    }

    fn default_managed_storage_class(&self) -> String {
        if self.coronafs_provisioner().is_some() {
            "coronafs-managed".to_string()
        } else {
            "managed-default".to_string()
        }
    }
}

async fn inspect_qemu_image(qemu_img_path: &Path, path: &Path) -> Result<QemuImageInfo, Status> {
    let output = Command::new(qemu_img_path)
        .args(["info", "--output", "json", path.to_string_lossy().as_ref()])
        .output()
        .await
        .map_err(|e| Status::internal(format!("failed to spawn qemu-img info: {e}")))?;
    if !output.status.success() {
        return Err(Status::internal(format!(
            "qemu-img info failed for {} with status {}",
            path.display(),
            output.status
        )));
    }
    serde_json::from_slice::<QemuImageInfo>(&output.stdout)
        .map_err(|e| Status::internal(format!("failed to decode qemu-img info output: {e}")))
}

async fn coronafs_local_target_ready(path: &str) -> bool {
    match tokio::fs::OpenOptions::new()
        .read(true)
        .write(true)
        .open(path)
        .await
    {
        Ok(file) => {
            drop(file);
            true
        }
        Err(err) => {
            tracing::warn!(
                volume_path = path,
                error = %err,
                "CoronaFS local volume path is not writable from PlasmaVMC; falling back to export path"
            );
            false
        }
    }
}

async fn clone_local_raw_into_coronafs_volume(
    source: &Path,
    destination: &Path,
    requested_size: u64,
    _qemu_img_path: &Path,
) -> Result<(), Status> {
    let temp_path = destination.with_extension("clone.tmp");
    if let Some(parent) = temp_path.parent() {
        tokio::fs::create_dir_all(parent)
            .await
            .map_err(|e| Status::internal(format!("failed to create clone dir: {e}")))?;
    }
    if tokio::fs::try_exists(&temp_path).await.unwrap_or(false) {
        let _ = tokio::fs::remove_file(&temp_path).await;
    }
    tokio::fs::copy(source, &temp_path).await.map_err(|e| {
        Status::internal(format!(
            "failed to clone raw image {} into {}: {e}",
            source.display(),
            temp_path.display()
        ))
    })?;

    let file = tokio::fs::OpenOptions::new()
        .write(true)
        .open(&temp_path)
        .await
        .map_err(|e| {
            Status::internal(format!(
                "failed to open cloned CoronaFS temp volume {}: {e}",
                temp_path.display()
            ))
        })?;
    file.set_len(requested_size).await.map_err(|e| {
        Status::internal(format!(
            "failed to resize cloned CoronaFS temp volume {}: {e}",
            temp_path.display()
        ))
    })?;
    drop(file);
    ensure_coronafs_clone_permissions(&temp_path).await?;

    tokio::fs::rename(&temp_path, destination)
        .await
        .map_err(|e| {
            Status::internal(format!(
                "failed to finalize cloned CoronaFS volume {}: {e}",
                destination.display()
            ))
        })?;
    ensure_coronafs_clone_permissions(destination).await?;

    Ok(())
}

async fn sync_local_coronafs_volume_to_export(
    qemu_img_path: &Path,
    local_volume: &CoronaFsVolumeResponse,
    export_uri: &str,
) -> Result<(), Status> {
    let local_format = local_volume.format.unwrap_or(CoronaFsVolumeFormat::Raw);
    let status = Command::new(qemu_img_path)
        .args([
            "convert",
            "-t",
            "none",
            "-T",
            "none",
            "-m",
            CORONAFS_IMAGE_CONVERT_PARALLELISM,
            "-n",
            "-W",
            "-f",
            local_format.as_qemu_arg(),
            "-O",
            "raw",
            local_volume.path.as_str(),
            export_uri,
        ])
        .status()
        .await
        .map_err(|e| Status::internal(format!("failed to spawn qemu-img sync: {e}")))?;
    if status.success() {
        Ok(())
    } else {
        Err(Status::internal(format!(
            "qemu-img sync from {} to {} failed with status {status}",
            local_volume.path, export_uri
        )))
    }
}

async fn ensure_coronafs_clone_permissions(path: &Path) -> Result<(), Status> {
    #[cfg(unix)]
    {
        use std::os::unix::fs::PermissionsExt;

        let permissions = std::fs::Permissions::from_mode(0o660);
        tokio::fs::set_permissions(path, permissions)
            .await
            .map_err(|e| {
                Status::internal(format!(
                    "failed to set CoronaFS clone permissions on {}: {e}",
                    path.display()
                ))
            })?;
    }
    Ok(())
}

fn volume_format_name(format: VolumeFormat) -> &'static str {
    match format {
        VolumeFormat::Raw => "raw",
        VolumeFormat::Qcow2 => "qcow2",
    }
}

fn should_prefer_coronafs_export_attachment(volume: &CoronaFsVolumeResponse) -> bool {
    volume.node_local
        && matches!(volume.format, Some(CoronaFsVolumeFormat::Qcow2))
        && volume
            .materialized_from
            .as_deref()
            .map(|source| source.starts_with("nbd://"))
            .unwrap_or(false)
}

fn volume_coronafs_image_source_id(volume: &Volume) -> Option<&str> {
    volume
        .metadata
        .get(CORONAFS_IMAGE_SOURCE_ID_METADATA_KEY)
        .map(String::as_str)
        .filter(|value| !value.trim().is_empty())
}

fn volume_has_pending_coronafs_image_seed(volume: &Volume) -> bool {
    matches!(
        volume
            .metadata
            .get(CORONAFS_IMAGE_SEED_PENDING_METADATA_KEY)
            .map(String::as_str),
        Some("1" | "true" | "yes" | "on")
    )
}

fn derived_volume_id(vm_id: &str, disk_id: &str) -> String {
    let digest = Sha256::digest(format!("ultracloud-vm-disk:{vm_id}:{disk_id}").as_bytes());
    let mut bytes = [0u8; 16];
    bytes.copy_from_slice(&digest[..16]);
    bytes[6] = (bytes[6] & 0x0f) | 0x50;
    bytes[8] = (bytes[8] & 0x3f) | 0x80;
    Uuid::from_bytes(bytes).to_string()
}

fn volume_is_auto_delete(volume: &Volume) -> bool {
    matches!(
        volume
            .metadata
            .get(AUTO_DELETE_VOLUME_METADATA_KEY)
            .map(String::as_str),
        Some("1" | "true" | "yes")
    )
}

fn now_epoch() -> u64 {
    std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .unwrap_or_default()
        .as_secs()
}

fn to_status(err: crate::storage::StorageError) -> Status {
    Status::internal(err.to_string())
}

fn gib_to_bytes(size_gib: u64) -> u64 {
    size_gib.saturating_mul(1024 * 1024 * 1024)
}

fn validate_uuid(value: &str, field_name: &str) -> Result<(), Status> {
    Uuid::parse_str(value)
        .map(|_| ())
        .map_err(|_| Status::invalid_argument(format!("{field_name} must be a UUID")))
}

fn validate_ceph_identifier(field_name: &str, value: &str) -> Result<(), Status> {
    let trimmed = value.trim();
    if trimmed.is_empty() {
        return Err(Status::invalid_argument(format!(
            "{field_name} is required"
        )));
    }
    if trimmed.len() > CEPH_IDENTIFIER_MAX_LEN {
        return Err(Status::invalid_argument(format!(
            "{field_name} exceeds {CEPH_IDENTIFIER_MAX_LEN} characters"
        )));
    }
    let mut chars = trimmed.chars();
    let Some(first) = chars.next() else {
        return Err(Status::invalid_argument(format!(
            "{field_name} is required"
        )));
    };
    if !first.is_ascii_alphanumeric() {
        return Err(Status::invalid_argument(format!(
            "{field_name} must start with an ASCII alphanumeric character"
        )));
    }
    if chars.any(|ch| !(ch.is_ascii_alphanumeric() || matches!(ch, '.' | '_' | '-'))) {
        return Err(Status::invalid_argument(format!(
            "{field_name} contains unsupported characters"
        )));
    }
    Ok(())
}

fn validate_absolute_path(path: &str, field_name: &str) -> Result<(), Status> {
    let candidate = Path::new(path);
    if !candidate.is_absolute() {
        return Err(Status::failed_precondition(format!(
            "{field_name} must be an absolute path"
        )));
    }
    Ok(())
}

fn validate_nbd_uri(uri: &str, field_name: &str) -> Result<(), Status> {
    let parsed = reqwest::Url::parse(uri)
        .map_err(|e| Status::failed_precondition(format!("{field_name} is invalid: {e}")))?;
    if parsed.scheme() != "nbd" {
        return Err(Status::failed_precondition(format!(
            "{field_name} must use nbd://"
        )));
    }
    if !parsed.username().is_empty() || parsed.password().is_some() {
        return Err(Status::failed_precondition(format!(
            "{field_name} must not contain embedded credentials"
        )));
    }
    if parsed.host_str().is_none() || parsed.port().is_none() {
        return Err(Status::failed_precondition(format!(
            "{field_name} must include host and port"
        )));
    }
    Ok(())
}

fn validate_coronafs_export(export: &CoronaFsExport) -> Result<(), Status> {
    validate_nbd_uri(&export.uri, "coronafs export uri")
}

fn validate_coronafs_volume_response(volume: &CoronaFsVolumeResponse) -> Result<(), Status> {
    validate_absolute_path(&volume.path, "coronafs volume path")?;
    if let Some(export) = &volume.export {
        validate_coronafs_export(export)?;
    }
    if let Some(source) = &volume.materialized_from {
        if source.starts_with("nbd://") {
            validate_nbd_uri(source, "coronafs materialized_from")?;
        }
    }
    Ok(())
}

fn resolve_binary_path(
    configured_path: Option<&Path>,
    binary_name: &str,
) -> Result<PathBuf, Box<dyn std::error::Error>> {
    let candidate = match configured_path {
        Some(path) => path.to_path_buf(),
        None => std::env::var_os("PATH")
            .into_iter()
            .flat_map(|paths| std::env::split_paths(&paths).collect::<Vec<_>>())
            .map(|entry| entry.join(binary_name))
            .find(|candidate| candidate.exists())
            .ok_or_else(|| {
                std::io::Error::new(
                    std::io::ErrorKind::NotFound,
                    format!("failed to locate {binary_name} in PATH"),
                )
            })?,
    };
    let metadata = std::fs::metadata(&candidate)?;
    if !metadata.is_file() {
        return Err(std::io::Error::new(
            std::io::ErrorKind::InvalidInput,
            format!("{} is not a regular file", candidate.display()),
        )
        .into());
    }
    Ok(candidate)
}

#[cfg(test)]
fn coronafs_node_local_attach_enabled_from_values(
    stable_value: Option<&str>,
    legacy_value: Option<&str>,
) -> bool {
    stable_value
        .map(parse_truthy)
        .or_else(|| legacy_value.map(parse_truthy))
        .unwrap_or(false)
}

fn ceph_cluster_from_config(config: &CephConfig) -> Option<CephClusterConfig> {
    if config.monitors.is_empty() {
        None
    } else {
        Some(CephClusterConfig {
            cluster_id: config.cluster_id.clone(),
            monitors: config.monitors.clone(),
            user: config.user.clone(),
            secret: config
                .secret
                .clone()
                .or_else(|| std::env::var("PLASMAVMC_CEPH_SECRET").ok()),
        })
    }
}

#[cfg(test)]
fn parse_truthy(value: &str) -> bool {
    matches!(
        value.trim().to_ascii_lowercase().as_str(),
        "1" | "true" | "yes" | "on"
    )
}

fn resolve_coronafs_clients_with_config(
    config: &CoronaFsConfig,
) -> (Option<CoronaFsClient>, Option<CoronaFsClient>) {
    let (controller_endpoint, node_endpoint) = resolve_coronafs_endpoints(
        config
            .controller_endpoint
            .clone()
            .and_then(normalize_coronafs_endpoint),
        config
            .node_endpoint
            .clone()
            .and_then(normalize_coronafs_endpoint),
        config
            .endpoint
            .clone()
            .and_then(normalize_coronafs_endpoint),
    );
    (
        controller_endpoint.map(CoronaFsClient::new),
        node_endpoint.map(CoronaFsClient::new),
    )
}

fn resolve_coronafs_endpoints(
    controller_endpoint: Option<String>,
    node_endpoint: Option<String>,
    legacy_endpoint: Option<String>,
) -> (Option<String>, Option<String>) {
    let controller_endpoint = controller_endpoint.or_else(|| legacy_endpoint.clone());
    let node_endpoint = node_endpoint.or(legacy_endpoint);
    (controller_endpoint, node_endpoint)
}

fn normalize_coronafs_endpoint(endpoint: String) -> Option<String> {
    let endpoints = parse_coronafs_endpoints(&endpoint);
    if endpoints.is_empty() {
        None
    } else {
        Some(endpoints.join(","))
    }
}

fn parse_coronafs_endpoints(value: &str) -> Vec<String> {
    value
        .split(',')
        .filter_map(normalize_coronafs_endpoint_candidate)
        .collect()
}

fn normalize_coronafs_endpoint_candidate(endpoint: &str) -> Option<String> {
    let endpoint = endpoint.trim().trim_end_matches('/');
    if endpoint.is_empty() {
        return None;
    }
    if endpoint.starts_with("http://") || endpoint.starts_with("https://") {
        Some(endpoint.to_string())
    } else {
        Some(format!("http://{endpoint}"))
    }
}

impl CoronaFsClient {
    fn new(endpoint: String) -> Self {
        let endpoints = parse_coronafs_endpoints(&endpoint);
        let endpoint = endpoints.join(",");
        Self {
            endpoint,
            endpoints,
            http: reqwest::Client::builder()
                .timeout(std::time::Duration::from_secs(300))
                .build()
                .expect("valid reqwest client"),
        }
    }

    async fn supports_local_backing_file(&self) -> bool {
        match local_ip_addrs().await {
            Ok(local_ips) => self.endpoints.iter().any(|endpoint| {
                coronafs_endpoint_host(endpoint)
                    .map(|endpoint_host| {
                        endpoint_host.is_loopback() || local_ips.contains(&endpoint_host)
                    })
                    .unwrap_or(false)
            }),
            Err(error) => {
                tracing::warn!(
                    endpoint = %self.endpoint,
                    error = %error,
                    "Failed to resolve local IP addresses for CoronaFS endpoint locality check"
                );
                false
            }
        }
    }

    fn shares_endpoint_with(&self, other: &Self) -> bool {
        self.endpoints.iter().any(|endpoint| {
            other
                .endpoints
                .iter()
                .any(|candidate| candidate == endpoint)
        })
    }

    async fn create_blank(
        &self,
        volume_id: &str,
        size_bytes: u64,
    ) -> Result<CoronaFsVolumeResponse, Status> {
        let request = CoronaFsCreateRequest {
            size_bytes,
            format: None,
            backing_file: None,
            backing_format: None,
        };
        let volume = self
            .try_request("create CoronaFS volume", |endpoint, http| {
                http.put(format!("{endpoint}/v1/volumes/{volume_id}"))
                    .json(&request)
            })
            .await?
            .json::<CoronaFsVolumeResponse>()
            .await
            .map_err(|e| {
                Status::internal(format!("failed to decode CoronaFS create response: {e}"))
            })?;
        validate_coronafs_volume_response(&volume)?;
        Ok(volume)
    }

    async fn create_image_backed(
        &self,
        volume_id: &str,
        size_bytes: u64,
        backing_file: &Path,
        backing_format: CoronaFsVolumeFormat,
    ) -> Result<CoronaFsVolumeResponse, Status> {
        let request = CoronaFsCreateRequest {
            size_bytes,
            format: Some(CoronaFsVolumeFormat::Qcow2),
            backing_file: Some(backing_file.to_string_lossy().into_owned()),
            backing_format: Some(backing_format),
        };
        let volume = self
            .try_request("create CoronaFS image-backed volume", |endpoint, http| {
                http.put(format!("{endpoint}/v1/volumes/{volume_id}"))
                    .json(&request)
            })
            .await?
            .json::<CoronaFsVolumeResponse>()
            .await
            .map_err(|e| {
                Status::internal(format!(
                    "failed to decode CoronaFS image-backed create response: {e}"
                ))
            })?;
        validate_coronafs_volume_response(&volume)?;
        Ok(volume)
    }

    async fn get_volume(&self, volume_id: &str) -> Result<CoronaFsVolumeResponse, Status> {
        let volume = self
            .try_request("inspect CoronaFS volume", |endpoint, http| {
                http.get(format!("{endpoint}/v1/volumes/{volume_id}"))
            })
            .await?
            .json::<CoronaFsVolumeResponse>()
            .await
            .map_err(|e| {
                Status::internal(format!("failed to decode CoronaFS inspect response: {e}"))
            })?;
        validate_coronafs_volume_response(&volume)?;
        Ok(volume)
    }

    async fn get_volume_optional(
        &self,
        volume_id: &str,
    ) -> Result<Option<CoronaFsVolumeResponse>, Status> {
        match self.get_volume(volume_id).await {
            Ok(volume) => Ok(Some(volume)),
            Err(status) if status.code() == tonic::Code::NotFound => Ok(None),
            Err(status) => Err(status),
        }
    }

    async fn ensure_export(&self, volume_id: &str) -> Result<CoronaFsExport, Status> {
        self.ensure_export_with_mode(volume_id, false).await
    }

    async fn release_export(&self, volume_id: &str) -> Result<(), Status> {
        self.try_request("release CoronaFS export", |endpoint, http| {
            http.delete(format!("{endpoint}/v1/volumes/{volume_id}/export"))
        })
        .await?;
        Ok(())
    }

    async fn ensure_export_read_only(&self, volume_id: &str) -> Result<CoronaFsExport, Status> {
        self.ensure_export_with_mode(volume_id, true).await
    }

    async fn ensure_export_with_mode(
        &self,
        volume_id: &str,
        read_only: bool,
    ) -> Result<CoronaFsExport, Status> {
        let response = self
            .try_request("export CoronaFS volume", |endpoint, http| {
                http.post(format!("{endpoint}/v1/volumes/{volume_id}/export"))
                    .query(&[("read_only", read_only)])
            })
            .await?
            .json::<CoronaFsVolumeResponse>()
            .await
            .map_err(|e| {
                Status::internal(format!("failed to decode CoronaFS export response: {e}"))
            })?;
        let export = response.export.ok_or_else(|| {
            Status::internal("CoronaFS export response did not include an export URI")
        })?;
        validate_coronafs_export(&export)?;
        Ok(export)
    }

    async fn materialize_from_export(
        &self,
        volume_id: &str,
        source_uri: &str,
        size_bytes: u64,
        format: Option<CoronaFsVolumeFormat>,
        lazy: bool,
    ) -> Result<CoronaFsVolumeResponse, Status> {
        validate_nbd_uri(source_uri, "coronafs materialize source_uri")?;
        let request = CoronaFsMaterializeRequest {
            source_uri: source_uri.to_string(),
            size_bytes,
            format,
            lazy,
        };
        let volume = self
            .try_request("materialize CoronaFS volume", |endpoint, http| {
                http.post(format!("{endpoint}/v1/volumes/{volume_id}/materialize"))
                    .json(&request)
            })
            .await?
            .json::<CoronaFsVolumeResponse>()
            .await
            .map_err(|e| {
                Status::internal(format!(
                    "failed to decode CoronaFS materialize response: {e}"
                ))
            })?;
        validate_coronafs_volume_response(&volume)?;
        Ok(volume)
    }

    async fn resize_volume(&self, volume_id: &str, size_bytes: u64) -> Result<(), Status> {
        let request = CoronaFsResizeRequest { size_bytes };
        self.try_request("resize CoronaFS volume", |endpoint, http| {
            http.post(format!("{endpoint}/v1/volumes/{volume_id}/resize"))
                .json(&request)
        })
        .await?;
        Ok(())
    }

    async fn delete_volume(&self, volume_id: &str) -> Result<(), Status> {
        self.try_request("delete CoronaFS volume", |endpoint, http| {
            http.delete(format!("{endpoint}/v1/volumes/{volume_id}"))
        })
        .await?;
        Ok(())
    }

    async fn try_request<F>(
        &self,
        context: &'static str,
        mut build_request: F,
    ) -> Result<reqwest::Response, Status>
    where
        F: FnMut(&str, &reqwest::Client) -> reqwest::RequestBuilder,
    {
        let mut last_retryable_status = None;
        let mut last_transport_error = None;

        for endpoint in &self.endpoints {
            let response = match build_request(endpoint, &self.http).send().await {
                Ok(response) => response,
                Err(error) => {
                    tracing::warn!(
                        endpoint = %endpoint,
                        error = %error,
                        "{context} failed against CoronaFS endpoint"
                    );
                    last_transport_error = Some(error);
                    continue;
                }
            };

            match response.error_for_status() {
                Ok(response) => return Ok(response),
                Err(error) if retryable_coronafs_status(error.status()) => {
                    tracing::warn!(
                        endpoint = %endpoint,
                        error = %error,
                        "{context} received retryable response from CoronaFS endpoint"
                    );
                    last_retryable_status = Some(error);
                }
                Err(error) => return Err(http_status_error(context)(error)),
            }
        }

        if let Some(error) = last_retryable_status {
            return Err(http_status_error(context)(error));
        }

        Err(Status::internal(format!(
            "failed to {context}: {}",
            last_transport_error
                .map(|error| error.to_string())
                .unwrap_or_else(|| "no usable CoronaFS endpoints configured".to_string())
        )))
    }
}

fn retryable_coronafs_status(status: Option<reqwest::StatusCode>) -> bool {
    matches!(
        status,
        Some(reqwest::StatusCode::BAD_GATEWAY)
            | Some(reqwest::StatusCode::SERVICE_UNAVAILABLE)
            | Some(reqwest::StatusCode::GATEWAY_TIMEOUT)
    )
}

fn http_status_error(context: &'static str) -> impl Fn(reqwest::Error) -> Status {
    move |error| match error.status() {
        Some(reqwest::StatusCode::NOT_FOUND) => {
            Status::not_found(format!("{context} returned an error: {error}"))
        }
        Some(reqwest::StatusCode::BAD_REQUEST)
        | Some(reqwest::StatusCode::CONFLICT)
        | Some(reqwest::StatusCode::PRECONDITION_FAILED)
        | Some(reqwest::StatusCode::METHOD_NOT_ALLOWED) => {
            Status::failed_precondition(format!("{context} returned an error: {error}"))
        }
        _ => Status::internal(format!("{context} returned an error: {error}")),
    }
}

fn coronafs_endpoint_host(endpoint: &str) -> Option<IpAddr> {
    let parsed = reqwest::Url::parse(endpoint).ok()?;
    let host = parsed.host_str()?.trim_matches(['[', ']']);
    host.parse().ok()
}

async fn local_ip_addrs() -> Result<Vec<IpAddr>, Status> {
    let mut last_error = None;
    let candidates = [
        "/run/current-system/sw/bin/ip",
        "/nix/var/nix/profiles/default/bin/ip",
        "ip",
    ];
    let mut output = None;

    for candidate in candidates {
        match Command::new(candidate)
            .args(["-o", "addr", "show", "up"])
            .output()
            .await
        {
            Ok(candidate_output) if candidate_output.status.success() => {
                output = Some(candidate_output);
                break;
            }
            Ok(candidate_output) => {
                last_error = Some(format!(
                    "{candidate} exited with status {}",
                    candidate_output.status
                ));
            }
            Err(error) => {
                last_error = Some(format!("{candidate}: {error}"));
            }
        }
    }

    let output = output.ok_or_else(|| {
        Status::internal(format!(
            "failed to enumerate local IP addresses: {}",
            last_error.unwrap_or_else(|| "no usable ip command found".to_string())
        ))
    })?;

    let stdout = String::from_utf8(output.stdout)
        .map_err(|e| Status::internal(format!("failed to parse local IP address output: {e}")))?;
    let mut addresses = Vec::new();
    for line in stdout.lines() {
        let fields: Vec<&str> = line.split_whitespace().collect();
        if fields.len() < 4 {
            continue;
        }
        if fields[2] != "inet" && fields[2] != "inet6" {
            continue;
        }
        let Some((ip, _prefix_len)) = fields[3].split_once('/') else {
            continue;
        };
        if let Ok(addr) = ip.parse::<IpAddr>() {
            addresses.push(addr);
        }
    }
    Ok(addresses)
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::os::unix::fs::PermissionsExt;
    use tempfile::tempdir;

    #[cfg(unix)]
    #[tokio::test]
    async fn local_coronafs_clone_keeps_group_writable_volume_permissions() {
        let tempdir = tempdir().unwrap();
        let source = tempdir.path().join("source.raw");
        let destination = tempdir.path().join("volumes").join("dest.raw");
        tokio::fs::create_dir_all(destination.parent().unwrap())
            .await
            .unwrap();
        tokio::fs::write(&source, b"raw-seed").await.unwrap();

        clone_local_raw_into_coronafs_volume(
            &source,
            &destination,
            4096,
            Path::new("/usr/bin/qemu-img"),
        )
        .await
        .unwrap();

        let cloned = tokio::fs::read(&destination).await.unwrap();
        assert_eq!(&cloned[..8], b"raw-seed");
        assert_eq!(cloned.len(), 4096);

        let mode = std::fs::metadata(&destination)
            .unwrap()
            .permissions()
            .mode()
            & 0o777;
        assert_eq!(mode, 0o660);
    }

    #[test]
    fn coronafs_endpoint_host_extracts_ip_hosts_only() {
        assert_eq!(
            coronafs_endpoint_host("http://10.100.0.11:50088"),
            Some("10.100.0.11".parse().unwrap())
        );
        assert_eq!(
            coronafs_endpoint_host("https://[::1]:50088"),
            Some("::1".parse().unwrap())
        );
        assert_eq!(
            coronafs_endpoint_host("http://coronafs.internal:50088"),
            None
        );
    }

    #[test]
    fn resolve_coronafs_endpoints_prefers_split_endpoints_and_falls_back_to_legacy() {
        assert_eq!(
            resolve_coronafs_endpoints(
                Some("http://controller.internal:50088".to_string()),
                Some("http://127.0.0.1:50088".to_string()),
                Some("http://legacy.internal:50088".to_string())
            ),
            (
                Some("http://controller.internal:50088".to_string()),
                Some("http://127.0.0.1:50088".to_string())
            )
        );

        assert_eq!(
            resolve_coronafs_endpoints(
                Some("http://controller.internal:50088".to_string()),
                None,
                Some("http://legacy.internal:50088".to_string())
            ),
            (
                Some("http://controller.internal:50088".to_string()),
                Some("http://legacy.internal:50088".to_string())
            )
        );

        assert_eq!(
            resolve_coronafs_endpoints(
                None,
                None,
                Some("http://legacy.internal:50088".to_string())
            ),
            (
                Some("http://legacy.internal:50088".to_string()),
                Some("http://legacy.internal:50088".to_string())
            )
        );
    }

    #[test]
    fn derived_volume_id_is_stable_uuid() {
        let volume_id = derived_volume_id("d1d891a9-7dd1-442d-bf71-50672f150afe", "root");
        assert_eq!(
            volume_id,
            derived_volume_id("d1d891a9-7dd1-442d-bf71-50672f150afe", "root")
        );
        assert_ne!(
            volume_id,
            derived_volume_id("d1d891a9-7dd1-442d-bf71-50672f150afe", "data")
        );
        Uuid::parse_str(&volume_id).unwrap();
    }

    #[test]
    fn normalize_coronafs_endpoint_supports_comma_separated_values() {
        assert_eq!(
            normalize_coronafs_endpoint(" 10.0.0.1:50088, http://10.0.0.2:50088/ ".to_string()),
            Some("http://10.0.0.1:50088,http://10.0.0.2:50088".to_string())
        );
    }

    #[test]
    fn parse_coronafs_endpoints_normalizes_candidates() {
        assert_eq!(
            parse_coronafs_endpoints("10.0.0.1:50088, https://10.0.0.2:50088/, ,"),
            vec![
                "http://10.0.0.1:50088".to_string(),
                "https://10.0.0.2:50088".to_string()
            ]
        );
    }

    #[test]
    fn coronafs_clients_detect_shared_endpoints_from_lists() {
        let controller =
            CoronaFsClient::new("http://10.0.0.1:50088,http://10.0.0.2:50088".to_string());
        let node = CoronaFsClient::new("http://10.0.0.2:50088".to_string());
        let remote = CoronaFsClient::new("http://10.0.0.3:50088".to_string());

        assert!(controller.shares_endpoint_with(&node));
        assert!(!controller.shares_endpoint_with(&remote));
    }

    #[test]
    fn parse_truthy_recognizes_expected_values() {
        assert!(parse_truthy("true"));
        assert!(parse_truthy("On"));
        assert!(parse_truthy(" yes "));
        assert!(!parse_truthy("0"));
        assert!(!parse_truthy("false"));
    }

    #[test]
    fn coronafs_node_local_attach_prefers_stable_env_flag() {
        assert!(!coronafs_node_local_attach_enabled_from_values(None, None));
        assert!(coronafs_node_local_attach_enabled_from_values(
            None,
            Some("true")
        ));
        assert!(!coronafs_node_local_attach_enabled_from_values(
            Some("false"),
            Some("true")
        ));
        assert!(coronafs_node_local_attach_enabled_from_values(
            Some("on"),
            Some("false")
        ));
    }

    #[test]
    fn lazy_node_local_qcow2_prefers_export_attachment() {
        let lazy_local = CoronaFsVolumeResponse {
            id: "vol".to_string(),
            size_bytes: 1024,
            format: Some(CoronaFsVolumeFormat::Qcow2),
            node_local: true,
            materialized_from: Some("nbd://10.0.0.1:11000".to_string()),
            path: "/tmp/vol.qcow2".to_string(),
            export: None,
        };
        let plain_local = CoronaFsVolumeResponse {
            materialized_from: None,
            ..lazy_local.clone()
        };
        let raw_local = CoronaFsVolumeResponse {
            format: Some(CoronaFsVolumeFormat::Raw),
            materialized_from: Some("nbd://10.0.0.1:11000".to_string()),
            ..lazy_local.clone()
        };

        assert!(should_prefer_coronafs_export_attachment(&lazy_local));
        assert!(!should_prefer_coronafs_export_attachment(&plain_local));
        assert!(!should_prefer_coronafs_export_attachment(&raw_local));
    }

    #[test]
    fn coronafs_export_attachment_is_presented_as_raw_block_device() {
        let attachment = DiskAttachment::Nbd {
            uri: "nbd://10.0.0.1:11000".to_string(),
            format: VolumeFormat::Raw,
        };
        match attachment {
            DiskAttachment::Nbd { format, .. } => assert_eq!(format, VolumeFormat::Raw),
            other => panic!("expected NBD attachment, got {other:?}"),
        }
    }

    #[test]
    fn pending_coronafs_image_seed_metadata_helpers_round_trip() {
        let mut volume = Volume::new("vol", "vol", "org", "project", 10);
        assert!(volume_coronafs_image_source_id(&volume).is_none());
        assert!(!volume_has_pending_coronafs_image_seed(&volume));

        volume.metadata.insert(
            CORONAFS_IMAGE_SOURCE_ID_METADATA_KEY.to_string(),
            "image-123".to_string(),
        );
        volume.metadata.insert(
            CORONAFS_IMAGE_SEED_PENDING_METADATA_KEY.to_string(),
            "true".to_string(),
        );

        assert_eq!(volume_coronafs_image_source_id(&volume), Some("image-123"));
        assert!(volume_has_pending_coronafs_image_seed(&volume));

        volume
            .metadata
            .remove(CORONAFS_IMAGE_SEED_PENDING_METADATA_KEY);
        assert!(!volume_has_pending_coronafs_image_seed(&volume));
    }

    #[test]
    fn ceph_identifier_validation_rejects_option_injection_characters() {
        assert!(validate_ceph_identifier("pool", "pool-alpha").is_ok());
        assert!(validate_ceph_identifier("pool", "pool,inject").is_err());
        assert!(validate_ceph_identifier("image", "image:inject").is_err());
    }

    #[test]
    fn coronafs_export_validation_requires_nbd_uri() {
        let valid = CoronaFsExport {
            uri: "nbd://10.0.0.1:11000".to_string(),
            port: 11000,
            pid: None,
        };
        let invalid = CoronaFsExport {
            uri: "http://10.0.0.1:11000".to_string(),
            ..valid.clone()
        };

        assert!(validate_coronafs_export(&valid).is_ok());
        assert!(validate_coronafs_export(&invalid).is_err());
    }
}
