//! Scalar functions.

use std::time::{SystemTime, UNIX_EPOCH};


use crate::error::{ExecutionError, ExecutionResult};
use crate::result::CypherValue;

/// id(node) or id(relationship) - returns the ID
pub fn id(args: Vec<CypherValue>) -> ExecutionResult<CypherValue> {
    if args.len() != 1 {
        return Err(ExecutionError::InvalidArgument(
            "id() requires exactly 1 argument".to_string(),
        ));
    }

    match &args[0] {
        CypherValue::Node(n) => Ok(CypherValue::Integer(n.id as i64)),
        CypherValue::Relationship(r) => Ok(CypherValue::Integer(r.id as i64)),
        CypherValue::Null => Ok(CypherValue::Null),
        other => Err(ExecutionError::Type(format!(
            "id() requires a node or relationship, got {}",
            other.type_name()
        ))),
    }
}

/// type(relationship) - returns the relationship type
pub fn type_fn(args: Vec<CypherValue>) -> ExecutionResult<CypherValue> {
    if args.len() != 1 {
        return Err(ExecutionError::InvalidArgument(
            "type() requires exactly 1 argument".to_string(),
        ));
    }

    match &args[0] {
        CypherValue::Relationship(r) => Ok(CypherValue::String(r.rel_type.clone())),
        CypherValue::Null => Ok(CypherValue::Null),
        other => Err(ExecutionError::Type(format!(
            "type() requires a relationship, got {}",
            other.type_name()
        ))),
    }
}

/// labels(node) - returns the labels of a node
pub fn labels(args: Vec<CypherValue>) -> ExecutionResult<CypherValue> {
    if args.len() != 1 {
        return Err(ExecutionError::InvalidArgument(
            "labels() requires exactly 1 argument".to_string(),
        ));
    }

    match &args[0] {
        CypherValue::Node(n) => Ok(CypherValue::List(
            n.labels.iter().cloned().map(CypherValue::String).collect(),
        )),
        CypherValue::Null => Ok(CypherValue::Null),
        other => Err(ExecutionError::Type(format!(
            "labels() requires a node, got {}",
            other.type_name()
        ))),
    }
}

/// keys(node|relationship|map) - returns the property keys
pub fn keys(args: Vec<CypherValue>) -> ExecutionResult<CypherValue> {
    if args.len() != 1 {
        return Err(ExecutionError::InvalidArgument(
            "keys() requires exactly 1 argument".to_string(),
        ));
    }

    // For nodes and relationships, keys() returns only keys with non-null values (Neo4j behavior)
    // For maps, keys() returns ALL keys including those with null values (per TCK Map3 test)
    match &args[0] {
        CypherValue::Node(n) => Ok(CypherValue::List(
            n.properties
                .iter()
                .filter(|(_, v)| !v.is_null())
                .map(|(k, _)| CypherValue::String(k.clone()))
                .collect(),
        )),
        CypherValue::Relationship(r) => Ok(CypherValue::List(
            r.properties
                .iter()
                .filter(|(_, v)| !v.is_null())
                .map(|(k, _)| CypherValue::String(k.clone()))
                .collect(),
        )),
        CypherValue::Map(m) => Ok(CypherValue::List(
            m.iter()
                .map(|(k, _)| CypherValue::String(k.clone()))
                .collect(),
        )),
        CypherValue::Null => Ok(CypherValue::Null),
        other => Err(ExecutionError::Type(format!(
            "keys() requires a node, relationship, or map, got {}",
            other.type_name()
        ))),
    }
}

