use std::fmt;

/// Unique identifier for any entity in the database.
///
/// Wraps a `u64` and provides big-endian byte encoding that preserves
/// numeric ordering — critical for storage key scans.
#[derive(
    Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
)]
pub struct EntityId(u64);

impl EntityId {
    #[must_use]
    pub const fn new(id: u64) -> Self {
        Self(id)
    }

    #[must_use]
    pub const fn as_u64(self) -> u64 {
        self.0
    }

    /// Big-endian encoding that preserves numeric ordering for storage scans.
    #[must_use]
    pub const fn to_be_bytes(self) -> [u8; 8] {
        self.0.to_be_bytes()
    }
}

impl From<u64> for EntityId {
    fn from(id: u64) -> Self {
        Self(id)
    }
}

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

impl fmt::Debug for EntityId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "EntityId({})", self.0)
    }
}

/// The kind of entity stored in the database.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum EntityKind {
    Item,
    User,
    Creator,
}

impl fmt::Display for EntityKind {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(match self {
            Self::Item => "item",
            Self::User => "user",
            Self::Creator => "creator",
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn entity_id_display() {
        let id = EntityId::new(42);
        assert_eq!(id.to_string(), "42");
    }

    #[test]
    fn entity_id_debug() {
        let id = EntityId::new(42);
        assert_eq!(format!("{id:?}"), "EntityId(42)");
    }

    #[test]
    fn entity_id_from_u64() {
        let id: EntityId = 99u64.into();
        assert_eq!(id.as_u64(), 99);
    }

    #[test]
    fn entity_id_be_bytes_roundtrip() {
        let id = EntityId::new(0x0102_0304_0506_0708);
        let bytes = id.to_be_bytes();
        let recovered = u64::from_be_bytes(bytes);
        assert_eq!(recovered, id.as_u64());
    }

    #[test]
    fn entity_kind_display() {
        assert_eq!(EntityKind::Item.to_string(), "item");
        assert_eq!(EntityKind::User.to_string(), "user");
        assert_eq!(EntityKind::Creator.to_string(), "creator");
    }

    mod proptests {
        use super::*;
        use proptest::prelude::*;

        proptest! {
            #[test]
            fn be_bytes_roundtrip(val: u64) {
                let id = EntityId::new(val);
                let recovered = u64::from_be_bytes(id.to_be_bytes());
                prop_assert_eq!(recovered, val);
            }

            #[test]
            fn numeric_ordering_matches_byte_ordering(a: u64, b: u64) {
                let id_a = EntityId::new(a);
                let id_b = EntityId::new(b);
                let bytes_a = id_a.to_be_bytes();
                let bytes_b = id_b.to_be_bytes();
                prop_assert_eq!(a.cmp(&b), bytes_a.cmp(&bytes_b));
            }
        }
    }
}
