use super::error::BtrfsError;
use super::types::DeviceType;
use super::cmd::{hdparm_execute, lsblk_execute_and_parse, mkfs_btrfs_execute_and_parse_uuid, find_mountpoint};
use super::path_utils::{ensure_block_device, path_to_str};
use std::fs;
use std::path::{Path, PathBuf};

const SEEKTIME_FILENAME: &str = ".seektime";

#[derive(Clone)]
pub struct DeviceManager {
    device_path: PathBuf,
    device_path_str: String,
    label: Option<String>,
    uuid: Option<String>,
    mount_path: Option<PathBuf>,
}

impl DeviceManager {
    /// Create a new DeviceManager for a block device
    ///
    /// # Example
    ///
    /// ```rust,no_run
    /// use herolib_os::virt::btrfs::DeviceManager;
    ///
    /// let manager = DeviceManager::new("/dev/sda1")?;
    /// ```
    pub fn new(device_path: &str) -> Result<Self, BtrfsError> {
        let path = Path::new(device_path);
        ensure_block_device(path)?;
        
        let device_path_str = path_to_str(path)?.to_string();
        let device_path_buf = path.to_path_buf();
        
        let (label, uuid, mount_path) = match Self::get_filesystem_info(&device_path_str) {
            Ok(Some(info)) => (info.label, info.uuid, info.mount_path),
            Ok(None) | Err(_) => (None, None, None),
        };
        
        Ok(Self {
            device_path: device_path_buf,
            device_path_str,
            label,
            uuid,
            mount_path,
        })
    }

    fn get_filesystem_info(device_str: &str) -> Result<Option<super::cmd::LsblkDeviceInfo>, BtrfsError> {
        lsblk_execute_and_parse(device_str)
            .map_err(|_| BtrfsError::InvalidDevice(format!("Failed to get filesystem info for {}", device_str)))
    }

    /// Format the device with Btrfs filesystem
    ///
    /// # Example
    ///
    /// ```rust,no_run
    /// let mut manager = DeviceManager::new("/dev/sda1")?;
    /// // Format with label
    /// manager.format(Some("my-label"))?;
    ///
    /// // Format without label
    /// manager.format(None)?;
    /// ```
    pub fn format(&mut self, label: Option<&str>) -> Result<(), BtrfsError> {
        let device_str = path_to_str(&self.device_path)?;
        let uuid = mkfs_btrfs_execute_and_parse_uuid(device_str, label)
            .map_err(|e| BtrfsError::FormatFailed(format!("Failed to format device and parse UUID: {}", e)))?;

        self.label = label.map(|s| s.to_string());
        self.uuid = Some(uuid);
        self.mount_path = None; // Formatting unmounts the device

        Ok(())
    }

    /// Shutdown the device using hdparm (puts device in standby mode)
    ///
    /// # Example
    ///
    /// ```rust,no_run
    /// manager.shutdown()?;
    /// ```
    pub fn shutdown(&self) -> Result<(), BtrfsError> {
        let device_str = path_to_str(&self.device_path)?;
        hdparm_execute(device_str, &["-y"])?;
        Ok(())
    }

    /// Set the device type (SSD or HDD) by storing it in a .seektime file on the mounted filesystem
    ///
    /// # Example
    ///
    /// ```rust,no_run
    /// use herolib_os::virt::btrfs::DeviceType;
    ///
    /// manager.set_type(DeviceType::SSD)?;
    /// ```
    pub fn set_type(&self, device_type: DeviceType) -> Result<(), BtrfsError> {
        let mount_point = find_mountpoint(&self.device_path)
            .map_err(|_| BtrfsError::NotMounted(format!(
                "Device {} must be mounted to set device type",
                self.device_path_str
            )))?;
        
        let seektime_path = mount_point.join(SEEKTIME_FILENAME);
        let content = device_type.as_str();
        
        fs::write(&seektime_path, content).map_err(|e| {
            BtrfsError::DeviceError(format!(
                "Failed to write device type to {}: {}",
                seektime_path.display(), e
            ))
        })?;
        
        Ok(())
    }

    /// Get the device type (SSD or HDD) by reading it from a .seektime file on the mounted filesystem
    ///
    /// # Example
    ///
    /// ```rust,no_run
    /// match manager.get_type()? {
    ///     Some(device_type) => println!("Device type: {:?}", device_type),
    ///     None => println!("Device type not stored"),
    /// }
    /// ```
    pub fn get_type(&self) -> Result<Option<DeviceType>, BtrfsError> {
        let mount_point = find_mountpoint(&self.device_path)
            .map_err(|_| BtrfsError::NotMounted(format!(
                "Device {} must be mounted to get device type",
                self.device_path_str
            )))?;
        
        let seektime_path = mount_point.join(SEEKTIME_FILENAME);
        
        if !seektime_path.exists() {
            return Ok(None);
        }
        
        let content = fs::read_to_string(&seektime_path).map_err(|e| {
            BtrfsError::DeviceError(format!(
                "Failed to read device type from {}: {}",
                seektime_path.display(), e
            ))
        })?;
        
        let device_type = DeviceType::from_str(content.trim());
        Ok(device_type)
    }

    /// Get the device path
    pub fn device_path(&self) -> &Path {
        &self.device_path
    }

    /// Get the device path as string
    pub fn device_path_str(&self) -> &str {
        &self.device_path_str
    }

    /// Get the label if available
    pub fn label(&self) -> Option<&str> {
        self.label.as_deref()
    }

    /// Get the UUID if available
    pub fn uuid(&self) -> Option<&str> {
        self.uuid.as_deref()
    }

    /// Get the mount path if mounted
    pub fn mount_path(&self) -> Option<&Path> {
        self.mount_path.as_deref()
    }
}