/// properties(node|relationship|map) - returns the properties as a map
pub fn properties(args: Vec<CypherValue>) -> ExecutionResult<CypherValue> {
    if args.len() != 1 {
        return Err(ExecutionError::InvalidArgument(
            "properties() requires exactly 1 argument".to_string(),
        ));
    }

    match &args[0] {
        CypherValue::Node(n) => Ok(CypherValue::Map(n.properties.clone())),
        CypherValue::Relationship(r) => Ok(CypherValue::Map(r.properties.clone())),
        CypherValue::Map(m) => Ok(CypherValue::Map(m.clone())),
        CypherValue::Null => Ok(CypherValue::Null),
        other => Err(ExecutionError::Type(format!(
            "properties() requires a node, relationship, or map, got {}",
            other.type_name()
        ))),
    }
}

/// coalesce(v1, v2, ...) - returns the first non-null value
pub fn coalesce(args: Vec<CypherValue>) -> ExecutionResult<CypherValue> {
    for arg in args {
        if !arg.is_null() {
            return Ok(arg);
        }
    }
    Ok(CypherValue::Null)
}

/// timestamp() - returns the current timestamp in milliseconds
pub fn timestamp(args: Vec<CypherValue>) -> ExecutionResult<CypherValue> {
    if !args.is_empty() {
        return Err(ExecutionError::InvalidArgument(
            "timestamp() takes no arguments".to_string(),
        ));
    }

    let now = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map_err(|e| ExecutionError::Internal(e.to_string()))?;
    Ok(CypherValue::Integer(now.as_millis() as i64))
}

/// toInteger(value) - converts a value to an integer
pub fn to_integer(args: Vec<CypherValue>) -> ExecutionResult<CypherValue> {
    if args.len() != 1 {
        return Err(ExecutionError::InvalidArgument(
            "toInteger() requires exactly 1 argument".to_string(),
        ));
    }

    match &args[0] {
        CypherValue::Integer(i) => Ok(CypherValue::Integer(*i)),
        CypherValue::Float(f) => Ok(CypherValue::Integer(*f as i64)),
        CypherValue::String(s) => {
            s.parse::<i64>()
                .map(CypherValue::Integer)
                .or_else(|_| {
                    s.parse::<f64>()
                        .map(|f| CypherValue::Integer(f as i64))
                        .or(Ok(CypherValue::Null))  // Return null for invalid strings
                })
        }
        CypherValue::Boolean(b) => Ok(CypherValue::Integer(if *b { 1 } else { 0 })),
        CypherValue::Null => Ok(CypherValue::Null),
        other => Err(ExecutionError::Type(format!(
            "Cannot convert {} to integer",
            other.type_name()
        ))),
    }
}

/// toFloat(value) - converts a value to a float
pub fn to_float(args: Vec<CypherValue>) -> ExecutionResult<CypherValue> {
    if args.len() != 1 {
        return Err(ExecutionError::InvalidArgument(
            "toFloat() requires exactly 1 argument".to_string(),
        ));
    }

    match &args[0] {
        CypherValue::Integer(i) => Ok(CypherValue::Float(*i as f64)),
        CypherValue::Float(f) => Ok(CypherValue::Float(*f)),
        CypherValue::String(s) => {
            s.parse::<f64>()
                .map(CypherValue::Float)
                .or(Ok(CypherValue::Null))  // Return null for invalid strings
        }
        CypherValue::Null => Ok(CypherValue::Null),
        other => Err(ExecutionError::Type(format!(
            "Cannot convert {} to float",
            other.type_name()
        ))),
    }
}

/// toBoolean(value) - converts a value to a boolean
pub fn to_boolean(args: Vec<CypherValue>) -> ExecutionResult<CypherValue> {
    if args.len() != 1 {
        return Err(ExecutionError::InvalidArgument(
            "toBoolean() requires exactly 1 argument".to_string(),
        ));
    }

    match &args[0] {
        CypherValue::Boolean(b) => Ok(CypherValue::Boolean(*b)),
        CypherValue::String(s) => {
            // Empty string or only whitespace should return null according to TCK
            if s.is_empty() {
                return Ok(CypherValue::Null);
            }
            let lower = s.to_lowercase();
            match lower.as_str() {
                "true" => Ok(CypherValue::Boolean(true)),
                "false" => Ok(CypherValue::Boolean(false)),
                _ => Ok(CypherValue::Null),  // Invalid strings return null
            }
        }
        CypherValue::Integer(i) => Ok(CypherValue::Boolean(*i != 0)),
        CypherValue::Null => Ok(CypherValue::Null),
        other => Err(ExecutionError::Type(format!(
            "Cannot convert {} to boolean",
            other.type_name()
        ))),
    }
}

