// Copyright 2024 The Drasi Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::sync::Arc;

use crate::evaluation::functions::{
    Function, LTrim, Left, RTrim, RandomUUID, Replace, Reverse, Right, Split, Substring, ToLower,
    ToString, ToUpper, Trim,
};
use crate::evaluation::variable_value::VariableValue;

use crate::evaluation::context::QueryVariables;
use crate::evaluation::functions::FunctionRegistry;
use crate::evaluation::{ExpressionEvaluationContext, ExpressionEvaluator, InstantQueryClock};

use crate::in_memory_index::in_memory_result_index::InMemoryResultIndex;

fn create_string_expression_test_function_registry() -> Arc<FunctionRegistry> {
    let registry = Arc::new(FunctionRegistry::new());

    registry.register_function("left", Function::Scalar(Arc::new(Left {})));
    registry.register_function("ltrim", Function::Scalar(Arc::new(LTrim {})));
    registry.register_function("replace", Function::Scalar(Arc::new(Replace {})));
    registry.register_function("reverse", Function::Scalar(Arc::new(Reverse {})));
    registry.register_function("right", Function::Scalar(Arc::new(Right {})));
    registry.register_function("rtrim", Function::Scalar(Arc::new(RTrim {})));
    registry.register_function("split", Function::Scalar(Arc::new(Split {})));
    registry.register_function("substring", Function::Scalar(Arc::new(Substring {})));
    registry.register_function("toLower", Function::Scalar(Arc::new(ToLower {})));
    registry.register_function("toString", Function::Scalar(Arc::new(ToString {})));
    registry.register_function("toUpper", Function::Scalar(Arc::new(ToUpper {})));
    registry.register_function("trim", Function::Scalar(Arc::new(Trim {})));
    registry.register_function("randomUUID", Function::Scalar(Arc::new(RandomUUID {})));

    registry
}

#[tokio::test]
async fn evaluate_left() {
    let expr = "left('drasi', 3)";
    let expr = drasi_query_cypher::parse_expression(expr).unwrap();

    let function_registry = create_string_expression_test_function_registry();
    let ari = Arc::new(InMemoryResultIndex::new());
    let evaluator = ExpressionEvaluator::new(function_registry.clone(), ari.clone());

    let variables = QueryVariables::new();
    {
        let context =
            ExpressionEvaluationContext::new(&variables, Arc::new(InstantQueryClock::new(0, 0)));
        assert_eq!(
            evaluator
                .evaluate_expression(&context, &expr)
                .await
                .unwrap(),
            VariableValue::String("dra".to_string())
        );
    }

    let expr = "left(NULL, 10)";
    let expr = drasi_query_cypher::parse_expression(expr).unwrap();
    {
        let context =
            ExpressionEvaluationContext::new(&variables, Arc::new(InstantQueryClock::new(0, 0)));
        assert_eq!(
            evaluator
                .evaluate_expression(&context, &expr)
                .await
                .unwrap(),
            VariableValue::Null
        );
    }
}

#[tokio::test]
async fn evaluate_ltrim() {
    let expr = "ltrim('   drasi   ')";
    let expr = drasi_query_cypher::parse_expression(expr).unwrap();

    let function_registry = create_string_expression_test_function_registry();
    let ari = Arc::new(InMemoryResultIndex::new());
    let evaluator = ExpressionEvaluator::new(function_registry.clone(), ari.clone());

    let variables = QueryVariables::new();
    {
        let context =
            ExpressionEvaluationContext::new(&variables, Arc::new(InstantQueryClock::new(0, 0)));
        assert_eq!(
            evaluator
                .evaluate_expression(&context, &expr)
                .await
                .unwrap(),
            VariableValue::String("drasi   ".to_string())
        );
    }

    let expr = "ltrim(   NULL)";
    let expr = drasi_query_cypher::parse_expression(expr).unwrap();
    {
        let context =
            ExpressionEvaluationContext::new(&variables, Arc::new(InstantQueryClock::new(0, 0)));
        assert_eq!(
            evaluator
                .evaluate_expression(&context, &expr)
                .await
                .unwrap(),
            VariableValue::Null
        );
    }
}

