//! Graph edge (relationship) definition.

use super::node::NodeId;
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
use std::sync::atomic::{AtomicU64, Ordering};

/// Unique edge identifier.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct EdgeId(pub [u8; 32]);

impl EdgeId {
    /// Creates a new unique edge ID.
    pub fn new() -> Self {
        static COUNTER: AtomicU64 = AtomicU64::new(1);
        let id = COUNTER.fetch_add(1, Ordering::SeqCst);
        let mut bytes = [0u8; 32];
        bytes[..8].copy_from_slice(&id.to_be_bytes());
        let now = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap()
            .as_nanos() as u64;
        bytes[8..16].copy_from_slice(&now.to_be_bytes());
        EdgeId(*blake3::hash(&bytes).as_bytes())
    }

    /// Creates from raw bytes.
    pub fn from_bytes(bytes: [u8; 32]) -> Self {
        EdgeId(bytes)
    }

    /// Creates from hex string.
    pub fn from_hex(hex: &str) -> Option<Self> {
        let bytes = hex::decode(hex).ok()?;
        if bytes.len() != 32 {
            return None;
        }
        let mut arr = [0u8; 32];
        arr.copy_from_slice(&bytes);
        Some(EdgeId(arr))
    }

    /// Returns the bytes.
    pub fn as_bytes(&self) -> &[u8; 32] {
        &self.0
    }

    /// Converts to hex string.
    pub fn to_hex(&self) -> String {
        hex::encode(self.0)
    }
}

impl Default for EdgeId {
    fn default() -> Self {
        Self::new()
    }
}

impl std::fmt::Display for EdgeId {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "edge_{}", hex::encode(&self.0[..8]))
    }
}

/// An edge (relationship) in the graph.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Edge {
    /// Unique edge ID.
    pub id: EdgeId,
    /// Source node ID.
    pub source: NodeId,
    /// Target node ID.
    pub target: NodeId,
    /// Edge type (relationship type), e.g., "FRIEND", "OWNS".
    pub edge_type: String,
    /// Properties stored as JSON.
    pub properties: JsonValue,
    /// Whether this is a directed edge.
    pub directed: bool,
    /// Weight for path-finding algorithms.
    pub weight: f64,
    /// Creation timestamp.
    pub created_at: u64,
}

impl Edge {
    /// Creates a new directed edge.
    pub fn new(source: NodeId, target: NodeId, edge_type: impl Into<String>, properties: JsonValue) -> Self {
        let now = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap()
            .as_millis() as u64;

        Self {
            id: EdgeId::new(),
            source,
            target,
            edge_type: edge_type.into(),
            properties,
            directed: true,
            weight: 1.0,
            created_at: now,
        }
    }

    /// Creates an undirected edge.
    pub fn undirected(source: NodeId, target: NodeId, edge_type: impl Into<String>, properties: JsonValue) -> Self {
        let mut edge = Self::new(source, target, edge_type, properties);
        edge.directed = false;
        edge
    }

    /// Sets the weight for this edge.
    pub fn with_weight(mut self, weight: f64) -> Self {
        self.weight = weight;
        self
    }

    /// Returns the other end of this edge from the given node.
    pub fn other_end(&self, from: &NodeId) -> Option<NodeId> {
        if &self.source == from {
            Some(self.target)
        } else if &self.target == from && !self.directed {
            Some(self.source)
        } else if &self.target == from {
            // For directed edges, can't traverse backward
            None
        } else {
            None
        }
    }

    /// Checks if this edge connects the given node (as source or target).
    pub fn connects(&self, node: &NodeId) -> bool {
        &self.source == node || &self.target == node
    }

    /// Checks if this edge connects two specific nodes.
    pub fn connects_pair(&self, a: &NodeId, b: &NodeId) -> bool {
        (&self.source == a && &self.target == b) ||
        (!self.directed && &self.source == b && &self.target == a)
    }

    /// Gets a property value.
    pub fn get_property(&self, key: &str) -> Option<&JsonValue> {
        self.properties.get(key)
    }

