//! Core types for the property graph.

use indexmap::IndexMap;
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use std::fmt;

/// A property value in the graph.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum PropertyValue {
    Null,
    Bool(bool),
    Integer(i64),
    Float(f64),
    String(String),
    List(Vec<PropertyValue>),
    Map(IndexMap<String, PropertyValue>),
    // Temporal types (standard range: ±262K years)
    Date(crate::result::DateValue),
    Time(crate::result::TimeValue),
    LocalTime(crate::result::LocalTimeValue),
    DateTime(crate::result::DateTimeValue),
    LocalDateTime(crate::result::LocalDateTimeValue),
    Duration(crate::result::DurationValue),
    // Extended temporal types (full Neo4j range: ±999M years)
    // These use the NxDate/NxLocalDateTime types which automatically
    // promote/demote between chrono and extended representations
    ExtendedDate(crate::result::NxDate),
    ExtendedLocalDateTime(crate::result::NxLocalDateTime),
}

impl PropertyValue {
    /// Check if the value is null.
    pub fn is_null(&self) -> bool {
        matches!(self, PropertyValue::Null)
    }

    /// Check if the value is truthy.
    pub fn is_truthy(&self) -> bool {
        match self {
            PropertyValue::Null => false,
            PropertyValue::Bool(b) => *b,
            PropertyValue::Integer(i) => *i != 0,
            PropertyValue::Float(f) => *f != 0.0,
            PropertyValue::String(s) => !s.is_empty(),
            PropertyValue::List(l) => !l.is_empty(),
            PropertyValue::Map(m) => !m.is_empty(),
            // Temporal types are always truthy
            PropertyValue::Date(_)
            | PropertyValue::Time(_)
            | PropertyValue::LocalTime(_)
            | PropertyValue::DateTime(_)
            | PropertyValue::LocalDateTime(_)
            | PropertyValue::Duration(_)
            | PropertyValue::ExtendedDate(_)
            | PropertyValue::ExtendedLocalDateTime(_) => true,
        }
    }

    /// Try to get as bool.
    pub fn as_bool(&self) -> Option<bool> {
        match self {
            PropertyValue::Bool(b) => Some(*b),
            _ => None,
        }
    }

    /// Try to get as integer.
    pub fn as_integer(&self) -> Option<i64> {
        match self {
            PropertyValue::Integer(i) => Some(*i),
            _ => None,
        }
    }

    /// Try to get as float.
    pub fn as_float(&self) -> Option<f64> {
        match self {
            PropertyValue::Integer(i) => Some(*i as f64),
            PropertyValue::Float(f) => Some(*f),
            _ => None,
        }
    }

    /// Try to get as string.
    pub fn as_string(&self) -> Option<&str> {
        match self {
            PropertyValue::String(s) => Some(s),
            _ => None,
        }
    }

    /// Try to get as list.
    pub fn as_list(&self) -> Option<&Vec<PropertyValue>> {
        match self {
            PropertyValue::List(l) => Some(l),
            _ => None,
        }
    }

    /// Try to get as map.
    pub fn as_map(&self) -> Option<&IndexMap<String, PropertyValue>> {
        match self {
            PropertyValue::Map(m) => Some(m),
            _ => None,
        }
    }

    /// Get the type name of this value.
    pub fn type_name(&self) -> &'static str {
        match self {
            PropertyValue::Null => "NULL",
            PropertyValue::Bool(_) => "BOOLEAN",
            PropertyValue::Integer(_) => "INTEGER",
            PropertyValue::Float(_) => "FLOAT",
            PropertyValue::String(_) => "STRING",
            PropertyValue::List(_) => "LIST",
            PropertyValue::Map(_) => "MAP",
            PropertyValue::Date(_) => "DATE",
            PropertyValue::Time(_) => "TIME",
            PropertyValue::LocalTime(_) => "LOCAL TIME",
            PropertyValue::DateTime(_) => "DATE TIME",
            PropertyValue::LocalDateTime(_) => "LOCAL DATE TIME",
            PropertyValue::Duration(_) => "DURATION",
            // Extended types use the same type names as standard
            PropertyValue::ExtendedDate(_) => "DATE",
            PropertyValue::ExtendedLocalDateTime(_) => "LOCAL DATE TIME",
        }
    }
}