/// startNode(relationship) - returns the start node of a relationship
pub fn start_node(args: Vec<CypherValue>) -> ExecutionResult<CypherValue> {
    if args.len() != 1 {
        return Err(ExecutionError::InvalidArgument(
            "startNode() requires exactly 1 argument".to_string(),
        ));
    }

    match &args[0] {
        CypherValue::Relationship(r) => {
            // Create a minimal node with just the ID
            // In a real implementation, we'd fetch the full node from the graph
            Ok(CypherValue::Node(crate::result::NodeValue {
                id: r.start_node_id,
                labels: vec![],
                properties: indexmap::IndexMap::new(),
            }))
        }
        CypherValue::Null => Ok(CypherValue::Null),
        other => Err(ExecutionError::Type(format!(
            "startNode() requires a relationship, got {}",
            other.type_name()
        ))),
    }
}

/// endNode(relationship) - returns the end node of a relationship
pub fn end_node(args: Vec<CypherValue>) -> ExecutionResult<CypherValue> {
    if args.len() != 1 {
        return Err(ExecutionError::InvalidArgument(
            "endNode() requires exactly 1 argument".to_string(),
        ));
    }

    match &args[0] {
        CypherValue::Relationship(r) => {
            // Create a minimal node with just the ID
            // In a real implementation, we'd fetch the full node from the graph
            Ok(CypherValue::Node(crate::result::NodeValue {
                id: r.end_node_id,
                labels: vec![],
                properties: indexmap::IndexMap::new(),
            }))
        }
        CypherValue::Null => Ok(CypherValue::Null),
        other => Err(ExecutionError::Type(format!(
            "endNode() requires a relationship, got {}",
            other.type_name()
        ))),
    }
}

