use crate::context::Context;
use crate::functions::FunctionContext;
use crate::ser::SerializationError;
use crate::ExecutionError::NoSuchKey;
use crate::{to_value, ExecutionError};
use cel_parser::{ArithmeticOp, Atom, Expression, Member, RelationOp, UnaryOp};
use chrono::{DateTime, Duration, FixedOffset};
use core::ops;
use serde::Serialize;
use std::cmp::Ordering;
use std::collections::HashMap;
use std::convert::{Infallible, TryFrom, TryInto};
use std::fmt::{Display, Formatter};
use std::rc::Rc;
use std::sync::Arc;
#[derive(Debug, PartialEq, Clone)]
pub struct Map {
    pub map: Rc<HashMap<Key, Value>>,
}
impl PartialOrd for Map {
    fn partial_cmp(&self, _: &Self) -> Option<Ordering> {
        None
    }
}
impl Map {
    pub fn get(&self, key: &Key) -> Option<&Value> {
        self.map.get(key).or_else(|| {
            let converted = match key {
                Key::Int(k) => Key::Uint(u64::try_from(*k).ok()?),
                Key::Uint(k) => Key::Int(i64::try_from(*k).ok()?),
                _ => return None,
            };
            self.map.get(&converted)
        })
    }
}
#[derive(Debug, Eq, PartialEq, Hash, Ord, Clone, PartialOrd)]
pub enum Key {
    Int(i64),
    Uint(u64),
    Bool(bool),
    String(Arc<String>),
}
impl From<String> for Key {
    fn from(v: String) -> Self {
        Key::String(v.into())
    }
}
impl From<Arc<String>> for Key {
    fn from(v: Arc<String>) -> Self {
        Key::String(v.clone())
    }
}
impl<'a> From<&'a str> for Key {
    fn from(v: &'a str) -> Self {
        Key::String(Arc::new(v.into()))
    }
}
impl From<bool> for Key {
    fn from(v: bool) -> Self {
        Key::Bool(v)
    }
}
impl From<i64> for Key {
    fn from(v: i64) -> Self {
        Key::Int(v)
    }
}
impl From<u64> for Key {
    fn from(v: u64) -> Self {
        Key::Uint(v)
    }
}
impl TryInto<Key> for Value {
    type Error = Value;
    #[inline(always)]
    fn try_into(self) -> Result<Key, Self::Error> {
        match self {
            Value::Int(v) => Ok(Key::Int(v)),
            Value::UInt(v) => Ok(Key::Uint(v)),
            Value::String(v) => Ok(Key::String(v)),
            Value::Bool(v) => Ok(Key::Bool(v)),
            _ => Err(self),
        }
    }
}
impl<K: Into<Key>, V: Into<Value>> From<HashMap<K, V>> for Map {
    fn from(map: HashMap<K, V>) -> Self {
        let mut new_map = HashMap::new();
        for (k, v) in map {
            new_map.insert(k.into(), v.into());
        }
        Map {
            map: Rc::new(new_map),
        }
    }
}
pub trait TryIntoValue {
    type Error: std::error::Error + 'static;
    fn try_into_value(self) -> Result<Value, Self::Error>;
}
impl TryIntoValue for Value {
    type Error = Infallible;
    fn try_into_value(self) -> Result<Value, Self::Error> {
        Ok(self)
    }
}
impl TryIntoValue for Key {
    type Error = Infallible;
    fn try_into_value(self) -> Result<Value, Self::Error> {
        Ok(self.clone().into())
    }
}
impl<T: Serialize> TryIntoValue for T {
    type Error = SerializationError;
    fn try_into_value(self) -> Result<Value, Self::Error> {
        to_value(self)
    }
}
impl TryIntoValue for &Value {
    type Error = Infallible;
    fn try_into_value(self) -> Result<Value, Self::Error> {
        Ok(self.clone())
    }
}
impl TryIntoValue for &Key {
    type Error = Infallible;
    fn try_into_value(self) -> Result<Value, Self::Error> {
        Ok(self.clone().into())
    }
}
#[derive(Debug, Clone)]
pub enum Value {
    List(Arc<Vec<Value>>),
    Map(Map),
    Function(Arc<String>, Option<Box<Value>>),
    Int(i64),
    UInt(u64),
    Float(f64),
    String(Arc<String>),
    Bytes(Arc<Vec<u8>>),
    Bool(bool),
    Duration(Duration),
    Timestamp(DateTime<FixedOffset>),
    Null,
}
pub enum ValueType {
    List,
    Map,
    Function,
    Int,
    UInt,
    Float,
    String,
    Bytes,
    Bool,
    Duration,
    Timestamp,
    Null,
}
impl Display for ValueType {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            ValueType::List => write!(f, "list"),
            ValueType::Map => write!(f, "map"),
            ValueType::Function => write!(f, "function"),
            ValueType::Int => write!(f, "int"),
            ValueType::UInt => write!(f, "uint"),
            ValueType::Float => write!(f, "float"),
            ValueType::String => write!(f, "string"),
            ValueType::Bytes => write!(f, "bytes"),
            ValueType::Bool => write!(f, "bool"),
            ValueType::Duration => write!(f, "duration"),
            ValueType::Timestamp => write!(f, "timestamp"),
            ValueType::Null => write!(f, "null"),
        }
    }
}
impl Value {
    pub fn type_of(&self) -> ValueType {
        match self {
            Value::List(_) => ValueType::List,
            Value::Map(_) => ValueType::Map,
            Value::Function(_, _) => ValueType::Function,
            Value::Int(_) => ValueType::Int,
            Value::UInt(_) => ValueType::UInt,
            Value::Float(_) => ValueType::Float,
            Value::String(_) => ValueType::String,
            Value::Bytes(_) => ValueType::Bytes,
            Value::Bool(_) => ValueType::Bool,
            Value::Duration(_) => ValueType::Duration,
            Value::Timestamp(_) => ValueType::Timestamp,
            Value::Null => ValueType::Null,
        }
    }
    pub fn error_expected_type(&self, expected: ValueType) -> ExecutionError {
        ExecutionError::UnexpectedType {
            got: self.type_of().to_string(),
            want: expected.to_string(),
        }
    }
}
impl From<&Value> for Value {
    fn from(value: &Value) -> Self {
        value.clone()
    }
}
impl PartialEq for Value {
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (Value::Map(a), Value::Map(b)) => a == b,
            (Value::List(a), Value::List(b)) => a == b,
            (Value::Function(a1, a2), Value::Function(b1, b2)) => a1 == b1 && a2 == b2,
            (Value::Int(a), Value::Int(b)) => a == b,
            (Value::UInt(a), Value::UInt(b)) => a == b,
            (Value::Float(a), Value::Float(b)) => a == b,
            (Value::String(a), Value::String(b)) => a == b,
            (Value::Bytes(a), Value::Bytes(b)) => a == b,
            (Value::Bool(a), Value::Bool(b)) => a == b,
            (Value::Null, Value::Null) => true,
            (Value::Duration(a), Value::Duration(b)) => a == b,
            (Value::Timestamp(a), Value::Timestamp(b)) => a == b,
            (Value::Int(a), Value::UInt(b)) => a
                .to_owned()
                .try_into()
                .and_then(|a: u64| Ok(a == *b))
                .unwrap_or(false),
            (Value::Int(a), Value::Float(b)) => (*a as f64) == *b,
            (Value::UInt(a), Value::Int(b)) => a
                .to_owned()
                .try_into()
                .and_then(|a: i64| Ok(a == *b))
                .unwrap_or(false),
            (Value::UInt(a), Value::Float(b)) => (*a as f64) == *b,
            (Value::Float(a), Value::Int(b)) => *a == (*b as f64),
            (Value::Float(a), Value::UInt(b)) => *a == (*b as f64),
            (a, b) => panic!("unable to compare {:?} with {:?}", a, b),
        }
    }
}
impl Eq for Value {}
impl PartialOrd for Value {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        match (self, other) {
            (Value::Int(a), Value::Int(b)) => Some(a.cmp(b)),
            (Value::UInt(a), Value::UInt(b)) => Some(a.cmp(b)),
            (Value::Float(a), Value::Float(b)) => a.partial_cmp(b),
            (Value::String(a), Value::String(b)) => Some(a.cmp(b)),
            (Value::Bool(a), Value::Bool(b)) => Some(a.cmp(b)),
            (Value::Null, Value::Null) => Some(Ordering::Equal),
            (Value::Duration(a), Value::Duration(b)) => Some(a.cmp(b)),
            (Value::Timestamp(a), Value::Timestamp(b)) => Some(a.cmp(b)),
            (Value::Int(a), Value::UInt(b)) => Some(
                a.to_owned()
                    .try_into()
                    .and_then(|a: u64| Ok(a.cmp(b)))
                    .unwrap_or(Ordering::Less),
            ),
            (Value::Int(a), Value::Float(b)) => (*a as f64).partial_cmp(b),
            (Value::UInt(a), Value::Int(b)) => Some(
                a.to_owned()
                    .try_into()
                    .and_then(|a: i64| Ok(a.cmp(b)))
                    .unwrap_or(Ordering::Greater),
            ),
            (Value::UInt(a), Value::Float(b)) => (*a as f64).partial_cmp(b),
            (Value::Float(a), Value::Int(b)) => a.partial_cmp(&(*b as f64)),
            (Value::Float(a), Value::UInt(b)) => a.partial_cmp(&(*b as f64)),
            (a, b) => panic!("unable to compare {:?} with {:?}", a, b),
        }
    }
}
impl From<&Key> for Value {
    fn from(value: &Key) -> Self {
        match value {
            Key::Int(v) => Value::Int(*v),
            Key::Uint(v) => Value::UInt(*v),
            Key::Bool(v) => Value::Bool(*v),
            Key::String(v) => Value::String(v.clone()),
        }
    }
}
impl From<Key> for Value {
    fn from(value: Key) -> Self {
        match value {
            Key::Int(v) => Value::Int(v),
            Key::Uint(v) => Value::UInt(v),
            Key::Bool(v) => Value::Bool(v),
            Key::String(v) => Value::String(v),
        }
    }
}
impl From<&Key> for Key {
    fn from(key: &Key) -> Self {
        key.clone()
    }
}
impl<T: Into<Value>> From<Vec<T>> for Value {
    fn from(v: Vec<T>) -> Self {
        Value::List(v.into_iter().map(|v| v.into()).collect::<Vec<_>>().into())
    }
}
impl From<Vec<u8>> for Value {
    fn from(v: Vec<u8>) -> Self {
        Value::Bytes(v.into())
    }
}
impl From<String> for Value {
    fn from(v: String) -> Self {
        Value::String(v.into())
    }
}
impl From<&str> for Value {
    fn from(v: &str) -> Self {
        Value::String(v.to_string().into())
    }
}
impl<T: Into<Value>> From<Option<T>> for Value {
    fn from(v: Option<T>) -> Self {
        match v {
            Some(v) => v.into(),
            None => Value::Null,
        }
    }
}
impl<K: Into<Key>, V: Into<Value>> From<HashMap<K, V>> for Value {
    fn from(v: HashMap<K, V>) -> Self {
        Value::Map(v.into())
    }
}
impl From<ExecutionError> for ResolveResult {
    fn from(value: ExecutionError) -> Self {
        Err(value)
    }
}
pub type ResolveResult = Result<Value, ExecutionError>;
impl From<Value> for ResolveResult {
    fn from(value: Value) -> Self {
        Ok(value)
    }
}
impl<'a> Value {
    pub fn resolve_all(expr: &[Expression], ctx: &Context) -> ResolveResult {
        let mut res = Vec::with_capacity(expr.len());
        for expr in expr {
            res.push(Value::resolve(expr, ctx)?);
        }
        Ok(Value::List(res.into()))
    }
    #[inline(always)]
    pub fn resolve(expr: &'a Expression, ctx: &Context) -> ResolveResult {
        match expr {
            Expression::Atom(atom) => Ok(atom.into()),
            Expression::Arithmetic(left, op, right) => {
                let left = Value::resolve(left, ctx)?;
                let right = Value::resolve(right, ctx)?;
                match op {
                    ArithmeticOp::Add => left + right,
                    ArithmeticOp::Subtract => left - right,
                    ArithmeticOp::Divide => left / right,
                    ArithmeticOp::Multiply => left * right,
                    ArithmeticOp::Modulus => left % right,
                }
                .into()
            }
            Expression::Relation(left, op, right) => {
                let left = Value::resolve(left, ctx)?;
                let right = Value::resolve(right, ctx)?;
                let res = match op {
                    RelationOp::LessThan => {
                        left.partial_cmp(&right)
                            .ok_or(ExecutionError::ValuesNotComparable(left, right))?
                            == Ordering::Less
                    }
                    RelationOp::LessThanEq => {
                        left.partial_cmp(&right)
                            .ok_or(ExecutionError::ValuesNotComparable(left, right))?
                            != Ordering::Greater
                    }
                    RelationOp::GreaterThan => {
                        left.partial_cmp(&right)
                            .ok_or(ExecutionError::ValuesNotComparable(left, right))?
                            == Ordering::Greater
                    }
                    RelationOp::GreaterThanEq => {
                        left.partial_cmp(&right)
                            .ok_or(ExecutionError::ValuesNotComparable(left, right))?
                            != Ordering::Less
                    }
                    RelationOp::Equals => right.eq(&left),
                    RelationOp::NotEquals => right.ne(&left),
                    RelationOp::In => match (left, right) {
                        (Value::String(l), Value::String(r)) => r.contains(&*l),
                        (any, Value::List(v)) => v.contains(&any),
                        (any, Value::Map(m)) => m.map.contains_key(&any.try_into().unwrap()),
                        _ => unimplemented!(),
                    },
                };
                Value::Bool(res).into()
            }
            Expression::Ternary(cond, left, right) => {
                let cond = Value::resolve(cond, ctx)?;
                if cond.to_bool() {
                    Value::resolve(left, ctx)
                } else {
                    Value::resolve(right, ctx)
                }
            }
            Expression::Or(left, right) => {
                let left = Value::resolve(left, ctx)?;
                if left.to_bool() {
                    left.into()
                } else {
                    Value::resolve(right, ctx)
                }
            }
            Expression::And(left, right) => {
                let left = Value::resolve(left, ctx)?;
                let right = Value::resolve(right, ctx)?;
                Value::Bool(left.to_bool() && right.to_bool()).into()
            }
            Expression::Unary(op, expr) => {
                let expr = Value::resolve(expr, ctx)?;
                match op {
                    UnaryOp::Not => Value::Bool(!expr.to_bool()),
                    UnaryOp::DoubleNot => Value::Bool(expr.to_bool()),
                    UnaryOp::Minus => match expr {
                        Value::Int(i) => Value::Int(-i),
                        Value::Float(i) => Value::Float(-i),
                        _ => unimplemented!(),
                    },
                    UnaryOp::DoubleMinus => match expr {
                        Value::Int(_) => expr,
                        Value::UInt(_) => expr,
                        Value::Float(_) => expr,
                        _ => unimplemented!(),
                    },
                }
                .into()
            }
            Expression::Member(left, right) => {
                let left = Value::resolve(left, ctx)?;
                left.member(right, ctx)
            }
            Expression::List(items) => {
                let list = items
                    .iter()
                    .map(|i| Value::resolve(i, ctx))
                    .collect::<Result<Vec<_>, _>>()?;
                Value::List(list.into()).into()
            }
            Expression::Map(items) => {
                let mut map = HashMap::default();
                for (k, v) in items.iter() {
                    let key = Value::resolve(k, ctx)?
                        .try_into()
                        .map_err(ExecutionError::UnsupportedKeyType)?;
                    let value = Value::resolve(v, ctx)?;
                    map.insert(key, value);
                }
                Value::Map(Map { map: Rc::from(map) }).into()
            }
            Expression::Ident(name) => {
                if ctx.has_function(&***name) {
                    Value::Function(name.clone(), None).into()
                } else {
                    ctx.get_variable(&***name)
                }
            }
        }
    }
    fn member(self, member: &Member, ctx: &Context) -> ResolveResult {
        match member {
            Member::Index(idx) => {
                let idx = Value::resolve(idx, ctx)?;
                match (self, idx) {
                    (Value::List(items), Value::Int(idx)) => {
                        items.get(idx as usize).unwrap().clone().into()
                    }
                    (Value::String(str), Value::Int(idx)) => {
                        match str.get(idx as usize..(idx + 1) as usize) {
                            None => Value::Null,
                            Some(str) => Value::String(str.to_string().into()),
                        }
                        .into()
                    }
                    (Value::Map(map), Value::String(property)) => map
                        .get(&property.into())
                        .cloned()
                        .unwrap_or(Value::Null)
                        .into(),
                    (Value::Map(map), Value::Bool(property)) => map
                        .get(&property.into())
                        .cloned()
                        .unwrap_or(Value::Null)
                        .into(),
                    (Value::Map(map), Value::Int(property)) => map
                        .get(&property.into())
                        .cloned()
                        .unwrap_or(Value::Null)
                        .into(),
                    (Value::Map(map), Value::UInt(property)) => map
                        .get(&property.into())
                        .cloned()
                        .unwrap_or(Value::Null)
                        .into(),
                    _ => unimplemented!(),
                }
            }
            Member::Fields(_) => unimplemented!(),
            Member::Attribute(name) => {
                let child = match self {
                    Value::Map(ref m) => m.map.get(&name.clone().into()).cloned(),
                    _ => None,
                };
                match (child.is_some(), ctx.has_function(&***name)) {
                    (false, false) => NoSuchKey(name.clone()).into(),
                    (true, true) | (true, false) => child.unwrap().into(),
                    (false, true) => Value::Function(name.clone(), Some(self.into())).into(),
                }
            }
            Member::FunctionCall(args) => {
                if let Value::Function(name, target) = self {
                    let func = ctx.get_function(&**name).unwrap();
                    match target {
                        None => {
                            let mut ctx =
                                FunctionContext::new(name.clone(), None, ctx, args.clone());
                            func.call_with_context(&mut ctx)
                        }
                        Some(target) => {
                            let mut ctx = FunctionContext::new(
                                name.clone(),
                                Some(*target),
                                ctx,
                                args.clone(),
                            );
                            func.call_with_context(&mut ctx)
                        }
                    }
                } else {
                    unreachable!("FunctionCall without Value::Function - {:?}", self)
                }
            }
        }
    }
    #[inline(always)]
    fn to_bool(&self) -> bool {
        match self {
            Value::List(v) => !v.is_empty(),
            Value::Map(v) => !v.map.is_empty(),
            Value::Int(v) => *v != 0,
            Value::UInt(v) => *v != 0,
            Value::Float(v) => *v != 0.0,
            Value::String(v) => !v.is_empty(),
            Value::Bytes(v) => !v.is_empty(),
            Value::Bool(v) => *v,
            Value::Null => false,
            Value::Duration(v) => v.num_nanoseconds().map(|n| n != 0).unwrap_or(false),
            Value::Timestamp(v) => v.timestamp_nanos() > 0,
            Value::Function(_, _) => false,
        }
    }
}
impl From<&Atom> for Value {
    #[inline(always)]
    fn from(atom: &Atom) -> Self {
        match atom {
            Atom::Int(v) => Value::Int(*v),
            Atom::UInt(v) => Value::UInt(*v),
            Atom::Float(v) => Value::Float(*v),
            Atom::String(v) => Value::String(v.clone()),
            Atom::Bytes(v) => Value::Bytes(v.clone()),
            Atom::Bool(v) => Value::Bool(*v),
            Atom::Null => Value::Null,
        }
    }
}
impl ops::Add<Value> for Value {
    type Output = Value;
    #[inline(always)]
    fn add(self, rhs: Value) -> Self::Output {
        match (self, rhs) {
            (Value::Int(l), Value::Int(r)) => Value::Int(l + r),
            (Value::UInt(l), Value::UInt(r)) => Value::UInt(l + r),
            (Value::Float(l), Value::Float(r)) => Value::Float(l + r),
            (Value::Int(l), Value::Float(r)) => Value::Float(l as f64 + r),
            (Value::Float(l), Value::Int(r)) => Value::Float(l + r as f64),
            (Value::UInt(l), Value::Float(r)) => Value::Float(l as f64 + r),
            (Value::Float(l), Value::UInt(r)) => Value::Float(l + r as f64),
            (Value::List(l), Value::List(r)) => {
                Value::List(l.iter().chain(r.iter()).cloned().collect::<Vec<_>>().into())
            }
            (Value::String(l), Value::String(r)) => {
                let mut new = String::with_capacity(l.len() + r.len());
                new.push_str(&l);
                new.push_str(&r);
                Value::String(new.into())
            }
            (Value::Map(l), Value::Map(r)) => {
                let mut new = HashMap::default();
                for (k, v) in l.map.iter() {
                    new.insert(k.clone(), v.clone());
                }
                for (k, v) in r.map.iter() {
                    new.insert(k.clone(), v.clone());
                }
                Value::Map(Map { map: Rc::new(new) })
            }
            (Value::Duration(l), Value::Duration(r)) => Value::Duration(l + r),
            (Value::Timestamp(l), Value::Duration(r)) => Value::Timestamp(l + r),
            (Value::Duration(l), Value::Timestamp(r)) => Value::Timestamp(r + l),
            _ => unimplemented!(),
        }
    }
}
impl ops::Sub<Value> for Value {
    type Output = Value;
    #[inline(always)]
    fn sub(self, rhs: Value) -> Self::Output {
        match (self, rhs) {
            (Value::Int(l), Value::Int(r)) => Value::Int(l - r),
            (Value::UInt(l), Value::UInt(r)) => Value::UInt(l - r),
            (Value::Float(l), Value::Float(r)) => Value::Float(l - r),
            (Value::Int(l), Value::Float(r)) => Value::Float(l as f64 - r),
            (Value::Float(l), Value::Int(r)) => Value::Float(l - r as f64),
            (Value::UInt(l), Value::Float(r)) => Value::Float(l as f64 - r),
            (Value::Float(l), Value::UInt(r)) => Value::Float(l - r as f64),
            (Value::Duration(l), Value::Duration(r)) => Value::Duration(l - r),
            (Value::Timestamp(l), Value::Duration(r)) => Value::Timestamp(l - r),
            (Value::Timestamp(l), Value::Timestamp(r)) => Value::Duration(l - r),
            _ => unimplemented!(),
        }
    }
}
impl ops::Div<Value> for Value {
    type Output = Value;
    #[inline(always)]
    fn div(self, rhs: Value) -> Self::Output {
        match (self, rhs) {
            (Value::Int(l), Value::Int(r)) => Value::Int(l / r),
            (Value::UInt(l), Value::UInt(r)) => Value::UInt(l / r),
            (Value::Float(l), Value::Float(r)) => Value::Float(l / r),
            (Value::Int(l), Value::Float(r)) => Value::Float(l as f64 / r),
            (Value::Float(l), Value::Int(r)) => Value::Float(l / r as f64),
            (Value::UInt(l), Value::Float(r)) => Value::Float(l as f64 / r),
            (Value::Float(l), Value::UInt(r)) => Value::Float(l / r as f64),
            _ => unimplemented!(),
        }
    }
}
impl ops::Mul<Value> for Value {
    type Output = Value;
    #[inline(always)]
    fn mul(self, rhs: Value) -> Self::Output {
        match (self, rhs) {
            (Value::Int(l), Value::Int(r)) => Value::Int(l * r),
            (Value::UInt(l), Value::UInt(r)) => Value::UInt(l * r),
            (Value::Float(l), Value::Float(r)) => Value::Float(l * r),
            (Value::Int(l), Value::Float(r)) => Value::Float(l as f64 * r),
            (Value::Float(l), Value::Int(r)) => Value::Float(l * r as f64),
            (Value::UInt(l), Value::Float(r)) => Value::Float(l as f64 * r),
            (Value::Float(l), Value::UInt(r)) => Value::Float(l * r as f64),
            _ => unimplemented!(),
        }
    }
}
impl ops::Rem<Value> for Value {
    type Output = Value;
    #[inline(always)]
    fn rem(self, rhs: Value) -> Self::Output {
        match (self, rhs) {
            (Value::Int(l), Value::Int(r)) => Value::Int(l % r),
            (Value::UInt(l), Value::UInt(r)) => Value::UInt(l % r),
            (Value::Float(l), Value::Float(r)) => Value::Float(l % r),
            (Value::Int(l), Value::Float(r)) => Value::Float(l as f64 % r),
            (Value::Float(l), Value::Int(r)) => Value::Float(l % r as f64),
            (Value::UInt(l), Value::Float(r)) => Value::Float(l as f64 % r),
            (Value::Float(l), Value::UInt(r)) => Value::Float(l % r as f64),
            _ => unimplemented!(),
        }
    }
}
#[cfg(test)]
mod tests {
    use crate::{objects::Key, Context, Program};
    use std::collections::HashMap;
    #[test]
    fn test_indexed_map_access() {
        let mut context = Context::default();
        let mut headers = HashMap::new();
        headers.insert("Content-Type", "application/json".to_string());
        context.add_variable_from_value("headers", headers);
        let program = Program::compile("headers[\"Content-Type\"]").unwrap();
        let value = program.execute(&context).unwrap();
        assert_eq!(value, "application/json".into());
    }
    #[test]
    fn test_numeric_map_access() {
        let mut context = Context::default();
        let mut numbers = HashMap::new();
        numbers.insert(Key::Uint(1), "one".to_string());
        context.add_variable_from_value("numbers", numbers);
        let program = Program::compile("numbers[1]").unwrap();
        let value = program.execute(&context).unwrap();
        assert_eq!(value, "one".into());
    }
    #[test]
    fn test_heterogeneous_compare() {
        let context = Context::default();
        let program = Program::compile("1 < uint(2)").unwrap();
        let value = program.execute(&context).unwrap();
        assert_eq!(value, true.into());
        let program = Program::compile("1 < 1.1").unwrap();
        let value = program.execute(&context).unwrap();
        assert_eq!(value, true.into());
        let program = Program::compile("uint(0) > -10").unwrap();
        let value = program.execute(&context).unwrap();
        assert_eq!(
            value,
            true.into(),
            "negative signed ints should be less than uints"
        );
    }
    #[test]
    fn test_float_compare() {
        let context = Context::default();
        let program = Program::compile("1.0 > 0.0").unwrap();
        let value = program.execute(&context).unwrap();
        assert_eq!(value, true.into());
        let program = Program::compile("double('NaN') == double('NaN')").unwrap();
        let value = program.execute(&context).unwrap();
        assert_eq!(value, false.into(), "NaN should not equal itself");
        let program = Program::compile("1.0 > double('NaN')").unwrap();
        let result = program.execute(&context);
        assert!(
            result.is_err(),
            "NaN should not be comparable with inequality operators"
        );
    }
}