#[tokio::test]
async fn evaluate_replace() {
    let expr = "replace('hello', 'l', 'x')";
    let expr = drasi_query_cypher::parse_expression(expr).unwrap();

    let function_registry = create_string_expression_test_function_registry();
    let ari = Arc::new(InMemoryResultIndex::new());
    let evaluator = ExpressionEvaluator::new(function_registry.clone(), ari.clone());

    let variables = QueryVariables::new();
    {
        let context =
            ExpressionEvaluationContext::new(&variables, Arc::new(InstantQueryClock::new(0, 0)));
        assert_eq!(
            evaluator
                .evaluate_expression(&context, &expr)
                .await
                .unwrap(),
            VariableValue::String("hexxo".to_string())
        );
    }

    let expr =
        "replace('-reactive-graph is ...? reactive-graph can xxxx', 'reactive-graph', 'drasi')";
    let expr = drasi_query_cypher::parse_expression(expr).unwrap();
    {
        let context =
            ExpressionEvaluationContext::new(&variables, Arc::new(InstantQueryClock::new(0, 0)));
        assert_eq!(
            evaluator
                .evaluate_expression(&context, &expr)
                .await
                .unwrap(),
            VariableValue::String("-drasi is ...? drasi can xxxx".to_string())
        );
    }

    let expr = "replace('drasi', 'e', 't')";
    let expr = drasi_query_cypher::parse_expression(expr).unwrap();
    {
        let context =
            ExpressionEvaluationContext::new(&variables, Arc::new(InstantQueryClock::new(0, 0)));
        assert_eq!(
            evaluator
                .evaluate_expression(&context, &expr)
                .await
                .unwrap(),
            VariableValue::String("drasi".to_string())
        );
    }

    let expr = "replace(NULL, 'e', 't')";
    let expr = drasi_query_cypher::parse_expression(expr).unwrap();
    {
        let context =
            ExpressionEvaluationContext::new(&variables, Arc::new(InstantQueryClock::new(0, 0)));
        assert_eq!(
            evaluator
                .evaluate_expression(&context, &expr)
                .await
                .unwrap(),
            VariableValue::Null
        );
    }
}

#[tokio::test]
async fn evaluate_reverse() {
    let expr = "reverse('drasi')";
    let expr = drasi_query_cypher::parse_expression(expr).unwrap();

    let function_registry = create_string_expression_test_function_registry();
    let ari = Arc::new(InMemoryResultIndex::new());
    let evaluator = ExpressionEvaluator::new(function_registry.clone(), ari.clone());

    let variables = QueryVariables::new();
    {
        let context =
            ExpressionEvaluationContext::new(&variables, Arc::new(InstantQueryClock::new(0, 0)));
        assert_eq!(
            evaluator
                .evaluate_expression(&context, &expr)
                .await
                .unwrap(),
            VariableValue::String("isard".to_string())
        );
    }

    let expr = "reverse(  NULL )";
    let expr = drasi_query_cypher::parse_expression(expr).unwrap();
    {
        let context =
            ExpressionEvaluationContext::new(&variables, Arc::new(InstantQueryClock::new(0, 0)));
        assert_eq!(
            evaluator
                .evaluate_expression(&context, &expr)
                .await
                .unwrap(),
            VariableValue::Null
        );
    }
}

#[tokio::test]
async fn evaluate_right() {
    let expr = "right('drasi', 3)";
    let expr = drasi_query_cypher::parse_expression(expr).unwrap();

    let function_registry = create_string_expression_test_function_registry();
    let ari = Arc::new(InMemoryResultIndex::new());
    let evaluator = ExpressionEvaluator::new(function_registry.clone(), ari.clone());

    let variables = QueryVariables::new();
    {
        let context =
            ExpressionEvaluationContext::new(&variables, Arc::new(InstantQueryClock::new(0, 0)));
        assert_eq!(
            evaluator
                .evaluate_expression(&context, &expr)
                .await
                .unwrap(),
            VariableValue::String("asi".to_string())
        );
    }

    let expr = "right(null, 10)";
    let expr = drasi_query_cypher::parse_expression(expr).unwrap();
    {
        let context =
            ExpressionEvaluationContext::new(&variables, Arc::new(InstantQueryClock::new(0, 0)));
        assert_eq!(
            evaluator
                .evaluate_expression(&context, &expr)
                .await
                .unwrap(),
            VariableValue::Null
        );
    }
}