impl fmt::Display for PropertyValue {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            PropertyValue::Null => write!(f, "null"),
            PropertyValue::Bool(b) => write!(f, "{}", b),
            PropertyValue::Integer(i) => write!(f, "{}", i),
            PropertyValue::Float(fl) => write!(f, "{}", fl),
            PropertyValue::String(s) => write!(f, "\"{}\"", s),
            PropertyValue::List(l) => {
                write!(f, "[")?;
                for (i, v) in l.iter().enumerate() {
                    if i > 0 {
                        write!(f, ", ")?;
                    }
                    write!(f, "{}", v)?;
                }
                write!(f, "]")
            }
            PropertyValue::Map(m) => {
                write!(f, "{{")?;
                for (i, (k, v)) in m.iter().enumerate() {
                    if i > 0 {
                        write!(f, ", ")?;
                    }
                    write!(f, "{}: {}", k, v)?;
                }
                write!(f, "}}")
            }
            PropertyValue::Date(d) => write!(f, "{}", d),
            PropertyValue::Time(t) => write!(f, "{}", t),
            PropertyValue::LocalTime(lt) => write!(f, "{}", lt),
            PropertyValue::DateTime(dt) => write!(f, "{}", dt),
            PropertyValue::LocalDateTime(ldt) => write!(f, "{}", ldt),
            PropertyValue::Duration(dur) => write!(f, "{}", dur),
            PropertyValue::ExtendedDate(d) => write!(f, "{}", d),
            PropertyValue::ExtendedLocalDateTime(ldt) => write!(f, "{}", ldt),
        }
    }
}

impl From<bool> for PropertyValue {
    fn from(b: bool) -> Self {
        PropertyValue::Bool(b)
    }
}

impl From<i64> for PropertyValue {
    fn from(i: i64) -> Self {
        PropertyValue::Integer(i)
    }
}

impl From<i32> for PropertyValue {
    fn from(i: i32) -> Self {
        PropertyValue::Integer(i as i64)
    }
}

impl From<f64> for PropertyValue {
    fn from(f: f64) -> Self {
        PropertyValue::Float(f)
    }
}

impl From<String> for PropertyValue {
    fn from(s: String) -> Self {
        PropertyValue::String(s)
    }
}

impl From<&str> for PropertyValue {
    fn from(s: &str) -> Self {
        PropertyValue::String(s.to_string())
    }
}

impl<T: Into<PropertyValue>> From<Vec<T>> for PropertyValue {
    fn from(v: Vec<T>) -> Self {
        PropertyValue::List(v.into_iter().map(Into::into).collect())
    }
}

impl<T: Into<PropertyValue>> From<Option<T>> for PropertyValue {
    fn from(opt: Option<T>) -> Self {
        match opt {
            Some(v) => v.into(),
            None => PropertyValue::Null,
        }
    }
}

/// A node in the property graph.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GraphNode {
    /// Unique identifier for this node.
    pub id: u64,
    /// Set of labels attached to this node.
    pub labels: HashSet<String>,
    /// Properties of this node.
    pub properties: IndexMap<String, PropertyValue>,
}

impl GraphNode {
    /// Create a new node with the given ID.
    pub fn new(id: u64) -> Self {
        Self {
            id,
            labels: HashSet::new(),
            properties: IndexMap::new(),
        }
    }

    /// Create a new node with labels.
    pub fn with_labels(id: u64, labels: impl IntoIterator<Item = impl Into<String>>) -> Self {
        Self {
            id,
            labels: labels.into_iter().map(Into::into).collect(),
            properties: IndexMap::new(),
        }
    }

    /// Add a label to this node.
    pub fn add_label(&mut self, label: impl Into<String>) {
        self.labels.insert(label.into());
    }

    /// Remove a label from this node.
    pub fn remove_label(&mut self, label: &str) -> bool {
        self.labels.remove(label)
    }

    /// Check if this node has a label.
    pub fn has_label(&self, label: &str) -> bool {
        self.labels.contains(label)
    }

    /// Set a property on this node.
    pub fn set_property(&mut self, key: impl Into<String>, value: impl Into<PropertyValue>) {
        self.properties.insert(key.into(), value.into());
    }

    /// Get a property from this node.
    pub fn get_property(&self, key: &str) -> Option<&PropertyValue> {
        self.properties.get(key)
    }

    /// Remove a property from this node.
    pub fn remove_property(&mut self, key: &str) -> Option<PropertyValue> {
        self.properties.shift_remove(key)
    }
}

impl PartialEq for GraphNode {
    fn eq(&self, other: &Self) -> bool {
        self.id == other.id
    }
}

