use crate::cloudhv::errors::{CloudHypervisorError, Result};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;

const DEFAULT_BINARY_PATHS: &[&str] = &[
    "/usr/local/bin/cloud-hypervisor",
    "/usr/bin/cloud-hypervisor",
    "cloud-hypervisor", // Will search in PATH
];

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CloudHypervisorConfig {
    pub binary_path: PathBuf,
    pub api_socket_path: PathBuf,
    pub log_file: Option<PathBuf>,
    pub seccomp: bool,
    pub landlock: bool,
}

impl CloudHypervisorConfig {
    /// Create a new config with the specified binary path
    /// If the path doesn't exist, returns an error
    pub fn new(binary_path: impl Into<PathBuf>) -> Self {
        Self {
            binary_path: binary_path.into(),
            api_socket_path: PathBuf::from("/tmp/ch-api.sock"),
            log_file: None,
            seccomp: true,
            landlock: true,
        }
    }

    /// Create a new config with automatic binary path detection
    /// Tries default paths and returns error if binary not found
    pub fn new_with_defaults() -> Result<Self> {
        let binary_path = Self::find_binary()?;
        Ok(Self {
            binary_path,
            api_socket_path: PathBuf::from("/tmp/ch-api.sock"),
            log_file: None,
            seccomp: true,
            landlock: true,
        })
    }

    /// Create a new config, using provided path if it exists, otherwise try defaults
    pub fn new_smart(binary_path: Option<impl Into<PathBuf>>) -> Result<Self> {
        let path = if let Some(p) = binary_path {
            let path = p.into();
            if path.exists() {
                path
            } else {
                return Err(CloudHypervisorError::Config(format!(
                    "Specified binary not found: {}",
                    path.display()
                )));
            }
        } else {
            Self::find_binary()?
        };

        Ok(Self {
            binary_path: path,
            api_socket_path: PathBuf::from("/tmp/ch-api.sock"),
            log_file: None,
            seccomp: true,
            landlock: true,
        })
    }

    /// Find the cloud-hypervisor binary in default locations
    fn find_binary() -> Result<PathBuf> {
        for path_str in DEFAULT_BINARY_PATHS {
            let path = PathBuf::from(path_str);
            
            // For relative paths, check if it's in PATH
            if !path.is_absolute() {
                if let Ok(which_path) = which::which(path_str) {
                    return Ok(which_path);
                }
            } else if path.exists() {
                return Ok(path);
            }
        }

        Err(CloudHypervisorError::Config(
            format!(
                "Cloud Hypervisor binary not found. Searched in: {}. \
                Please install cloud-hypervisor or specify the binary path explicitly.",
                DEFAULT_BINARY_PATHS.join(", ")
            )
        ))
    }

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

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

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

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