#[tokio::test]
async fn evaluate_rtrim() {
    let expr = "rtrim('   drasi   ')";
    let expr = drasi_query_cypher::parse_expression(expr).unwrap();

    let function_registry = create_string_expression_test_function_registry();
    let ari = Arc::new(InMemoryResultIndex::new());
    let evaluator = ExpressionEvaluator::new(function_registry.clone(), ari.clone());

    let variables = QueryVariables::new();
    {
        let context =
            ExpressionEvaluationContext::new(&variables, Arc::new(InstantQueryClock::new(0, 0)));
        assert_eq!(
            evaluator
                .evaluate_expression(&context, &expr)
                .await
                .unwrap(),
            VariableValue::String("   drasi".to_string())
        );
    }

    let expr = "rtrim(   NULL)";
    let expr = drasi_query_cypher::parse_expression(expr).unwrap();
    {
        let context =
            ExpressionEvaluationContext::new(&variables, Arc::new(InstantQueryClock::new(0, 0)));
        assert_eq!(
            evaluator
                .evaluate_expression(&context, &expr)
                .await
                .unwrap(),
            VariableValue::Null
        );
    }
}

#[tokio::test]
async fn evaluate_split() {
    let expr = "split('hello,world', ',')";
    let expr = drasi_query_cypher::parse_expression(expr).unwrap();

    let function_registry = create_string_expression_test_function_registry();
    let ari = Arc::new(InMemoryResultIndex::new());
    let evaluator = ExpressionEvaluator::new(function_registry.clone(), ari.clone());

    let variables = QueryVariables::new();
    {
        let context =
            ExpressionEvaluationContext::new(&variables, Arc::new(InstantQueryClock::new(0, 0)));
        assert_eq!(
            evaluator
                .evaluate_expression(&context, &expr)
                .await
                .unwrap(),
            VariableValue::List(vec![
                VariableValue::String("hello".to_string()),
                VariableValue::String("world".to_string())
            ])
        );
    }

    let expr = "split('hello||world|| this is a test|| reactive-graph|| kubectl', '||')";
    let expr = drasi_query_cypher::parse_expression(expr).unwrap();
    {
        let context =
            ExpressionEvaluationContext::new(&variables, Arc::new(InstantQueryClock::new(0, 0)));
        assert_eq!(
            evaluator
                .evaluate_expression(&context, &expr)
                .await
                .unwrap(),
            VariableValue::List(vec![
                VariableValue::String("hello".to_string()),
                VariableValue::String("world".to_string()),
                VariableValue::String(" this is a test".to_string()),
                VariableValue::String(" reactive-graph".to_string()),
                VariableValue::String(" kubectl".to_string())
            ])
        );
    }

    let expr = "split('hello||world|| this is&&a test||reactive-&&-graph|| kubectl', '||')";
    let expr = drasi_query_cypher::parse_expression(expr).unwrap();
    {
        let context =
            ExpressionEvaluationContext::new(&variables, Arc::new(InstantQueryClock::new(0, 0)));
        assert_eq!(
            evaluator
                .evaluate_expression(&context, &expr)
                .await
                .unwrap(),
            VariableValue::List(vec![
                VariableValue::String("hello".to_string()),
                VariableValue::String("world".to_string()),
                VariableValue::String(" this is&&a test".to_string()),
                VariableValue::String("reactive-&&-graph".to_string()),
                VariableValue::String(" kubectl".to_string())
            ])
        );
    }

    let expr = "split(NULL, '||')";
    let expr = drasi_query_cypher::parse_expression(expr).unwrap();
    {
        let context =
            ExpressionEvaluationContext::new(&variables, Arc::new(InstantQueryClock::new(0, 0)));
        assert_eq!(
            evaluator
                .evaluate_expression(&context, &expr)
                .await
                .unwrap(),
            VariableValue::Null
        );
    }

    let expr = "split('hello||world', NULL)";
    let expr = drasi_query_cypher::parse_expression(expr).unwrap();
    {
        let context =
            ExpressionEvaluationContext::new(&variables, Arc::new(InstantQueryClock::new(0, 0)));
        assert_eq!(
            evaluator
                .evaluate_expression(&context, &expr)
                .await
                .unwrap(),
            VariableValue::Null
        );
    }
}

