use std::ops::Mul;

#[derive(thiserror::Error, Debug)]
enum FilesSizeUtilsError {
    #[error("UnitConvertError: {0}")]
    UnitConvert(String),
}

/// Holds a data size, convertible in any form
#[derive(
    serde::Serialize,
    serde::Deserialize,
    Copy,
    Clone,
    Debug,
    Eq,
    PartialEq,
    PartialOrd,
    Ord,
    Default,
)]
pub struct FileSize(usize);

impl FileSize {
    pub const fn from_bytes(size: usize) -> Self {
        Self(size)
    }

    pub const fn from_mb(mb: usize) -> Self {
        Self(mb * 1000 * 1000)
    }

    pub const fn from_gb(gb: usize) -> Self {
        Self(gb * 1000 * 1000 * 1000)
    }

    /// Convert size unit to MB
    pub fn from_size_unit(unit: &str, value: usize) -> anyhow::Result<Self> {
        let fact = match unit {
            "bytes" | "b" => 1f64,
            "KB" => 1000f64,
            "MB" => 1000f64 * 1000f64,
            "GB" => 1000f64 * 1000f64 * 1000f64,
            "TB" => 1000f64 * 1000f64 * 1000f64 * 1000f64,

            "k" | "KiB" => 1024f64,
            "M" | "MiB" => 1024f64 * 1024f64,
            "G" | "GiB" => 1024f64 * 1024f64 * 1024f64,
            "T" | "TiB" => 1024f64 * 1024f64 * 1024f64 * 1024f64,

            _ => {
                return Err(
                    FilesSizeUtilsError::UnitConvert(format!("Unknown size unit: {unit}")).into(),
                );
            }
        };

        Ok(Self((value as f64).mul(fact).ceil() as usize))
    }

    /// Get file size as bytes
    pub fn as_bytes(&self) -> usize {
        self.0
    }

    /// Get file size as megabytes
    pub fn as_mb(&self) -> usize {
        self.0 / (1000 * 1000)
    }
}

#[cfg(test)]
mod tests {
    use crate::utils::file_size_utils::FileSize;

    #[test]
    fn convert_units_mb() {
        assert_eq!(FileSize::from_size_unit("MB", 1).unwrap().as_mb(), 1);
        assert_eq!(FileSize::from_size_unit("MB", 1000).unwrap().as_mb(), 1000);
        assert_eq!(
            FileSize::from_size_unit("GB", 1000).unwrap().as_mb(),
            1000 * 1000
        );
        assert_eq!(FileSize::from_size_unit("GB", 1).unwrap().as_mb(), 1000);
        assert_eq!(FileSize::from_size_unit("GiB", 3).unwrap().as_mb(), 3221);
        assert_eq!(
            FileSize::from_size_unit("KiB", 488281).unwrap().as_mb(),
            499
        );
    }
}