use objc2::rc::Retained;
use objc2::AnyThread;
use objc2_foundation::{NSString, NSURL};
use objc2_virtualization::{
    VZDiskImageCachingMode, VZDiskImageStorageDeviceAttachment, VZDiskImageSynchronizationMode,
    VZDiskSynchronizationMode, VZNetworkBlockDeviceStorageDeviceAttachment,
    VZStorageDeviceAttachment, VZStorageDeviceConfiguration, VZVirtioBlockDeviceConfiguration,
};

use crate::error::{Result, VzError};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DiskImageCachingMode {
    Automatic,
    Cached,
    Uncached,
}

impl DiskImageCachingMode {
    fn to_vz(self) -> VZDiskImageCachingMode {
        match self {
            Self::Automatic => VZDiskImageCachingMode::Automatic,
            Self::Cached => VZDiskImageCachingMode::Cached,
            Self::Uncached => VZDiskImageCachingMode::Uncached,
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DiskImageSynchronizationMode {
    Full,
    Fsync,
    None,
}

impl DiskImageSynchronizationMode {
    fn to_vz(self) -> VZDiskImageSynchronizationMode {
        match self {
            Self::Full => VZDiskImageSynchronizationMode::Full,
            Self::Fsync => VZDiskImageSynchronizationMode::Fsync,
            Self::None => VZDiskImageSynchronizationMode::None,
        }
    }
}

pub trait StorageDevice {
    fn as_storage_config(&self) -> Retained<VZStorageDeviceConfiguration>;
}

pub trait StorageAttachment {
    fn as_vz_attachment(&self) -> Retained<VZStorageDeviceAttachment>;
}

pub struct DiskImageAttachment {
    inner: Retained<VZDiskImageStorageDeviceAttachment>,
}

impl DiskImageAttachment {
    pub fn new(path: &str, read_only: bool) -> Result<Self> {
        unsafe {
            let ns_path = NSString::from_str(path);
            let url = NSURL::fileURLWithPath_isDirectory(&ns_path, false);
            let inner = VZDiskImageStorageDeviceAttachment::initWithURL_readOnly_error(
                VZDiskImageStorageDeviceAttachment::alloc(),
                &url,
                read_only,
            )
            .map_err(|e| VzError::from_ns_error(&e))?;
            Ok(DiskImageAttachment { inner })
        }
    }

    pub fn new_with_options(
        path: &str,
        read_only: bool,
        caching_mode: DiskImageCachingMode,
        synchronization_mode: DiskImageSynchronizationMode,
    ) -> Result<Self> {
        unsafe {
            let ns_path = NSString::from_str(path);
            let url = NSURL::fileURLWithPath_isDirectory(&ns_path, false);
            let inner = VZDiskImageStorageDeviceAttachment::initWithURL_readOnly_cachingMode_synchronizationMode_error(
                VZDiskImageStorageDeviceAttachment::alloc(),
                &url,
                read_only,
                caching_mode.to_vz(),
                synchronization_mode.to_vz(),
            )
            .map_err(|e| VzError::from_ns_error(&e))?;
            Ok(DiskImageAttachment { inner })
        }
    }
}

impl StorageAttachment for DiskImageAttachment {
    fn as_vz_attachment(&self) -> Retained<VZStorageDeviceAttachment> {
        unsafe { Retained::cast_unchecked(self.inner.clone()) }
    }
}

pub struct NbdAttachment {
    inner: Retained<VZNetworkBlockDeviceStorageDeviceAttachment>,
}

impl NbdAttachment {
    pub fn new(uri: &str, timeout_secs: f64, read_only: bool) -> Result<Self> {
        unsafe {
            let ns_uri = NSString::from_str(uri);
            let url = NSURL::URLWithString(&ns_uri)
                .ok_or_else(|| VzError::new(format!("invalid NBD URI: {}", uri)))?;
            let sync_mode = if read_only {
                VZDiskSynchronizationMode::None
            } else {
                VZDiskSynchronizationMode::Full
            };
            let inner = VZNetworkBlockDeviceStorageDeviceAttachment::initWithURL_timeout_forcedReadOnly_synchronizationMode_error(
                VZNetworkBlockDeviceStorageDeviceAttachment::alloc(),
                &url,
                timeout_secs,
                read_only,
                sync_mode,
            )
            .map_err(|e| VzError::from_ns_error(&e))?;
            Ok(NbdAttachment { inner })
        }
    }
}

impl StorageAttachment for NbdAttachment {
    fn as_vz_attachment(&self) -> Retained<VZStorageDeviceAttachment> {
        unsafe { Retained::cast_unchecked(self.inner.clone()) }
    }
}

pub struct VirtioBlockDevice {
    inner: Retained<VZVirtioBlockDeviceConfiguration>,
}

impl VirtioBlockDevice {
    pub fn new(attachment: &dyn StorageAttachment) -> Self {
        unsafe {
            let attachment_id = attachment.as_vz_attachment();
            let inner = VZVirtioBlockDeviceConfiguration::initWithAttachment(
                VZVirtioBlockDeviceConfiguration::alloc(),
                &attachment_id,
            );
            VirtioBlockDevice { inner }
        }
    }

    pub fn validate_identifier(identifier: &str) -> Result<()> {
        unsafe {
            let id = NSString::from_str(identifier);
            VZVirtioBlockDeviceConfiguration::validateBlockDeviceIdentifier_error(&id)
                .map_err(|e| VzError::from_ns_error(&e))?;
            Ok(())
        }
    }

    pub fn set_identifier(&self, identifier: &str) {
        unsafe {
            let id = NSString::from_str(identifier);
            self.inner.setBlockDeviceIdentifier(&id);
        }
    }
}

impl StorageDevice for VirtioBlockDevice {
    fn as_storage_config(&self) -> Retained<VZStorageDeviceConfiguration> {
        unsafe { Retained::cast_unchecked(self.inner.clone()) }
    }
}