#[tokio::test]
async fn evaluate_substring() {
    let expr = "substring('drasi', 2, 3)";
    let expr = drasi_query_cypher::parse_expression(expr).unwrap();

    let function_registry = create_string_expression_test_function_registry();
    let ari = Arc::new(InMemoryResultIndex::new());
    let evaluator = ExpressionEvaluator::new(function_registry.clone(), ari.clone());

    let variables = QueryVariables::new();
    {
        let context =
            ExpressionEvaluationContext::new(&variables, Arc::new(InstantQueryClock::new(0, 0)));
        assert_eq!(
            evaluator
                .evaluate_expression(&context, &expr)
                .await
                .unwrap(),
            VariableValue::String("asi".to_string())
        );
    }

    let expr = "substring(NULL, 2, 3)";
    let expr = drasi_query_cypher::parse_expression(expr).unwrap();
    {
        let context =
            ExpressionEvaluationContext::new(&variables, Arc::new(InstantQueryClock::new(0, 0)));
        assert_eq!(
            evaluator
                .evaluate_expression(&context, &expr)
                .await
                .unwrap(),
            VariableValue::Null
        );
    }

    let expr = "substring('drasi', 2, 0)";
    let expr = drasi_query_cypher::parse_expression(expr).unwrap();
    {
        let context =
            ExpressionEvaluationContext::new(&variables, Arc::new(InstantQueryClock::new(0, 0)));
        assert_eq!(
            evaluator
                .evaluate_expression(&context, &expr)
                .await
                .unwrap(),
            VariableValue::String("".to_string())
        );
    }
}

#[tokio::test]
async fn evaluate_to_lower() {
    let expr = "toLower('Drasi')";
    let expr = drasi_query_cypher::parse_expression(expr).unwrap();

    let function_registry = create_string_expression_test_function_registry();
    let ari = Arc::new(InMemoryResultIndex::new());
    let evaluator = ExpressionEvaluator::new(function_registry.clone(), ari.clone());

    let variables = QueryVariables::new();
    {
        let context =
            ExpressionEvaluationContext::new(&variables, Arc::new(InstantQueryClock::new(0, 0)));
        assert_eq!(
            evaluator
                .evaluate_expression(&context, &expr)
                .await
                .unwrap(),
            VariableValue::String("drasi".to_string())
        );
    }

    let expr = "toLower(NULL)";
    let expr = drasi_query_cypher::parse_expression(expr).unwrap();
    {
        let context =
            ExpressionEvaluationContext::new(&variables, Arc::new(InstantQueryClock::new(0, 0)));
        assert_eq!(
            evaluator
                .evaluate_expression(&context, &expr)
                .await
                .unwrap(),
            VariableValue::Null
        );
    }
}

#[tokio::test]
async fn test_to_upper() {
    let expr = "toUpper('Drasi')";
    let expr = drasi_query_cypher::parse_expression(expr).unwrap();

    let function_registry = create_string_expression_test_function_registry();
    let ari = Arc::new(InMemoryResultIndex::new());
    let evaluator = ExpressionEvaluator::new(function_registry.clone(), ari.clone());

    let variables = QueryVariables::new();
    {
        let context =
            ExpressionEvaluationContext::new(&variables, Arc::new(InstantQueryClock::new(0, 0)));
        assert_eq!(
            evaluator
                .evaluate_expression(&context, &expr)
                .await
                .unwrap(),
            VariableValue::String("DRASI".to_string())
        );
    }

    let expr = "toUpper(NULL)";
    let expr = drasi_query_cypher::parse_expression(expr).unwrap();
    {
        let context =
            ExpressionEvaluationContext::new(&variables, Arc::new(InstantQueryClock::new(0, 0)));
        assert_eq!(
            evaluator
                .evaluate_expression(&context, &expr)
                .await
                .unwrap(),
            VariableValue::Null
        );
    }
}