    pub fn validate(&self) -> Result<()> {
        if !self.binary_path.exists() {
            return Err(CloudHypervisorError::Config(format!(
                "Binary not found: {}",
                self.binary_path.display()
            )));
        }
        Ok(())
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VMConfig {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub payload: Option<PayloadConfig>,
    pub cpus: CpuConfig,
    pub memory: MemoryConfig,
    #[serde(skip_serializing_if = "Vec::is_empty")]
    pub disks: Vec<DiskConfig>,
    #[serde(skip_serializing_if = "Vec::is_empty")]
    pub net: Vec<NetConfig>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub rng: Option<RngConfig>,
    #[serde(skip_serializing_if = "Vec::is_empty")]
    pub fs: Vec<FsConfig>,
    #[serde(skip_serializing_if = "Vec::is_empty")]
    pub vhost_user_net: Vec<VhostUserNetConfig>,
    #[serde(skip_serializing_if = "Vec::is_empty")]
    pub vhost_user_blk: Vec<VhostUserBlkConfig>,
    #[serde(skip_serializing_if = "Vec::is_empty")]
    pub vfio: Vec<VfioConfig>,
    pub console: ConsoleConfig,
    pub serial: SerialConfig,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PayloadConfig {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub kernel: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub initramfs: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub cmdline: Option<String>,
}

impl Default for VMConfig {
    fn default() -> Self {
        Self {
            payload: None,
            cpus: CpuConfig::default(),
            memory: MemoryConfig::default(),
            disks: vec![],
            net: vec![],
            rng: None,
            fs: vec![],
            vhost_user_net: vec![],
            vhost_user_blk: vec![],
            vfio: vec![],
            console: ConsoleConfig::default(),
            serial: SerialConfig::default(),
        }
    }
}

impl VMConfig {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn with_kernel(mut self, path: impl Into<PathBuf>) -> Self {
        let payload = self.payload.get_or_insert_with(|| PayloadConfig {
            kernel: None,
            initramfs: None,
            cmdline: None,
        });
        payload.kernel = Some(path.into().display().to_string());
        self
    }

    pub fn with_initramfs(mut self, path: impl Into<PathBuf>) -> Self {
        let payload = self.payload.get_or_insert_with(|| PayloadConfig {
            kernel: None,
            initramfs: None,
            cmdline: None,
        });
        payload.initramfs = Some(path.into().display().to_string());
        self
    }

    pub fn with_cmdline(mut self, args: Vec<String>) -> Self {
        let payload = self.payload.get_or_insert_with(|| PayloadConfig {
            kernel: None,
            initramfs: None,
            cmdline: None,
        });
        payload.cmdline = Some(args.join(" "));
        self
    }

    pub fn with_cpus(mut self, cpus: u32) -> Self {
        self.cpus.boot_vcpus = cpus;
        self.cpus.max_vcpus = cpus;
        self
    }

    pub fn with_memory(mut self, size_mb: u64) -> Self {
        self.memory.size = size_mb * 1024 * 1024;
        self
    }

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

    pub fn with_rng(mut self, src: RngSource) -> Self {
        self.rng = Some(RngConfig { src });
        self
    }

    pub fn add_virtio_blk(&mut self, path: impl Into<PathBuf>, readonly: bool) -> &mut Self {
        self.disks.push(DiskConfig {
            path: path.into(),
            readonly,
            direct: true,
            vhost_user: false,
            socket: None,
        });
        self
    }

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

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

    pub fn add_virtio_rng(&mut self) -> &mut Self {
        self.rng = Some(RngConfig { src: RngSource::Urandom });
        self
    }

    pub fn add_virtio_fs(&mut self, tag: impl Into<String>, socket_path: impl Into<PathBuf>) -> &mut Self {
        self.fs.push(FsConfig {
            tag: tag.into(),
            socket: socket_path.into(),
            num_queues: 1,
            queue_size: 1024,
            cache_size: 8 * 1024 * 1024 * 1024,
        });
        self
    }

    pub fn add_vhost_user_net(&mut self, socket: impl Into<PathBuf>) -> &mut Self {
        self.vhost_user_net.push(VhostUserNetConfig {
            socket: socket.into(),
            mac: None,
            num_queues: 2,
            queue_size: 256,
        });
        self
    }

    pub fn add_vhost_user_blk(&mut self, socket: impl Into<PathBuf>) -> &mut Self {
        self.vhost_user_blk.push(VhostUserBlkConfig {
            socket: socket.into(),
            num_queues: 1,
            queue_size: 128,
            readonly: false,
        });
        self
    }

    pub fn add_vfio(&mut self, device: impl Into<String>) -> &mut Self {
        self.vfio.push(VfioConfig {
            device: device.into(),
        });
        self
    }

    pub fn build(self) -> Result<VMSpec> {
        if self.payload.is_none() || self.payload.as_ref().unwrap().kernel.is_none() {
            return Err(CloudHypervisorError::Config(
                "Kernel path is required".to_string(),
            ));
        }

        if self.cpus.boot_vcpus == 0 {
            return Err(CloudHypervisorError::Config(
                "At least one CPU is required".to_string(),
            ));
        }

        if self.memory.size == 0 {
            return Err(CloudHypervisorError::Config(
                "Memory size must be greater than 0".to_string(),
            ));
        }

        Ok(VMSpec {
            config: self,
            id: uuid::Uuid::new_v4().to_string(),
        })
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VMSpec {
    pub config: VMConfig,
    pub id: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CpuConfig {
    pub boot_vcpus: u32,
    pub max_vcpus: u32,
}

impl Default for CpuConfig {
    fn default() -> Self {
        Self {
            boot_vcpus: 1,
            max_vcpus: 1,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MemoryConfig {
    pub size: u64,
    pub mergeable: bool,
    pub hotplug_method: HotplugMethod,
    pub hotplug_size: Option<u64>,
    pub hotplugged_size: Option<u64>,
    pub shared: bool,
    pub hugepages: bool,
}

impl Default for MemoryConfig {
    fn default() -> Self {
        Self {
            size: 512 * 1024 * 1024,
            mergeable: false,
            hotplug_method: HotplugMethod::Acpi,
            hotplug_size: None,
            hotplugged_size: None,
            shared: false,
            hugepages: false,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum HotplugMethod {
    Acpi,
    VirtioMem,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DiskConfig {
    pub path: PathBuf,
    pub readonly: bool,
    pub direct: bool,
    pub vhost_user: bool,
    pub socket: Option<PathBuf>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NetConfig {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tap: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub mac: Option<String>,
    #[serde(skip_serializing_if = "is_false")]
    pub vhost_user: bool,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub socket: Option<PathBuf>,
}

fn is_false(b: &bool) -> bool {
    !b
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RngConfig {
    pub src: RngSource,
}

impl Default for RngConfig {
    fn default() -> Self {
        Self {
            src: RngSource::Urandom,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum RngSource {
    Urandom,
    Random,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FsConfig {
    pub tag: String,
    pub socket: PathBuf,
    pub num_queues: usize,
    pub queue_size: u16,
    pub cache_size: u64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VhostUserNetConfig {
    pub socket: PathBuf,
    pub mac: Option<String>,
    pub num_queues: usize,
    pub queue_size: u16,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VhostUserBlkConfig {
    pub socket: PathBuf,
    pub num_queues: usize,
    pub queue_size: u16,
    pub readonly: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VfioConfig {
    pub device: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConsoleConfig {
    pub mode: ConsoleMode,
}

impl Default for ConsoleConfig {
    fn default() -> Self {
        Self {
            mode: ConsoleMode::Off,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ConsoleMode {
    Off,
    Tty,
    File,
    Null,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SerialConfig {
    pub mode: SerialMode,
}

impl Default for SerialConfig {
    fn default() -> Self {
        Self {
            mode: SerialMode::Null,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SerialMode {
    Off,
    Tty,
    File,
    Null,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeviceConfig {
    pub id: String,
    pub device_type: DeviceType,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "lowercase")]
pub enum DeviceType {
    Disk(DiskConfig),
    Net(NetConfig),
    Fs(FsConfig),
    VhostUserNet(VhostUserNetConfig),
    VhostUserBlk(VhostUserBlkConfig),
    Vfio(VfioConfig),
}