    /// Sets a property value.
    pub fn set_property(&mut self, key: &str, value: JsonValue) {
        if let Some(obj) = self.properties.as_object_mut() {
            obj.insert(key.to_string(), value);
        }
    }

    /// Checks if the edge matches a property filter.
    pub fn matches_properties(&self, filter: &JsonValue) -> bool {
        if let (Some(filter_obj), Some(props_obj)) = (filter.as_object(), self.properties.as_object()) {
            for (key, expected) in filter_obj {
                if let Some(actual) = props_obj.get(key) {
                    if actual != expected {
                        return false;
                    }
                } else {
                    return false;
                }
            }
            true
        } else {
            filter == &self.properties || filter == &JsonValue::Object(serde_json::Map::new())
        }
    }
}

/// Builder for creating edges.
pub struct EdgeBuilder {
    source: NodeId,
    target: NodeId,
    edge_type: String,
    properties: serde_json::Map<String, JsonValue>,
    directed: bool,
    weight: f64,
}

impl EdgeBuilder {
    /// Creates a new edge builder.
    pub fn new(source: NodeId, target: NodeId, edge_type: impl Into<String>) -> Self {
        Self {
            source,
            target,
            edge_type: edge_type.into(),
            properties: serde_json::Map::new(),
            directed: true,
            weight: 1.0,
        }
    }

    /// Sets the edge as undirected.
    pub fn undirected(mut self) -> Self {
        self.directed = false;
        self
    }

    /// Sets the weight.
    pub fn weight(mut self, weight: f64) -> Self {
        self.weight = weight;
        self
    }

    /// Sets a property.
    pub fn property(mut self, key: impl Into<String>, value: impl Into<JsonValue>) -> Self {
        self.properties.insert(key.into(), value.into());
        self
    }

    /// Builds the edge.
    pub fn build(self) -> Edge {
        let mut edge = Edge::new(self.source, self.target, self.edge_type, JsonValue::Object(self.properties));
        edge.directed = self.directed;
        edge.weight = self.weight;
        edge
    }
}

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

    #[test]
    fn test_edge_id() {
        let id1 = EdgeId::new();
        let id2 = EdgeId::new();
        assert_ne!(id1, id2);

        let hex = id1.to_hex();
        let id3 = EdgeId::from_hex(&hex).unwrap();
        assert_eq!(id1, id3);
    }

    #[test]
    fn test_edge_creation() {
        let source = NodeId::new();
        let target = NodeId::new();

        let edge = Edge::new(source, target, "FRIEND", serde_json::json!({"since": 2020}));

        assert_eq!(edge.source, source);
        assert_eq!(edge.target, target);
        assert_eq!(edge.edge_type, "FRIEND");
        assert!(edge.directed);
    }

    #[test]
    fn test_edge_builder() {
        let source = NodeId::new();
        let target = NodeId::new();

        let edge = EdgeBuilder::new(source, target, "OWNS")
            .undirected()
            .weight(2.5)
            .property("percentage", 50)
            .build();

        assert!(!edge.directed);
        assert_eq!(edge.weight, 2.5);
        assert_eq!(edge.get_property("percentage"), Some(&serde_json::json!(50)));
    }

    #[test]
    fn test_edge_other_end() {
        let source = NodeId::new();
        let target = NodeId::new();

        // Directed edge
        let directed = Edge::new(source, target, "A", serde_json::json!({}));
        assert_eq!(directed.other_end(&source), Some(target));
        assert_eq!(directed.other_end(&target), None); // Can't traverse backward

        // Undirected edge
        let undirected = Edge::undirected(source, target, "B", serde_json::json!({}));
        assert_eq!(undirected.other_end(&source), Some(target));
        assert_eq!(undirected.other_end(&target), Some(source));
    }

    #[test]
    fn test_edge_connects() {
        let a = NodeId::new();
        let b = NodeId::new();
        let c = NodeId::new();

        let edge = Edge::new(a, b, "LINK", serde_json::json!({}));

        assert!(edge.connects(&a));
        assert!(edge.connects(&b));
        assert!(!edge.connects(&c));

        assert!(edge.connects_pair(&a, &b));
        assert!(!edge.connects_pair(&b, &a)); // Directed
    }
}
