use crate::Prefix;
use std::fmt;
use std::marker::PhantomData;
use std::str::FromStr;
use uuid::Uuid;

/// A Prefixed UUID.
///
/// It wraps a standard [`Uuid`] but is strictly typed to a specific [`Prefix`].
///
/// # Type Parameters
/// * `T`: A unit struct implementing the [`Prefix`] trait.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Puuid<T: Prefix> {
    inner: Uuid,
    _marker: PhantomData<T>,
}

impl<T: Prefix> Puuid<T> {
    /// Create a new Puuid from an existing raw Uuid.
    pub const fn from_uuid(uuid: Uuid) -> Self {
        Self {
            inner: uuid,
            _marker: PhantomData,
        }
    }

    /// Get the underlying raw Uuid.
    pub const fn into_inner(self) -> Uuid {
        self.inner
    }

    /// Get the prefix string for this type (without the underscore).
    pub const fn prefix() -> &'static str {
        T::VALUE
    }
}

// --- Version Specific Constructors ---
impl<T: Prefix> Puuid<T> {
    #[cfg(feature = "v4")]
    pub fn new_v4() -> Self {
        Self::from_uuid(Uuid::new_v4())
    }

    #[cfg(feature = "v7")]
    pub fn new_v7() -> Self {
        Self::from_uuid(Uuid::now_v7())
    }

    #[cfg(feature = "v3")]
    pub fn new_v3(namespace: &Uuid, name: &[u8]) -> Self {
        Self::from_uuid(Uuid::new_v3(namespace, name))
    }

    #[cfg(feature = "v5")]
    pub fn new_v5(namespace: &Uuid, name: &[u8]) -> Self {
        Self::from_uuid(Uuid::new_v5(namespace, name))
    }
}

// --- Standard Trait Implementations ---

// Deref allows access to standard Uuid methods
impl<T: Prefix> std::ops::Deref for Puuid<T> {
    type Target = Uuid;
    fn deref(&self) -> &Self::Target {
        &self.inner
    }
}

impl<T: Prefix> fmt::Display for Puuid<T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}_{}", T::VALUE, self.inner)
    }
}

impl<T: Prefix> FromStr for Puuid<T> {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let prefix = T::VALUE;

        // IMPROVED LOGIC: Split first, then validate.
        // This ensures "Wrong prefix" errors appear even if lengths differ.

        // 1. Split prefix
        let (found_prefix, uuid_str) = s
            .split_once('_')
            .ok_or_else(|| "Missing '_' separator".to_string())?;

        // 2. Verify prefix
        if found_prefix != prefix {
            return Err(format!(
                "Wrong prefix. Expected '{}', found '{}'",
                prefix, found_prefix
            ));
        }

        // 3. Parse UUID
        // Uuid::from_str handles length and character validation robustness
        let uuid = Uuid::from_str(uuid_str).map_err(|e| format!("Invalid UUID format: {}", e))?;

        Ok(Self::from_uuid(uuid))
    }
}

impl<T: Prefix> Default for Puuid<T> {
    fn default() -> Self {
        #[cfg(feature = "v7")]
        return Self::new_v7();
        #[cfg(all(not(feature = "v7"), feature = "v4"))]
        return Self::new_v4();
        #[cfg(all(not(feature = "v7"), not(feature = "v4")))]
        return Self::from_uuid(Uuid::nil());
    }
}

// --- Unit Tests for Wrapper ---
#[cfg(test)]
mod tests {
    use super::*;
    use crate::prefix;

    prefix!(TestPre, "test");
    type TestId = Puuid<TestPre>;

    #[test]
    fn test_display_format() {
        let raw = Uuid::nil();
        let id = TestId::from_uuid(raw);
        assert_eq!(id.to_string(), "test_00000000-0000-0000-0000-000000000000");
    }

    #[test]
    fn test_parsing_success() {
        let input = "test_018c6427-4f30-7f89-a1b2-c3d4e5f67890";
        let id = TestId::from_str(input).expect("Should parse");
        assert_eq!(id.to_string(), input);
    }

    #[test]
    fn test_parsing_wrong_prefix() {
        // "wrong" is 5 chars, "test" is 4 chars.
        // With the new logic, this correctly returns "Wrong prefix" instead of "Invalid length"
        let input = "wrong_018c6427-4f30-7f89-a1b2-c3d4e5f67890";
        let err = TestId::from_str(input).unwrap_err();
        assert!(err.contains("Wrong prefix"));
    }

    #[test]
    fn test_parsing_bad_format() {
        let input = "test_notauuid";
        let err = TestId::from_str(input).unwrap_err();
        // Updated expectation: now it fails at UUID parsing step, not length check
        assert!(err.contains("Invalid UUID"));
    }
}