#[tokio::test]
async fn test_to_string() {
    let expr = "toString(123)";
    let expr = drasi_query_cypher::parse_expression(expr).unwrap();

    let function_registry = create_string_expression_test_function_registry();
    let ari = Arc::new(InMemoryResultIndex::new());
    let evaluator = ExpressionEvaluator::new(function_registry.clone(), ari.clone());

    let variables = QueryVariables::new();
    {
        let context =
            ExpressionEvaluationContext::new(&variables, Arc::new(InstantQueryClock::new(0, 0)));
        assert_eq!(
            evaluator
                .evaluate_expression(&context, &expr)
                .await
                .unwrap(),
            VariableValue::String("123".to_string())
        );
    }

    let expr = "toString(NULL)";
    let expr = drasi_query_cypher::parse_expression(expr).unwrap();
    {
        let context =
            ExpressionEvaluationContext::new(&variables, Arc::new(InstantQueryClock::new(0, 0)));
        assert_eq!(
            evaluator
                .evaluate_expression(&context, &expr)
                .await
                .unwrap(),
            VariableValue::Null
        );
    }

    let expr = "toString(TRUE   )";
    let expr = drasi_query_cypher::parse_expression(expr).unwrap();
    {
        let context =
            ExpressionEvaluationContext::new(&variables, Arc::new(InstantQueryClock::new(0, 0)));
        assert_eq!(
            evaluator
                .evaluate_expression(&context, &expr)
                .await
                .unwrap(),
            VariableValue::String("true".to_string())
        );
    }
}

#[tokio::test]
async fn test_trim() {
    let expr = "trim('   drasi   ')";
    let expr = drasi_query_cypher::parse_expression(expr).unwrap();

    let function_registry = create_string_expression_test_function_registry();
    let ari = Arc::new(InMemoryResultIndex::new());
    let evaluator = ExpressionEvaluator::new(function_registry.clone(), ari.clone());

    let variables = QueryVariables::new();
    {
        let context =
            ExpressionEvaluationContext::new(&variables, Arc::new(InstantQueryClock::new(0, 0)));
        assert_eq!(
            evaluator
                .evaluate_expression(&context, &expr)
                .await
                .unwrap(),
            VariableValue::String("drasi".to_string())
        );
    }

    let expr = "trim(   NULL)";
    let expr = drasi_query_cypher::parse_expression(expr).unwrap();
    {
        let context =
            ExpressionEvaluationContext::new(&variables, Arc::new(InstantQueryClock::new(0, 0)));
        assert_eq!(
            evaluator
                .evaluate_expression(&context, &expr)
                .await
                .unwrap(),
            VariableValue::Null
        );
    }
}

#[tokio::test]
async fn test_random_uuid() {
    let expr = "randomUUID()";
    let expr = drasi_query_cypher::parse_expression(expr).unwrap();

    let function_registry = create_string_expression_test_function_registry();
    let ari = Arc::new(InMemoryResultIndex::new());
    let evaluator = ExpressionEvaluator::new(function_registry.clone(), ari.clone());

    let variables = QueryVariables::new();
    let context =
        ExpressionEvaluationContext::new(&variables, Arc::new(InstantQueryClock::new(0, 0)));

    let result = evaluator
        .evaluate_expression(&context, &expr)
        .await
        .unwrap();

    if let VariableValue::String(uuid) = result {
        assert_eq!(uuid.len(), 36);
    }
}

