//! Relationship graph edges between entities.
//!
//! Implemented in Task 2 (p1-02).

use crate::schema::{EntityId, Timestamp};

/// The type of relationship between two entities.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum RelationshipType {
    Follows = 0x01,
    Blocks = 0x02,
    InteractionWeight = 0x03,
    Hide = 0x04,
    Mute = 0x05,
}

impl RelationshipType {
    /// Parse a byte back into a `RelationshipType`.
    #[must_use]
    pub const fn from_byte(b: u8) -> Option<Self> {
        match b {
            0x01 => Some(Self::Follows),
            0x02 => Some(Self::Blocks),
            0x03 => Some(Self::InteractionWeight),
            0x04 => Some(Self::Hide),
            0x05 => Some(Self::Mute),
            _ => None,
        }
    }

    /// The discriminant byte.
    #[must_use]
    pub const fn as_byte(self) -> u8 {
        self as u8
    }
}

/// A directed, weighted edge between two entities.
#[derive(Debug, Clone)]
pub struct RelationshipEdge {
    pub from: EntityId,
    pub to: EntityId,
    pub rel_type: RelationshipType,
    pub weight: f64,
    pub timestamp: Timestamp,
}

/// Encode a relationship key (19 bytes).
///
/// Format: `[from_entity_id: 8 BE][0x00][Tag::Rel: 0x04][rel_type: 1 byte][to_entity_id: 8 BE]`
///
/// Total: 8 + 1 + 1 + 1 + 8 = 19 bytes.
#[must_use]
pub fn encode_relationship_key(
    from: EntityId,
    rel_type: RelationshipType,
    to: EntityId,
) -> Vec<u8> {
    let mut key = Vec::with_capacity(19);
    key.extend_from_slice(&from.to_be_bytes());
    key.push(0x00); // separator
    key.push(0x04); // Tag::Rel
    key.push(rel_type.as_byte());
    key.extend_from_slice(&to.to_be_bytes());
    key
}

/// Build a prefix for scanning all relationships of a given type from an entity.
///
/// Returns 11 bytes: `[from_id: 8 BE][0x00][0x04][rel_type]`
#[must_use]
pub fn relationship_prefix(from: EntityId, rel_type: RelationshipType) -> Vec<u8> {
    let mut prefix = Vec::with_capacity(11);
    prefix.extend_from_slice(&from.to_be_bytes());
    prefix.push(0x00);
    prefix.push(0x04);
    prefix.push(rel_type.as_byte());
    prefix
}

/// Serialize relationship value (16 bytes).
///
/// Format: `[weight: 8 bytes f64 LE][timestamp_nanos: 8 bytes u64 LE]`
#[must_use]
pub fn serialize_relationship_value(weight: f64, timestamp: Timestamp) -> Vec<u8> {
    let mut buf = Vec::with_capacity(16);
    buf.extend_from_slice(&weight.to_le_bytes());
    buf.extend_from_slice(&timestamp.as_nanos().to_le_bytes());
    buf
}

/// Deserialize relationship value. Returns `(weight, timestamp_nanos)`.
#[must_use]
pub fn deserialize_relationship_value(bytes: &[u8]) -> Option<(f64, u64)> {
    if bytes.len() < 16 {
        return None;
    }
    let weight = f64::from_le_bytes([
        bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
    ]);
    let ts_nanos = u64::from_le_bytes([
        bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15],
    ]);
    Some((weight, ts_nanos))
}

/// Parse the `to` entity ID from a full relationship key (19 bytes).
///
/// The `to` entity starts at offset 11: 8 (from) + 1 (NUL) + 1 (`Tag::Rel`) + 1 (`rel_type`).
#[must_use]
pub fn parse_relationship_to(key: &[u8]) -> Option<EntityId> {
    if key.len() < 19 {
        return None;
    }
    let to = u64::from_be_bytes([
        key[11], key[12], key[13], key[14], key[15], key[16], key[17], key[18],
    ]);
    Some(EntityId::new(to))
}

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

    #[test]
    fn relationship_type_roundtrip() {
        let types = [
            RelationshipType::Follows,
            RelationshipType::Blocks,
            RelationshipType::InteractionWeight,
            RelationshipType::Hide,
            RelationshipType::Mute,
        ];
        for rt in types {
            let byte = rt.as_byte();
            let parsed = RelationshipType::from_byte(byte).unwrap();
            assert_eq!(parsed, rt);
        }
    }

    #[test]
    fn encode_relationship_key_length() {
        let key = encode_relationship_key(
            EntityId::new(1),
            RelationshipType::Follows,
            EntityId::new(2),
        );
        assert_eq!(key.len(), 19);
    }

    #[test]
    fn relationship_value_roundtrip() {
        let ts = Timestamp::from_nanos(1_000_000_000);
        let bytes = serialize_relationship_value(0.75, ts);
        let (w, t) = deserialize_relationship_value(&bytes).unwrap();
        assert!((w - 0.75).abs() < f64::EPSILON);
        assert_eq!(t, 1_000_000_000);
    }

    #[test]
    fn parse_to_from_key() {
        let key = encode_relationship_key(
            EntityId::new(100),
            RelationshipType::Blocks,
            EntityId::new(200),
        );
        let to = parse_relationship_to(&key).unwrap();
        assert_eq!(to, EntityId::new(200));
    }

    #[test]
    fn relationship_prefix_length() {
        let prefix = relationship_prefix(EntityId::new(1), RelationshipType::Follows);
        assert_eq!(prefix.len(), 11);
    }

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

        fn arb_relationship_type() -> impl Strategy<Value = RelationshipType> {
            prop_oneof![
                Just(RelationshipType::Follows),
                Just(RelationshipType::Blocks),
                Just(RelationshipType::InteractionWeight),
                Just(RelationshipType::Hide),
                Just(RelationshipType::Mute),
            ]
        }

        proptest! {
            /// Relationship key encode/decode roundtrip: from, rel_type, and to are all recovered.
            #[test]
            fn key_roundtrip(
                from_id in any::<u64>(),
                to_id in any::<u64>(),
                rel_type in arb_relationship_type(),
            ) {
                let key = encode_relationship_key(
                    EntityId::new(from_id),
                    rel_type,
                    EntityId::new(to_id),
                );
                prop_assert_eq!(key.len(), 19);

                // Parse back to_id.
                let parsed_to = parse_relationship_to(&key).unwrap();
                prop_assert_eq!(parsed_to, EntityId::new(to_id));

                // Parse back from_id (first 8 bytes, big-endian).
                let parsed_from = u64::from_be_bytes([
                    key[0], key[1], key[2], key[3],
                    key[4], key[5], key[6], key[7],
                ]);
                prop_assert_eq!(parsed_from, from_id);

                // Parse back rel_type (byte at offset 10).
                let parsed_rel = RelationshipType::from_byte(key[10]).unwrap();
                prop_assert_eq!(parsed_rel, rel_type);
            }

            /// Relationship value encode/decode roundtrip.
            #[test]
            fn value_roundtrip(
                weight in any::<f64>().prop_filter("finite", |w| w.is_finite()),
                ts_nanos in any::<u64>(),
            ) {
                let ts = Timestamp::from_nanos(ts_nanos);
                let bytes = serialize_relationship_value(weight, ts);
                let (dec_weight, dec_ts) = deserialize_relationship_value(&bytes).unwrap();
                prop_assert!((dec_weight - weight).abs() < f64::EPSILON);
                prop_assert_eq!(dec_ts, ts_nanos);
            }
        }
    }
}