impl Eq for GraphNode {}

impl std::hash::Hash for GraphNode {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        self.id.hash(state);
    }
}

/// An edge (relationship) in the property graph.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GraphEdge {
    /// Unique identifier for this edge.
    pub id: u64,
    /// The relationship type.
    pub rel_type: String,
    /// Properties of this edge.
    pub properties: IndexMap<String, PropertyValue>,
}

impl GraphEdge {
    /// Create a new edge with the given ID and type.
    pub fn new(id: u64, rel_type: impl Into<String>) -> Self {
        Self {
            id,
            rel_type: rel_type.into(),
            properties: IndexMap::new(),
        }
    }

    /// Set a property on this edge.
    pub fn set_property(&mut self, key: impl Into<String>, value: impl Into<PropertyValue>) {
        self.properties.insert(key.into(), value.into());
    }

    /// Get a property from this edge.
    pub fn get_property(&self, key: &str) -> Option<&PropertyValue> {
        self.properties.get(key)
    }

    /// Remove a property from this edge.
    pub fn remove_property(&mut self, key: &str) -> Option<PropertyValue> {
        self.properties.shift_remove(key)
    }
}

impl PartialEq for GraphEdge {
    fn eq(&self, other: &Self) -> bool {
        self.id == other.id
    }
}

impl Eq for GraphEdge {}

impl std::hash::Hash for GraphEdge {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        self.id.hash(state);
    }
}

/// A path in the graph, consisting of alternating nodes and relationships.
#[derive(Debug, Clone, PartialEq)]
pub struct Path {
    /// The nodes in the path.
    pub nodes: Vec<GraphNode>,
    /// The relationships in the path.
    pub relationships: Vec<GraphEdge>,
}

impl Path {
    /// Create a new path starting with a single node.
    pub fn new(start: GraphNode) -> Self {
        Self {
            nodes: vec![start],
            relationships: vec![],
        }
    }

    /// Extend the path with a relationship and node.
    pub fn extend(&mut self, rel: GraphEdge, node: GraphNode) {
        self.relationships.push(rel);
        self.nodes.push(node);
    }

    /// Get the length of the path (number of relationships).
    pub fn length(&self) -> usize {
        self.relationships.len()
    }

    /// Get the start node of the path.
    pub fn start(&self) -> Option<&GraphNode> {
        self.nodes.first()
    }

    /// Get the end node of the path.
    pub fn end(&self) -> Option<&GraphNode> {
        self.nodes.last()
    }
}

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

    #[test]
    fn test_property_value_is_truthy() {
        assert!(!PropertyValue::Null.is_truthy());
        assert!(!PropertyValue::Bool(false).is_truthy());
        assert!(PropertyValue::Bool(true).is_truthy());
        assert!(!PropertyValue::Integer(0).is_truthy());
        assert!(PropertyValue::Integer(1).is_truthy());
        assert!(!PropertyValue::String("".to_string()).is_truthy());
        assert!(PropertyValue::String("hello".to_string()).is_truthy());
    }

    #[test]
    fn test_graph_node_labels() {
        let mut node = GraphNode::new(1);
        node.add_label("Person");
        node.add_label("Employee");

        assert!(node.has_label("Person"));
        assert!(node.has_label("Employee"));
        assert!(!node.has_label("Company"));

        node.remove_label("Employee");
        assert!(!node.has_label("Employee"));
    }

    #[test]
    fn test_graph_node_properties() {
        let mut node = GraphNode::new(1);
        node.set_property("name", "Alice");
        node.set_property("age", 30i64);

        assert_eq!(
            node.get_property("name"),
            Some(&PropertyValue::String("Alice".to_string()))
        );
        assert_eq!(node.get_property("age"), Some(&PropertyValue::Integer(30)));

        node.remove_property("age");
        assert_eq!(node.get_property("age"), None);
    }

    #[test]
    fn test_path() {
        let node1 = GraphNode::with_labels(1, vec!["Person"]);
        let node2 = GraphNode::with_labels(2, vec!["Person"]);
        let edge = GraphEdge::new(1, "KNOWS");

        let mut path = Path::new(node1.clone());
        path.extend(edge, node2.clone());

        assert_eq!(path.length(), 1);
        assert_eq!(path.start().map(|n| n.id), Some(1));
        assert_eq!(path.end().map(|n| n.id), Some(2));
    }
}