#[tokio::test]
async fn evaluate_starts_with() {
    let function_registry = create_string_expression_test_function_registry();
    let ari = Arc::new(InMemoryResultIndex::new());
    let evaluator = ExpressionEvaluator::new(function_registry.clone(), ari.clone());
    let variables = QueryVariables::new();
    let context =
        ExpressionEvaluationContext::new(&variables, Arc::new(InstantQueryClock::new(0, 0)));

    // true case
    let expr = drasi_query_cypher::parse_expression("'drasi' STARTS WITH 'dra'").unwrap();
    assert_eq!(
        evaluator
            .evaluate_expression(&context, &expr)
            .await
            .unwrap(),
        VariableValue::Bool(true)
    );

    // false case
    let expr = drasi_query_cypher::parse_expression("'drasi' STARTS WITH 'xyz'").unwrap();
    assert_eq!(
        evaluator
            .evaluate_expression(&context, &expr)
            .await
            .unwrap(),
        VariableValue::Bool(false)
    );

    // null propagation
    let expr = drasi_query_cypher::parse_expression("NULL STARTS WITH 'a'").unwrap();
    assert_eq!(
        evaluator
            .evaluate_expression(&context, &expr)
            .await
            .unwrap(),
        VariableValue::Null
    );

    let expr = drasi_query_cypher::parse_expression("'a' STARTS WITH NULL").unwrap();
    assert_eq!(
        evaluator
            .evaluate_expression(&context, &expr)
            .await
            .unwrap(),
        VariableValue::Null
    );

    // empty string
    let expr = drasi_query_cypher::parse_expression("'drasi' STARTS WITH ''").unwrap();
    assert_eq!(
        evaluator
            .evaluate_expression(&context, &expr)
            .await
            .unwrap(),
        VariableValue::Bool(true)
    );
}

#[tokio::test]
async fn evaluate_ends_with() {
    let function_registry = create_string_expression_test_function_registry();
    let ari = Arc::new(InMemoryResultIndex::new());
    let evaluator = ExpressionEvaluator::new(function_registry.clone(), ari.clone());
    let variables = QueryVariables::new();
    let context =
        ExpressionEvaluationContext::new(&variables, Arc::new(InstantQueryClock::new(0, 0)));

    // true case
    let expr = drasi_query_cypher::parse_expression("'drasi' ENDS WITH 'si'").unwrap();
    assert_eq!(
        evaluator
            .evaluate_expression(&context, &expr)
            .await
            .unwrap(),
        VariableValue::Bool(true)
    );

    // false case
    let expr = drasi_query_cypher::parse_expression("'drasi' ENDS WITH 'xy'").unwrap();
    assert_eq!(
        evaluator
            .evaluate_expression(&context, &expr)
            .await
            .unwrap(),
        VariableValue::Bool(false)
    );

    // null propagation
    let expr = drasi_query_cypher::parse_expression("NULL ENDS WITH 'a'").unwrap();
    assert_eq!(
        evaluator
            .evaluate_expression(&context, &expr)
            .await
            .unwrap(),
        VariableValue::Null
    );
}

#[tokio::test]
async fn evaluate_contains_operator() {
    let function_registry = create_string_expression_test_function_registry();
    let ari = Arc::new(InMemoryResultIndex::new());
    let evaluator = ExpressionEvaluator::new(function_registry.clone(), ari.clone());
    let variables = QueryVariables::new();
    let context =
        ExpressionEvaluationContext::new(&variables, Arc::new(InstantQueryClock::new(0, 0)));

    // true case
    let expr = drasi_query_cypher::parse_expression("'drasi' CONTAINS 'ras'").unwrap();
    assert_eq!(
        evaluator
            .evaluate_expression(&context, &expr)
            .await
            .unwrap(),
        VariableValue::Bool(true)
    );

    // false case
    let expr = drasi_query_cypher::parse_expression("'drasi' CONTAINS 'xyz'").unwrap();
    assert_eq!(
        evaluator
            .evaluate_expression(&context, &expr)
            .await
            .unwrap(),
        VariableValue::Bool(false)
    );

    // null propagation
    let expr = drasi_query_cypher::parse_expression("NULL CONTAINS 'a'").unwrap();
    assert_eq!(
        evaluator
            .evaluate_expression(&context, &expr)
            .await
            .unwrap(),
        VariableValue::Null
    );

    // case-sensitive
    let expr = drasi_query_cypher::parse_expression("'Drasi' CONTAINS 'dra'").unwrap();
    assert_eq!(
        evaluator
            .evaluate_expression(&context, &expr)
            .await
            .unwrap(),
        VariableValue::Bool(false)
    );
}
