use crate::cloudhv::config::{
    DeviceConfig, DeviceType, DiskConfig, FsConfig, NetConfig, VfioConfig, VhostUserBlkConfig,
    VhostUserNetConfig,
};
use crate::cloudhv::errors::{CloudHypervisorError, Result};
use std::path::PathBuf;

pub struct DeviceBuilder {
    device_id: String,
    device_type: Option<DeviceType>,
}

impl DeviceBuilder {
    pub fn new(id: impl Into<String>) -> Self {
        Self {
            device_id: id.into(),
            device_type: None,
        }
    }

    pub fn disk(mut self, path: impl Into<PathBuf>, readonly: bool) -> Self {
        self.device_type = Some(DeviceType::Disk(DiskConfig {
            path: path.into(),
            readonly,
            direct: true,
            vhost_user: false,
            socket: None,
        }));
        self
    }

    pub fn net(mut self, tap: impl Into<String>) -> Self {
        self.device_type = Some(DeviceType::Net(NetConfig {
            tap: Some(tap.into()),
            mac: None,
            vhost_user: false,
            socket: None,
        }));
        self
    }

    pub fn fs(mut self, tag: impl Into<String>, socket: impl Into<PathBuf>) -> Self {
        self.device_type = Some(DeviceType::Fs(FsConfig {
            tag: tag.into(),
            socket: socket.into(),
            num_queues: 1,
            queue_size: 1024,
            cache_size: 8 * 1024 * 1024 * 1024,
        }));
        self
    }

    pub fn vhost_user_net(mut self, socket: impl Into<PathBuf>) -> Self {
        self.device_type = Some(DeviceType::VhostUserNet(VhostUserNetConfig {
            socket: socket.into(),
            mac: None,
            num_queues: 2,
            queue_size: 256,
        }));
        self
    }

    pub fn vhost_user_blk(mut self, socket: impl Into<PathBuf>, readonly: bool) -> Self {
        self.device_type = Some(DeviceType::VhostUserBlk(VhostUserBlkConfig {
            socket: socket.into(),
            num_queues: 1,
            queue_size: 128,
            readonly,
        }));
        self
    }

    pub fn vfio(mut self, device: impl Into<String>) -> Self {
        self.device_type = Some(DeviceType::Vfio(VfioConfig {
            device: device.into(),
        }));
        self
    }

    pub fn build(self) -> Result<DeviceConfig> {
        let device_type = self.device_type.ok_or_else(|| {
            CloudHypervisorError::Device("Device type not specified".to_string())
        })?;

        Ok(DeviceConfig {
            id: self.device_id,
            device_type,
        })
    }
}

pub struct DiskBuilder {
    path: Option<PathBuf>,
    readonly: bool,
    direct: bool,
    vhost_user: bool,
    socket: Option<PathBuf>,
}

impl DiskBuilder {
    pub fn new() -> Self {
        Self {
            path: None,
            readonly: false,
            direct: true,
            vhost_user: false,
            socket: None,
        }
    }

    pub fn path(mut self, path: impl Into<PathBuf>) -> Self {
        self.path = Some(path.into());
        self
    }

    pub fn readonly(mut self, readonly: bool) -> Self {
        self.readonly = readonly;
        self
    }

    pub fn direct(mut self, direct: bool) -> Self {
        self.direct = direct;
        self
    }

    pub fn vhost_user(mut self, socket: impl Into<PathBuf>) -> Self {
        self.vhost_user = true;
        self.socket = Some(socket.into());
        self
    }

    pub fn build(self) -> Result<DiskConfig> {
        let path = self
            .path
            .ok_or_else(|| CloudHypervisorError::Device("Disk path not specified".to_string()))?;

        Ok(DiskConfig {
            path,
            readonly: self.readonly,
            direct: self.direct,
            vhost_user: self.vhost_user,
            socket: self.socket,
        })
    }
}

impl Default for DiskBuilder {
    fn default() -> Self {
        Self::new()
    }
}

pub struct NetBuilder {
    tap: Option<String>,
    mac: Option<String>,
    vhost_user: bool,
    socket: Option<PathBuf>,
}

impl NetBuilder {
    pub fn new() -> Self {
        Self {
            tap: None,
            mac: None,
            vhost_user: false,
            socket: None,
        }
    }

    pub fn tap(mut self, tap: impl Into<String>) -> Self {
        self.tap = Some(tap.into());
        self
    }

    pub fn mac(mut self, mac: impl Into<String>) -> Self {
        self.mac = Some(mac.into());
        self
    }

    pub fn vhost_user(mut self, socket: impl Into<PathBuf>) -> Self {
        self.vhost_user = true;
        self.socket = Some(socket.into());
        self
    }

    pub fn build(self) -> Result<NetConfig> {
        if !self.vhost_user && self.tap.is_none() {
            return Err(CloudHypervisorError::Device(
                "Either tap or vhost_user socket must be specified".to_string(),
            ));
        }

        Ok(NetConfig {
            tap: self.tap,
            mac: self.mac,
            vhost_user: self.vhost_user,
            socket: self.socket,
        })
    }
}

impl Default for NetBuilder {
    fn default() -> Self {
        Self::new()
    }
}
