use std::{ops::Deref, str::FromStr};
use std::fmt;
use uuid::Uuid;
use pyo3::{prelude::*, exceptions::PyValueError};
use serde::{Deserialize, Serialize};

/// Package ID. This is a UUID.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct Id(Uuid);

impl Id {
    /// Generates a new unique package ID.
    pub fn new() -> Self {
        Self(Uuid::new_v4())
    }
}

impl From<Uuid> for Id {
    fn from(uuid: Uuid) -> Self {
        Self(uuid)
    }
}

impl From<Id> for Uuid {
    fn from(id: Id) -> Self {
        id.0
    }
}

impl Deref for Id {
    type Target = Uuid;
    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl fmt::Display for Id {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.0.to_string().fmt(f)
    }
}

impl FromStr for Id {
    type Err = uuid::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(Self(Uuid::parse_str(s)?))
    }
}

impl FromPyObject<'_> for Id {
    fn extract(ob: &PyAny) -> PyResult<Self> {
        let string: String = ob.extract()?;
        let uuid = Uuid::parse_str(&string).map_err(|e| {
            PyValueError::new_err(format!("Invalid UUID: {}", e))
        })?;
        Ok(Self(uuid))
    }
}

impl ToPyObject for Id {
    fn to_object(&self, py: Python) -> PyObject {
        self.0.to_string().to_object(py)
    }
}

impl IntoPy<PyObject> for Id {
    fn into_py(self, py: Python) -> PyObject {
        self.to_object(py)
    }
}