/// between(value, min, max) - checks if value is between min and max (inclusive)
/// Returns true if min <= value <= max, false otherwise
/// Supports integers, floats, strings, dates, times, and durations
pub fn between(args: Vec<CypherValue>) -> ExecutionResult<CypherValue> {
    if args.len() != 3 {
        return Err(ExecutionError::InvalidArgument(
            "between() requires 3 arguments: value, min, max".to_string(),
        ));
    }

    // NULL propagation: if any argument is NULL, return NULL
    if matches!(&args[0], CypherValue::Null)
        || matches!(&args[1], CypherValue::Null)
        || matches!(&args[2], CypherValue::Null)
    {
        return Ok(CypherValue::Null);
    }

    let value = &args[0];
    let min = &args[1];
    let max = &args[2];

    // Check that all three values are comparable (same type)
    match (value, min, max) {
        (CypherValue::Integer(v), CypherValue::Integer(min_val), CypherValue::Integer(max_val)) => {
            Ok(CypherValue::Boolean(*v >= *min_val && *v <= *max_val))
        }
        (CypherValue::Float(v), CypherValue::Float(min_val), CypherValue::Float(max_val)) => {
            Ok(CypherValue::Boolean(*v >= *min_val && *v <= *max_val))
        }
        // Allow mixed int/float comparisons
        (CypherValue::Integer(v), CypherValue::Float(min_val), CypherValue::Float(max_val)) => {
            let v_float = *v as f64;
            Ok(CypherValue::Boolean(v_float >= *min_val && v_float <= *max_val))
        }
        (CypherValue::Float(v), CypherValue::Integer(min_val), CypherValue::Integer(max_val)) => {
            let min_float = *min_val as f64;
            let max_float = *max_val as f64;
            Ok(CypherValue::Boolean(*v >= min_float && *v <= max_float))
        }
        (CypherValue::Integer(v), CypherValue::Integer(min_val), CypherValue::Float(max_val)) => {
            let v_float = *v as f64;
            let min_float = *min_val as f64;
            Ok(CypherValue::Boolean(v_float >= min_float && v_float <= *max_val))
        }
        (CypherValue::Integer(v), CypherValue::Float(min_val), CypherValue::Integer(max_val)) => {
            let v_float = *v as f64;
            let max_float = *max_val as f64;
            Ok(CypherValue::Boolean(v_float >= *min_val && v_float <= max_float))
        }
        (CypherValue::Float(v), CypherValue::Float(min_val), CypherValue::Integer(max_val)) => {
            let max_float = *max_val as f64;
            Ok(CypherValue::Boolean(*v >= *min_val && *v <= max_float))
        }
        (CypherValue::Float(v), CypherValue::Integer(min_val), CypherValue::Float(max_val)) => {
            let min_float = *min_val as f64;
            Ok(CypherValue::Boolean(*v >= min_float && *v <= *max_val))
        }
        (CypherValue::String(v), CypherValue::String(min_val), CypherValue::String(max_val)) => {
            Ok(CypherValue::Boolean(v >= min_val && v <= max_val))
        }
        (CypherValue::Date(v), CypherValue::Date(min_val), CypherValue::Date(max_val)) => {
            Ok(CypherValue::Boolean(v.date >= min_val.date && v.date <= max_val.date))
        }
        (CypherValue::DateTime(v), CypherValue::DateTime(min_val), CypherValue::DateTime(max_val)) => {
            Ok(CypherValue::Boolean(
                v.datetime >= min_val.datetime && v.datetime <= max_val.datetime,
            ))
        }
        (CypherValue::LocalDateTime(v), CypherValue::LocalDateTime(min_val), CypherValue::LocalDateTime(max_val)) => {
            Ok(CypherValue::Boolean(
                v.datetime >= min_val.datetime && v.datetime <= max_val.datetime,
            ))
        }
        (CypherValue::Time(v), CypherValue::Time(min_val), CypherValue::Time(max_val)) => {
            // Compare times considering offsets
            Ok(CypherValue::Boolean(
                v.time >= min_val.time && v.time <= max_val.time,
            ))
        }
        (CypherValue::LocalTime(v), CypherValue::LocalTime(min_val), CypherValue::LocalTime(max_val)) => {
            Ok(CypherValue::Boolean(
                v.time >= min_val.time && v.time <= max_val.time,
            ))
        }
        _ => Err(ExecutionError::Type(format!(
            "between() requires comparable values of the same type, got {}, {}, {}",
            value.type_name(),
            min.type_name(),
            max.type_name()
        ))),
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::result::NodeValue;
    use indexmap::IndexMap;

    #[test]
    fn test_id() {
        let node = CypherValue::Node(NodeValue {
            id: 42,
            labels: vec!["Person".to_string()],
            properties: IndexMap::new(),
        });
        assert_eq!(id(vec![node]).unwrap(), CypherValue::Integer(42));
    }

    #[test]
    fn test_coalesce() {
        assert_eq!(
            coalesce(vec![CypherValue::Null, CypherValue::Integer(5)]).unwrap(),
            CypherValue::Integer(5)
        );
        assert_eq!(
            coalesce(vec![CypherValue::Null, CypherValue::Null]).unwrap(),
            CypherValue::Null
        );
    }

    #[test]
    fn test_to_integer() {
        assert_eq!(
            to_integer(vec![CypherValue::String("42".to_string())]).unwrap(),
            CypherValue::Integer(42)
        );
        assert_eq!(
            to_integer(vec![CypherValue::Float(3.7)]).unwrap(),
            CypherValue::Integer(3)
        );
    }
}
