// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.

#include <chrono>
#include <cmath>
#include <iterator>
#include <memory>
#include <unordered_map>
#include <vector>

#include <gmock/gmock.h>
#include <gtest/gtest.h>

#include "coordinator/shard_map.hpp"
#include "functions/awesome_memgraph_functions.hpp"
#include "parser/opencypher/parser.hpp"
#include "query/v2/accessors.hpp"
#include "query/v2/bindings/eval.hpp"
#include "query/v2/bindings/frame.hpp"
#include "query/v2/context.hpp"
#include "query/v2/frontend/ast/ast.hpp"
#include "query/v2/request_router.hpp"
#include "query/v2/requests.hpp"
#include "query_v2_query_common.hpp"
#include "storage/v3/property_value.hpp"
#include "storage/v3/shard.hpp"
#include "utils/exceptions.hpp"
#include "utils/string.hpp"

using memgraph::query::v2::test_common::ToIntList;
using testing::ElementsAre;
using testing::UnorderedElementsAre;

using memgraph::io::Time;
using memgraph::io::TimedOut;
using memgraph::io::simulator::Simulator;
using memgraph::io::simulator::SimulatorConfig;
using memgraph::io::simulator::SimulatorTransport;
using memgraph::msgs::CreateExpandRequest;
using memgraph::msgs::CreateExpandResponse;
using memgraph::msgs::CreateVerticesRequest;
using memgraph::msgs::CreateVerticesResponse;
using memgraph::msgs::ExpandOneRequest;
using memgraph::msgs::ExpandOneResponse;
using memgraph::msgs::ExpandOneResultRow;
using memgraph::msgs::GetPropertiesRequest;
using memgraph::msgs::GetPropertiesResultRow;
using memgraph::msgs::NewExpand;
using memgraph::msgs::NewVertex;
using memgraph::msgs::ScanVerticesRequest;
using memgraph::msgs::ScanVerticesResponse;
using ShardMap = memgraph::coordinator::ShardMap;
using LabelId = memgraph::storage::v3::LabelId;
using PropertyId = memgraph::storage::v3::PropertyId;
using memgraph::coordinator::Shards;
using CompoundKey = memgraph::coordinator::PrimaryKey;
using memgraph::expr::ExpressionRuntimeException;
using memgraph::functions::FunctionRuntimeException;

namespace memgraph::query::v2::tests {

class MockedRequestRouter : public RequestRouterInterface {
 public:
  using VertexAccessor = accessors::VertexAccessor;
  explicit MockedRequestRouter(ShardMap shard_map) : shards_map_(std::move(shard_map)) { SetUpNameIdMappers(); }
  memgraph::storage::v3::EdgeTypeId NameToEdgeType(const std::string &name) const override {
    return shards_map_.GetEdgeTypeId(name).value();
  }

  memgraph::storage::v3::PropertyId NameToProperty(const std::string &name) const override {
    return shards_map_.GetPropertyId(name).value();
  }

  memgraph::storage::v3::LabelId NameToLabel(const std::string &name) const override {
    return shards_map_.GetLabelId(name).value();
  }
  void StartTransaction() override {}
  void Commit() override {}
  std::vector<VertexAccessor> ScanVertices(std::optional<std::string> /* label */) override { return {}; }

  std::vector<CreateVerticesResponse> CreateVertices(std::vector<memgraph::msgs::NewVertex> new_vertices) override {
    return {};
  }

  std::vector<ExpandOneResultRow> ExpandOne(ExpandOneRequest request) override { return {}; }

  std::vector<CreateExpandResponse> CreateExpand(std::vector<NewExpand> new_edges) override { return {}; }

  std::vector<GetPropertiesResultRow> GetProperties(GetPropertiesRequest rqst) override { return {}; }

  const std::string &PropertyToName(memgraph::storage::v3::PropertyId id) const override {
    return properties_.IdToName(id.AsUint());
  }

  const std::string &LabelToName(memgraph::storage::v3::LabelId id) const override {
    return labels_.IdToName(id.AsUint());
  }

  const std::string &EdgeTypeToName(memgraph::storage::v3::EdgeTypeId id) const override {
    return edge_types_.IdToName(id.AsUint());
  }

  std::optional<storage::v3::PropertyId> MaybeNameToProperty(const std::string &name) const override {
    return shards_map_.GetPropertyId(name);
  }

  std::optional<storage::v3::EdgeTypeId> MaybeNameToEdgeType(const std::string &name) const override {
    return shards_map_.GetEdgeTypeId(name);
  }

  std::optional<storage::v3::LabelId> MaybeNameToLabel(const std::string &name) const override {
    return shards_map_.GetLabelId(name);
  }

  bool IsPrimaryLabel(LabelId label) const override { return true; }

  bool IsPrimaryKey(LabelId primary_label, PropertyId property) const override { return true; }

 private:
  void SetUpNameIdMappers() {
    std::unordered_map<uint64_t, std::string> id_to_name;
    for (const auto &[name, id] : shards_map_.labels) {
      id_to_name.emplace(id.AsUint(), name);
    }
    labels_.StoreMapping(std::move(id_to_name));
    id_to_name.clear();
    for (const auto &[name, id] : shards_map_.properties) {
      id_to_name.emplace(id.AsUint(), name);
    }
    properties_.StoreMapping(std::move(id_to_name));
    id_to_name.clear();
    for (const auto &[name, id] : shards_map_.edge_types) {
      id_to_name.emplace(id.AsUint(), name);
    }
    edge_types_.StoreMapping(std::move(id_to_name));
  }

  ShardMap shards_map_;
  memgraph::storage::v3::NameIdMapper properties_;
  memgraph::storage::v3::NameIdMapper edge_types_;
  memgraph::storage::v3::NameIdMapper labels_;
};

ShardMap CreateDummyShardmap() {
  static const std::string label_name = std::string("label1");
  ShardMap sm;

  // register new properties
  const std::vector<std::string> property_names = {"prop", "property_2", "age", "height", "a", "b", "c"};
  const auto properties = sm.AllocatePropertyIds(property_names);
  const auto property_id_1 = properties.at("prop");
  const auto property_id_2 = properties.at("property_2");
  const auto property_id_3 = properties.at("age");
  const auto property_id_4 = properties.at("height");
  const auto property_id_5 = properties.at("a");
  const auto property_id_6 = properties.at("b");
  const auto property_id_7 = properties.at("c");
  const auto type_1 = memgraph::common::SchemaType::INT;

  using SchemaProperty = memgraph::coordinator::SchemaProperty;
  // register new label space
  std::vector<SchemaProperty> schema = {
      SchemaProperty{.property_id = property_id_1, .type = type_1},
      SchemaProperty{.property_id = property_id_2, .type = type_1},
      SchemaProperty{.property_id = property_id_3, .type = type_1},
      SchemaProperty{.property_id = property_id_4, .type = type_1},
      SchemaProperty{.property_id = property_id_5, .type = type_1},
      SchemaProperty{.property_id = property_id_6, .type = type_1},
      SchemaProperty{.property_id = property_id_7, .type = type_1},
  };

  auto label_success = sm.InitializeNewLabel(label_name, schema, 1, sm.shard_map_version);
  MG_ASSERT(label_success);

  const LabelId label_id = sm.labels.at(label_name);
  auto &label_space = sm.label_spaces.at(label_id);
  Shards &shards_for_label = label_space.shards;
  shards_for_label.clear();

  auto key1 = memgraph::storage::v3::PropertyValue(0);
  auto key2 = memgraph::storage::v3::PropertyValue(0);
  CompoundKey compound_key_1 = {key1, key2};
  shards_for_label[compound_key_1] = {};

  auto key3 = memgraph::storage::v3::PropertyValue(12);
  auto key4 = memgraph::storage::v3::PropertyValue(13);
  CompoundKey compound_key_2 = {key3, key4};
  shards_for_label[compound_key_2] = {};

  sm.AllocateEdgeTypeIds(std::vector<memgraph::coordinator::EdgeTypeName>{"edge_type"});

  return sm;
}

class ExpressionEvaluatorTest : public ::testing::Test {
 public:
  ExpressionEvaluatorTest() {}

 protected:
  AstStorage storage;
  memgraph::utils::MonotonicBufferResource mem{1024};
  EvaluationContext ctx{.memory = &mem, .timestamp = QueryTimestamp()};
  SymbolTable symbol_table;

  Frame frame{128};
  std::unique_ptr<RequestRouterInterface> request_router = std::make_unique<MockedRequestRouter>(CreateDummyShardmap());
  ExpressionEvaluator eval{&frame, symbol_table, ctx, request_router.get(), memgraph::storage::v3::View::OLD};

  Identifier *CreateIdentifierWithValue(std::string name, const TypedValue &value) {
    auto id = storage.Create<Identifier>(name, true);
    auto symbol = symbol_table.CreateSymbol(name, true);
    id->MapTo(symbol);
    frame[symbol] = value;
    return id;
  }

  template <class TExpression>
  auto Eval(TExpression *expr) {
    ctx.properties = NamesToProperties(storage.properties_, request_router.get());
    ctx.labels = NamesToLabels(storage.labels_, request_router.get());
    auto value = expr->Accept(eval);
    EXPECT_EQ(value.GetMemoryResource(), &mem) << "ExpressionEvaluator must use the MemoryResource from "
                                                  "EvaluationContext for allocations!";
    return value;
  }
};

TEST_F(ExpressionEvaluatorTest, OrOperator) {
  auto *op =
      storage.Create<OrOperator>(storage.Create<PrimitiveLiteral>(true), storage.Create<PrimitiveLiteral>(false));
  auto val1 = Eval(op);
  ASSERT_EQ(val1.ValueBool(), true);
  op = storage.Create<OrOperator>(storage.Create<PrimitiveLiteral>(true), storage.Create<PrimitiveLiteral>(true));
  auto val2 = Eval(op);
  ASSERT_EQ(val2.ValueBool(), true);
}

TEST_F(ExpressionEvaluatorTest, XorOperator) {
  auto *op =
      storage.Create<XorOperator>(storage.Create<PrimitiveLiteral>(true), storage.Create<PrimitiveLiteral>(false));
  auto val1 = Eval(op);
  ASSERT_EQ(val1.ValueBool(), true);
  op = storage.Create<XorOperator>(storage.Create<PrimitiveLiteral>(true), storage.Create<PrimitiveLiteral>(true));
  auto val2 = Eval(op);
  ASSERT_EQ(val2.ValueBool(), false);
}

TEST_F(ExpressionEvaluatorTest, AndOperator) {
  auto *op =
      storage.Create<AndOperator>(storage.Create<PrimitiveLiteral>(true), storage.Create<PrimitiveLiteral>(true));
  auto val1 = Eval(op);
  ASSERT_EQ(val1.ValueBool(), true);
  op = storage.Create<AndOperator>(storage.Create<PrimitiveLiteral>(false), storage.Create<PrimitiveLiteral>(true));
  auto val2 = Eval(op);
  ASSERT_EQ(val2.ValueBool(), false);
}

TEST_F(ExpressionEvaluatorTest, AndOperatorShortCircuit) {
  {
    auto *op =
        storage.Create<AndOperator>(storage.Create<PrimitiveLiteral>(false), storage.Create<PrimitiveLiteral>(5));
    auto value = Eval(op);
    EXPECT_EQ(value.ValueBool(), false);
  }
  {
    auto *op =
        storage.Create<AndOperator>(storage.Create<PrimitiveLiteral>(5), storage.Create<PrimitiveLiteral>(false));
    // We are evaluating left to right, so we don't short circuit here and
    // raise due to `5`. This differs from neo4j, where they evaluate both
    // sides and return `false` without checking for type of the first
    // expression.
    EXPECT_THROW(Eval(op), ExpressionRuntimeException);
  }
}

TEST_F(ExpressionEvaluatorTest, AndOperatorNull) {
  {
    // Null doesn't short circuit
    auto *op = storage.Create<AndOperator>(storage.Create<PrimitiveLiteral>(TypedValue()),
                                           storage.Create<PrimitiveLiteral>(5));
    EXPECT_THROW(Eval(op), ExpressionRuntimeException);
  }
  {
    auto *op = storage.Create<AndOperator>(storage.Create<PrimitiveLiteral>(TypedValue()),
                                           storage.Create<PrimitiveLiteral>(true));
    auto value = Eval(op);
    EXPECT_TRUE(value.IsNull());
  }
  {
    auto *op = storage.Create<AndOperator>(storage.Create<PrimitiveLiteral>(TypedValue()),
                                           storage.Create<PrimitiveLiteral>(false));
    auto value = Eval(op);
    ASSERT_TRUE(value.IsBool());
    EXPECT_EQ(value.ValueBool(), false);
  }
}

TEST_F(ExpressionEvaluatorTest, AdditionOperator) {
  auto *op = storage.Create<AdditionOperator>(storage.Create<PrimitiveLiteral>(2), storage.Create<PrimitiveLiteral>(3));
  auto value = Eval(op);
  ASSERT_EQ(value.ValueInt(), 5);
}

TEST_F(ExpressionEvaluatorTest, SubtractionOperator) {
  auto *op =
      storage.Create<SubtractionOperator>(storage.Create<PrimitiveLiteral>(2), storage.Create<PrimitiveLiteral>(3));
  auto value = Eval(op);
  ASSERT_EQ(value.ValueInt(), -1);
}

TEST_F(ExpressionEvaluatorTest, MultiplicationOperator) {
  auto *op =
      storage.Create<MultiplicationOperator>(storage.Create<PrimitiveLiteral>(2), storage.Create<PrimitiveLiteral>(3));
  auto value = Eval(op);
  ASSERT_EQ(value.ValueInt(), 6);
}

TEST_F(ExpressionEvaluatorTest, DivisionOperator) {
  auto *op =
      storage.Create<DivisionOperator>(storage.Create<PrimitiveLiteral>(50), storage.Create<PrimitiveLiteral>(10));
  auto value = Eval(op);
  ASSERT_EQ(value.ValueInt(), 5);
}

TEST_F(ExpressionEvaluatorTest, ModOperator) {
  auto *op = storage.Create<ModOperator>(storage.Create<PrimitiveLiteral>(65), storage.Create<PrimitiveLiteral>(10));
  auto value = Eval(op);
  ASSERT_EQ(value.ValueInt(), 5);
}

TEST_F(ExpressionEvaluatorTest, EqualOperator) {
  auto *op = storage.Create<EqualOperator>(storage.Create<PrimitiveLiteral>(10), storage.Create<PrimitiveLiteral>(15));
  auto val1 = Eval(op);
  ASSERT_EQ(val1.ValueBool(), false);
  op = storage.Create<EqualOperator>(storage.Create<PrimitiveLiteral>(15), storage.Create<PrimitiveLiteral>(15));
  auto val2 = Eval(op);
  ASSERT_EQ(val2.ValueBool(), true);
  op = storage.Create<EqualOperator>(storage.Create<PrimitiveLiteral>(20), storage.Create<PrimitiveLiteral>(15));
  auto val3 = Eval(op);
  ASSERT_EQ(val3.ValueBool(), false);
}

TEST_F(ExpressionEvaluatorTest, NotEqualOperator) {
  auto *op =
      storage.Create<NotEqualOperator>(storage.Create<PrimitiveLiteral>(10), storage.Create<PrimitiveLiteral>(15));
  auto val1 = Eval(op);
  ASSERT_EQ(val1.ValueBool(), true);
  op = storage.Create<NotEqualOperator>(storage.Create<PrimitiveLiteral>(15), storage.Create<PrimitiveLiteral>(15));
  auto val2 = Eval(op);
  ASSERT_EQ(val2.ValueBool(), false);
  op = storage.Create<NotEqualOperator>(storage.Create<PrimitiveLiteral>(20), storage.Create<PrimitiveLiteral>(15));
  auto val3 = Eval(op);
  ASSERT_EQ(val3.ValueBool(), true);
}

TEST_F(ExpressionEvaluatorTest, LessOperator) {
  auto *op = storage.Create<LessOperator>(storage.Create<PrimitiveLiteral>(10), storage.Create<PrimitiveLiteral>(15));
  auto val1 = Eval(op);
  ASSERT_EQ(val1.ValueBool(), true);
  op = storage.Create<LessOperator>(storage.Create<PrimitiveLiteral>(15), storage.Create<PrimitiveLiteral>(15));
  auto val2 = Eval(op);
  ASSERT_EQ(val2.ValueBool(), false);
  op = storage.Create<LessOperator>(storage.Create<PrimitiveLiteral>(20), storage.Create<PrimitiveLiteral>(15));
  auto val3 = Eval(op);
  ASSERT_EQ(val3.ValueBool(), false);
}

TEST_F(ExpressionEvaluatorTest, GreaterOperator) {
  auto *op =
      storage.Create<GreaterOperator>(storage.Create<PrimitiveLiteral>(10), storage.Create<PrimitiveLiteral>(15));
  auto val1 = Eval(op);
  ASSERT_EQ(val1.ValueBool(), false);
  op = storage.Create<GreaterOperator>(storage.Create<PrimitiveLiteral>(15), storage.Create<PrimitiveLiteral>(15));
  auto val2 = Eval(op);
  ASSERT_EQ(val2.ValueBool(), false);
  op = storage.Create<GreaterOperator>(storage.Create<PrimitiveLiteral>(20), storage.Create<PrimitiveLiteral>(15));
  auto val3 = Eval(op);
  ASSERT_EQ(val3.ValueBool(), true);
}

TEST_F(ExpressionEvaluatorTest, LessEqualOperator) {
  auto *op =
      storage.Create<LessEqualOperator>(storage.Create<PrimitiveLiteral>(10), storage.Create<PrimitiveLiteral>(15));
  auto val1 = Eval(op);
  ASSERT_EQ(val1.ValueBool(), true);
  op = storage.Create<LessEqualOperator>(storage.Create<PrimitiveLiteral>(15), storage.Create<PrimitiveLiteral>(15));
  auto val2 = Eval(op);
  ASSERT_EQ(val2.ValueBool(), true);
  op = storage.Create<LessEqualOperator>(storage.Create<PrimitiveLiteral>(20), storage.Create<PrimitiveLiteral>(15));
  auto val3 = Eval(op);
  ASSERT_EQ(val3.ValueBool(), false);
}

TEST_F(ExpressionEvaluatorTest, GreaterEqualOperator) {
  auto *op =
      storage.Create<GreaterEqualOperator>(storage.Create<PrimitiveLiteral>(10), storage.Create<PrimitiveLiteral>(15));
  auto val1 = Eval(op);
  ASSERT_EQ(val1.ValueBool(), false);
  op = storage.Create<GreaterEqualOperator>(storage.Create<PrimitiveLiteral>(15), storage.Create<PrimitiveLiteral>(15));
  auto val2 = Eval(op);
  ASSERT_EQ(val2.ValueBool(), true);
  op = storage.Create<GreaterEqualOperator>(storage.Create<PrimitiveLiteral>(20), storage.Create<PrimitiveLiteral>(15));
  auto val3 = Eval(op);
  ASSERT_EQ(val3.ValueBool(), true);
}

TEST_F(ExpressionEvaluatorTest, InListOperator) {
  auto *list_literal = storage.Create<ListLiteral>(std::vector<Expression *>{
      storage.Create<PrimitiveLiteral>(1), storage.Create<PrimitiveLiteral>(2), storage.Create<PrimitiveLiteral>("a")});
  {
    // Element exists in list.
    auto *op = storage.Create<InListOperator>(storage.Create<PrimitiveLiteral>(2), list_literal);
    auto value = Eval(op);
    EXPECT_EQ(value.ValueBool(), true);
  }
  {
    // Element doesn't exist in list.
    auto *op = storage.Create<InListOperator>(storage.Create<PrimitiveLiteral>("x"), list_literal);
    auto value = Eval(op);
    EXPECT_EQ(value.ValueBool(), false);
  }
  {
    auto *list_literal = storage.Create<ListLiteral>(
        std::vector<Expression *>{storage.Create<PrimitiveLiteral>(TypedValue()), storage.Create<PrimitiveLiteral>(2),
                                  storage.Create<PrimitiveLiteral>("a")});
    // Element doesn't exist in list with null element.
    auto *op = storage.Create<InListOperator>(storage.Create<PrimitiveLiteral>("x"), list_literal);
    auto value = Eval(op);
    EXPECT_TRUE(value.IsNull());
  }
  {
    // Null list.
    auto *op = storage.Create<InListOperator>(storage.Create<PrimitiveLiteral>("x"),
                                              storage.Create<PrimitiveLiteral>(TypedValue()));
    auto value = Eval(op);
    EXPECT_TRUE(value.IsNull());
  }
  {
    // Null literal.
    auto *op = storage.Create<InListOperator>(storage.Create<PrimitiveLiteral>(TypedValue()), list_literal);
    auto value = Eval(op);
    EXPECT_TRUE(value.IsNull());
  }
  {
    // Null literal, empty list.
    auto *op = storage.Create<InListOperator>(storage.Create<PrimitiveLiteral>(TypedValue()),
                                              storage.Create<ListLiteral>(std::vector<Expression *>()));
    auto value = Eval(op);
    EXPECT_FALSE(value.ValueBool());
  }
}

TEST_F(ExpressionEvaluatorTest, ListIndexing) {
  auto *list_literal = storage.Create<ListLiteral>(
      std::vector<Expression *>{storage.Create<PrimitiveLiteral>(1), storage.Create<PrimitiveLiteral>(2),
                                storage.Create<PrimitiveLiteral>(3), storage.Create<PrimitiveLiteral>(4)});
  {
    // Legal indexing.
    auto *op = storage.Create<SubscriptOperator>(list_literal, storage.Create<PrimitiveLiteral>(2));
    auto value = Eval(op);
    EXPECT_EQ(value.ValueInt(), 3);
  }
  {
    // Out of bounds indexing.
    auto *op = storage.Create<SubscriptOperator>(list_literal, storage.Create<PrimitiveLiteral>(4));
    auto value = Eval(op);
    EXPECT_TRUE(value.IsNull());
  }
  {
    // Out of bounds indexing with negative bound.
    auto *op = storage.Create<SubscriptOperator>(list_literal, storage.Create<PrimitiveLiteral>(-100));
    auto value = Eval(op);
    EXPECT_TRUE(value.IsNull());
  }
  {
    // Legal indexing with negative index.
    auto *op = storage.Create<SubscriptOperator>(list_literal, storage.Create<PrimitiveLiteral>(-2));
    auto value = Eval(op);
    EXPECT_EQ(value.ValueInt(), 3);
  }
  {
    // Indexing with one operator being null.
    auto *op = storage.Create<SubscriptOperator>(storage.Create<PrimitiveLiteral>(TypedValue()),
                                                 storage.Create<PrimitiveLiteral>(-2));
    auto value = Eval(op);
    EXPECT_TRUE(value.IsNull());
  }
  {
    // Indexing with incompatible type.
    auto *op = storage.Create<SubscriptOperator>(list_literal, storage.Create<PrimitiveLiteral>("bla"));
    EXPECT_THROW(Eval(op), ExpressionRuntimeException);
  }
}

TEST_F(ExpressionEvaluatorTest, MapIndexing) {
  auto *map_literal = storage.Create<MapLiteral>(
      std::unordered_map<PropertyIx, Expression *>{{storage.GetPropertyIx("a"), storage.Create<PrimitiveLiteral>(1)},
                                                   {storage.GetPropertyIx("b"), storage.Create<PrimitiveLiteral>(2)},
                                                   {storage.GetPropertyIx("c"), storage.Create<PrimitiveLiteral>(3)}});
  {
    // Legal indexing.
    auto *op = storage.Create<SubscriptOperator>(map_literal, storage.Create<PrimitiveLiteral>("b"));
    auto value = Eval(op);
    EXPECT_EQ(value.ValueInt(), 2);
  }
  {
    // Legal indexing, non-existing key.
    auto *op = storage.Create<SubscriptOperator>(map_literal, storage.Create<PrimitiveLiteral>("z"));
    auto value = Eval(op);
    EXPECT_TRUE(value.IsNull());
  }
  {
    // Wrong key type.
    auto *op = storage.Create<SubscriptOperator>(map_literal, storage.Create<PrimitiveLiteral>(42));
    EXPECT_THROW(Eval(op), ExpressionRuntimeException);
  }
  {
    // Indexing with Null.
    auto *op = storage.Create<SubscriptOperator>(map_literal, storage.Create<PrimitiveLiteral>(TypedValue()));
    auto value = Eval(op);
    EXPECT_TRUE(value.IsNull());
  }
}

using Vertex = memgraph::msgs::Vertex;
using Edge = memgraph::msgs::Edge;
using EdgeType = memgraph::msgs::EdgeType;
using EdgeId = memgraph::msgs::EdgeId;
using Value = memgraph::msgs::Value;
using VertexId = memgraph::msgs::VertexId;
using Label = memgraph::msgs::Label;

accessors::VertexAccessor CreateVertex(std::vector<std::pair<PropertyId, Value>> props,
                                       const RequestRouterInterface *request_router, Vertex v = {}) {
  return {std::move(v), std::move(props), request_router};
}

accessors::EdgeAccessor CreateEdge(std::vector<std::pair<PropertyId, Value>> props,
                                   const RequestRouterInterface *request_router, EdgeId edge_id = {}, VertexId src = {},
                                   VertexId dst = {}) {
  auto edge = Edge{.src = std::move(src),
                   .dst = std::move(dst),
                   .properties = std::move(props),
                   .id = edge_id,
                   .type = EdgeType{request_router->NameToEdgeType("edge_type")}};
  return accessors::EdgeAccessor{std::move(edge), request_router};
}

TEST_F(ExpressionEvaluatorTest, VertexAndEdgeIndexing) {
  auto prop = request_router->NameToProperty("prop");
  auto vertex = CreateVertex({{prop, Value(static_cast<int64_t>(42))}}, request_router.get(), {});
  auto edge = CreateEdge({{prop, Value(static_cast<int64_t>(43))}}, request_router.get(), {}, {}, {});

  auto *vertex_id = CreateIdentifierWithValue("v1", TypedValue(vertex));
  auto *edge_id = CreateIdentifierWithValue("e11", TypedValue(edge));
  {
    // Legal indexing.
    auto *op1 = storage.Create<SubscriptOperator>(vertex_id, storage.Create<PrimitiveLiteral>("prop"));
    auto value1 = Eval(op1);
    EXPECT_EQ(value1.ValueInt(), 42);

    auto *op2 = storage.Create<SubscriptOperator>(edge_id, storage.Create<PrimitiveLiteral>("prop"));
    auto value2 = Eval(op2);
    EXPECT_EQ(value2.ValueInt(), 43);
  }

  {
    // Legal indexing, non-existing key.
    auto *op1 = storage.Create<SubscriptOperator>(vertex_id, storage.Create<PrimitiveLiteral>("blah"));
    auto value1 = Eval(op1);
    EXPECT_TRUE(value1.IsNull());
    auto *op2 = storage.Create<SubscriptOperator>(edge_id, storage.Create<PrimitiveLiteral>("blah"));
    auto value2 = Eval(op2);
    EXPECT_TRUE(value2.IsNull());
  }
  {
    // Wrong key type.
    auto *op1 = storage.Create<SubscriptOperator>(vertex_id, storage.Create<PrimitiveLiteral>(1));
    EXPECT_THROW(Eval(op1), ExpressionRuntimeException);

    auto *op2 = storage.Create<SubscriptOperator>(edge_id, storage.Create<PrimitiveLiteral>(1));
    EXPECT_THROW(Eval(op2), ExpressionRuntimeException);
  }
  {
    // Indexing with Null.
    auto *op1 = storage.Create<SubscriptOperator>(vertex_id, storage.Create<PrimitiveLiteral>(TypedValue{}));
    auto value1 = Eval(op1);
    EXPECT_TRUE(value1.IsNull());

    auto *op2 = storage.Create<SubscriptOperator>(edge_id, storage.Create<PrimitiveLiteral>(TypedValue{}));
    auto value2 = Eval(op2);
    EXPECT_TRUE(value2.IsNull());
  }
}

TEST_F(ExpressionEvaluatorTest, ListSlicingOperator) {
  auto *list_literal = storage.Create<ListLiteral>(
      std::vector<Expression *>{storage.Create<PrimitiveLiteral>(1), storage.Create<PrimitiveLiteral>(2),
                                storage.Create<PrimitiveLiteral>(3), storage.Create<PrimitiveLiteral>(4)});

  auto extract_ints = [](TypedValue list) {
    std::vector<int64_t> int_list;
    for (auto x : list.ValueList()) {
      int_list.push_back(x.ValueInt());
    }
    return int_list;
  };
  {
    // Legal slicing with both bounds defined.
    auto *op = storage.Create<ListSlicingOperator>(list_literal, storage.Create<PrimitiveLiteral>(2),
                                                   storage.Create<PrimitiveLiteral>(4));
    auto value = Eval(op);
    EXPECT_THAT(extract_ints(value), ElementsAre(3, 4));
  }
  {
    // Legal slicing with negative bound.
    auto *op = storage.Create<ListSlicingOperator>(list_literal, storage.Create<PrimitiveLiteral>(2),
                                                   storage.Create<PrimitiveLiteral>(-1));
    auto value = Eval(op);
    EXPECT_THAT(extract_ints(value), ElementsAre(3));
  }
  {
    // Lower bound larger than upper bound.
    auto *op = storage.Create<ListSlicingOperator>(list_literal, storage.Create<PrimitiveLiteral>(2),
                                                   storage.Create<PrimitiveLiteral>(-4));
    auto value = Eval(op);
    EXPECT_THAT(extract_ints(value), ElementsAre());
  }
  {
    // Bounds ouf or range.
    auto *op = storage.Create<ListSlicingOperator>(list_literal, storage.Create<PrimitiveLiteral>(-100),
                                                   storage.Create<PrimitiveLiteral>(10));
    auto value = Eval(op);
    EXPECT_THAT(extract_ints(value), ElementsAre(1, 2, 3, 4));
  }
  {
    // Lower bound undefined.
    auto *op = storage.Create<ListSlicingOperator>(list_literal, nullptr, storage.Create<PrimitiveLiteral>(3));
    auto value = Eval(op);
    EXPECT_THAT(extract_ints(value), ElementsAre(1, 2, 3));
  }
  {
    // Upper bound undefined.
    auto *op = storage.Create<ListSlicingOperator>(list_literal, storage.Create<PrimitiveLiteral>(-2), nullptr);
    auto value = Eval(op);
    EXPECT_THAT(extract_ints(value), ElementsAre(3, 4));
  }
  {
    // Bound of illegal type and null value bound.
    auto *op = storage.Create<ListSlicingOperator>(list_literal, storage.Create<PrimitiveLiteral>(TypedValue()),
                                                   storage.Create<PrimitiveLiteral>("mirko"));
    EXPECT_THROW(Eval(op), ExpressionRuntimeException);
  }
  {
    // List of illegal type.
    auto *op = storage.Create<ListSlicingOperator>(storage.Create<PrimitiveLiteral>("a"),
                                                   storage.Create<PrimitiveLiteral>(-2), nullptr);
    EXPECT_THROW(Eval(op), ExpressionRuntimeException);
  }
  {
    // Null value list with undefined upper bound.
    auto *op = storage.Create<ListSlicingOperator>(storage.Create<PrimitiveLiteral>(TypedValue()),
                                                   storage.Create<PrimitiveLiteral>(-2), nullptr);
    auto value = Eval(op);
    EXPECT_TRUE(value.IsNull());
    ;
  }
  {
    // Null value index.
    auto *op = storage.Create<ListSlicingOperator>(list_literal, storage.Create<PrimitiveLiteral>(-2),
                                                   storage.Create<PrimitiveLiteral>(TypedValue()));
    auto value = Eval(op);
    EXPECT_TRUE(value.IsNull());
    ;
  }
}

TEST_F(ExpressionEvaluatorTest, IfOperator) {
  auto *then_expression = storage.Create<PrimitiveLiteral>(10);
  auto *else_expression = storage.Create<PrimitiveLiteral>(20);
  {
    auto *condition_true =
        storage.Create<EqualOperator>(storage.Create<PrimitiveLiteral>(2), storage.Create<PrimitiveLiteral>(2));
    auto *op = storage.Create<IfOperator>(condition_true, then_expression, else_expression);
    auto value = Eval(op);
    ASSERT_EQ(value.ValueInt(), 10);
  }
  {
    auto *condition_false =
        storage.Create<EqualOperator>(storage.Create<PrimitiveLiteral>(2), storage.Create<PrimitiveLiteral>(3));
    auto *op = storage.Create<IfOperator>(condition_false, then_expression, else_expression);
    auto value = Eval(op);
    ASSERT_EQ(value.ValueInt(), 20);
  }
  {
    auto *condition_exception =
        storage.Create<AdditionOperator>(storage.Create<PrimitiveLiteral>(2), storage.Create<PrimitiveLiteral>(3));
    auto *op = storage.Create<IfOperator>(condition_exception, then_expression, else_expression);
    ASSERT_THROW(Eval(op), ExpressionRuntimeException);
  }
}

TEST_F(ExpressionEvaluatorTest, NotOperator) {
  auto *op = storage.Create<NotOperator>(storage.Create<PrimitiveLiteral>(false));
  auto value = Eval(op);
  ASSERT_EQ(value.ValueBool(), true);
}

TEST_F(ExpressionEvaluatorTest, UnaryPlusOperator) {
  auto *op = storage.Create<UnaryPlusOperator>(storage.Create<PrimitiveLiteral>(5));
  auto value = Eval(op);
  ASSERT_EQ(value.ValueInt(), 5);
}

TEST_F(ExpressionEvaluatorTest, UnaryMinusOperator) {
  auto *op = storage.Create<UnaryMinusOperator>(storage.Create<PrimitiveLiteral>(5));
  auto value = Eval(op);
  ASSERT_EQ(value.ValueInt(), -5);
}

TEST_F(ExpressionEvaluatorTest, IsNullOperator) {
  auto *op = storage.Create<IsNullOperator>(storage.Create<PrimitiveLiteral>(1));
  auto val1 = Eval(op);
  ASSERT_EQ(val1.ValueBool(), false);
  op = storage.Create<IsNullOperator>(storage.Create<PrimitiveLiteral>(TypedValue()));
  auto val2 = Eval(op);
  ASSERT_EQ(val2.ValueBool(), true);
}

TEST_F(ExpressionEvaluatorTest, LabelsTest) {
  Label label{request_router->NameToLabel("label1")};
  Vertex vertex = {{}, {label}};
  auto v1 = CreateVertex({}, request_router.get(), vertex);
  auto *identifier = storage.Create<Identifier>("n");
  auto node_symbol = symbol_table.CreateSymbol("n", true);
  identifier->MapTo(node_symbol);
  frame[node_symbol] = TypedValue(v1);
  {
    auto *op = storage.Create<LabelsTest>(identifier, std::vector<LabelIx>{LabelIx{"label1", label.id.AsInt()}});
    auto value = Eval(op);
    EXPECT_EQ(value.ValueBool(), true);
  }
  {
    auto *op = storage.Create<LabelsTest>(identifier, std::vector<LabelIx>{LabelIx{"label2", 10}});
    auto value = Eval(op);
    EXPECT_EQ(value.ValueBool(), false);
  }
  {
    auto *op = storage.Create<LabelsTest>(identifier, std::vector<LabelIx>{LabelIx{"label2", 10}});
    frame[node_symbol] = TypedValue();
    auto value = Eval(op);
    EXPECT_TRUE(value.IsNull());
  }
}

TEST_F(ExpressionEvaluatorTest, Aggregation) {
  auto aggr = storage.Create<Aggregation>(storage.Create<PrimitiveLiteral>(42), nullptr, Aggregation::Op::COUNT);
  auto aggr_sym = symbol_table.CreateSymbol("aggr", true);
  aggr->MapTo(aggr_sym);
  frame[aggr_sym] = TypedValue(1);
  auto value = Eval(aggr);
  EXPECT_EQ(value.ValueInt(), 1);
}

TEST_F(ExpressionEvaluatorTest, ListLiteral) {
  auto *list_literal = storage.Create<ListLiteral>(std::vector<Expression *>{storage.Create<PrimitiveLiteral>(1),
                                                                             storage.Create<PrimitiveLiteral>("bla"),
                                                                             storage.Create<PrimitiveLiteral>(true)});
  TypedValue result = Eval(list_literal);
  ASSERT_TRUE(result.IsList());
  auto &result_elems = result.ValueList();
  ASSERT_EQ(3, result_elems.size());
  EXPECT_TRUE(result_elems[0].IsInt());
  EXPECT_TRUE(result_elems[1].IsString());
  EXPECT_TRUE(result_elems[2].IsBool());
}

TEST_F(ExpressionEvaluatorTest, ParameterLookup) {
  ctx.parameters.Add(0, memgraph::storage::v3::PropertyValue(42));
  auto *param_lookup = storage.Create<ParameterLookup>(0);
  auto value = Eval(param_lookup);
  ASSERT_TRUE(value.IsInt());
  EXPECT_EQ(value.ValueInt(), 42);
}

TEST_F(ExpressionEvaluatorTest, FunctionAll1) {
  AstStorage storage;
  auto *ident_x = IDENT("x");
  auto *all = ALL("x", LIST(LITERAL(1), LITERAL(1)), WHERE(EQ(ident_x, LITERAL(1))));
  const auto x_sym = symbol_table.CreateSymbol("x", true);
  all->identifier_->MapTo(x_sym);
  ident_x->MapTo(x_sym);
  auto value = Eval(all);
  ASSERT_TRUE(value.IsBool());
  EXPECT_TRUE(value.ValueBool());
}

TEST_F(ExpressionEvaluatorTest, FunctionAll2) {
  AstStorage storage;
  auto *ident_x = IDENT("x");
  auto *all = ALL("x", LIST(LITERAL(1), LITERAL(2)), WHERE(EQ(ident_x, LITERAL(1))));
  const auto x_sym = symbol_table.CreateSymbol("x", true);
  all->identifier_->MapTo(x_sym);
  ident_x->MapTo(x_sym);
  auto value = Eval(all);
  ASSERT_TRUE(value.IsBool());
  EXPECT_FALSE(value.ValueBool());
}

TEST_F(ExpressionEvaluatorTest, FunctionAllNullList) {
  AstStorage storage;
  auto *all = ALL("x", LITERAL(TypedValue()), WHERE(LITERAL(true)));
  const auto x_sym = symbol_table.CreateSymbol("x", true);
  all->identifier_->MapTo(x_sym);
  auto value = Eval(all);
  EXPECT_TRUE(value.IsNull());
}

TEST_F(ExpressionEvaluatorTest, FunctionAllNullElementInList1) {
  AstStorage storage;
  auto *ident_x = IDENT("x");
  auto *all = ALL("x", LIST(LITERAL(1), LITERAL(TypedValue())), WHERE(EQ(ident_x, LITERAL(1))));
  const auto x_sym = symbol_table.CreateSymbol("x", true);
  all->identifier_->MapTo(x_sym);
  ident_x->MapTo(x_sym);
  auto value = Eval(all);
  ASSERT_TRUE(value.IsBool());
  EXPECT_FALSE(value.ValueBool());
}

TEST_F(ExpressionEvaluatorTest, FunctionAllNullElementInList2) {
  AstStorage storage;
  auto *ident_x = IDENT("x");
  auto *all = ALL("x", LIST(LITERAL(2), LITERAL(TypedValue())), WHERE(EQ(ident_x, LITERAL(1))));
  const auto x_sym = symbol_table.CreateSymbol("x", true);
  all->identifier_->MapTo(x_sym);
  ident_x->MapTo(x_sym);
  auto value = Eval(all);
  ASSERT_TRUE(value.IsBool());
  EXPECT_FALSE(value.ValueBool());
}

TEST_F(ExpressionEvaluatorTest, FunctionAllWhereWrongType) {
  AstStorage storage;
  auto *all = ALL("x", LIST(LITERAL(1)), WHERE(LITERAL(2)));
  const auto x_sym = symbol_table.CreateSymbol("x", true);
  all->identifier_->MapTo(x_sym);
  EXPECT_THROW(Eval(all), ExpressionRuntimeException);
}

TEST_F(ExpressionEvaluatorTest, FunctionSingle1) {
  AstStorage storage;
  auto *ident_x = IDENT("x");
  auto *single = SINGLE("x", LIST(LITERAL(1), LITERAL(2)), WHERE(EQ(ident_x, LITERAL(1))));
  const auto x_sym = symbol_table.CreateSymbol("x", true);
  single->identifier_->MapTo(x_sym);
  ident_x->MapTo(x_sym);
  auto value = Eval(single);
  ASSERT_TRUE(value.IsBool());
  EXPECT_TRUE(value.ValueBool());
}

TEST_F(ExpressionEvaluatorTest, FunctionSingle2) {
  AstStorage storage;
  auto *ident_x = IDENT("x");
  auto *single = SINGLE("x", LIST(LITERAL(1), LITERAL(2)), WHERE(GREATER(ident_x, LITERAL(0))));
  const auto x_sym = symbol_table.CreateSymbol("x", true);
  single->identifier_->MapTo(x_sym);
  ident_x->MapTo(x_sym);
  auto value = Eval(single);
  ASSERT_TRUE(value.IsBool());
  EXPECT_FALSE(value.ValueBool());
}

TEST_F(ExpressionEvaluatorTest, FunctionSingleNullList) {
  AstStorage storage;
  auto *single = SINGLE("x", LITERAL(TypedValue()), WHERE(LITERAL(true)));
  const auto x_sym = symbol_table.CreateSymbol("x", true);
  single->identifier_->MapTo(x_sym);
  auto value = Eval(single);
  EXPECT_TRUE(value.IsNull());
}

TEST_F(ExpressionEvaluatorTest, FunctionSingleNullElementInList1) {
  AstStorage storage;
  auto *ident_x = IDENT("x");
  auto *single = SINGLE("x", LIST(LITERAL(1), LITERAL(TypedValue())), WHERE(EQ(ident_x, LITERAL(1))));
  const auto x_sym = symbol_table.CreateSymbol("x", true);
  single->identifier_->MapTo(x_sym);
  ident_x->MapTo(x_sym);
  auto value = Eval(single);
  ASSERT_TRUE(value.IsBool());
  EXPECT_TRUE(value.ValueBool());
}

TEST_F(ExpressionEvaluatorTest, FunctionSingleNullElementInList2) {
  AstStorage storage;
  auto *ident_x = IDENT("x");
  auto *single = SINGLE("x", LIST(LITERAL(2), LITERAL(TypedValue())), WHERE(EQ(ident_x, LITERAL(1))));
  const auto x_sym = symbol_table.CreateSymbol("x", true);
  single->identifier_->MapTo(x_sym);
  ident_x->MapTo(x_sym);
  auto value = Eval(single);
  ASSERT_TRUE(value.IsBool());
  EXPECT_FALSE(value.ValueBool());
}

TEST_F(ExpressionEvaluatorTest, FunctionAny1) {
  AstStorage storage;
  auto *ident_x = IDENT("x");
  auto *any = ANY("x", LIST(LITERAL(1), LITERAL(2)), WHERE(EQ(ident_x, LITERAL(1))));
  const auto x_sym = symbol_table.CreateSymbol("x", true);
  any->identifier_->MapTo(x_sym);
  ident_x->MapTo(x_sym);
  auto value = Eval(any);
  ASSERT_TRUE(value.IsBool());
  EXPECT_TRUE(value.ValueBool());
}

TEST_F(ExpressionEvaluatorTest, FunctionAny2) {
  AstStorage storage;
  auto *ident_x = IDENT("x");
  auto *any = ANY("x", LIST(LITERAL(1), LITERAL(2)), WHERE(EQ(ident_x, LITERAL(0))));
  const auto x_sym = symbol_table.CreateSymbol("x", true);
  any->identifier_->MapTo(x_sym);
  ident_x->MapTo(x_sym);
  auto value = Eval(any);
  ASSERT_TRUE(value.IsBool());
  EXPECT_FALSE(value.ValueBool());
}

TEST_F(ExpressionEvaluatorTest, FunctionAnyNullList) {
  AstStorage storage;
  auto *any = ANY("x", LITERAL(TypedValue()), WHERE(LITERAL(true)));
  const auto x_sym = symbol_table.CreateSymbol("x", true);
  any->identifier_->MapTo(x_sym);
  auto value = Eval(any);
  EXPECT_TRUE(value.IsNull());
}

TEST_F(ExpressionEvaluatorTest, FunctionAnyNullElementInList1) {
  AstStorage storage;
  auto *ident_x = IDENT("x");
  auto *any = ANY("x", LIST(LITERAL(0), LITERAL(TypedValue())), WHERE(EQ(ident_x, LITERAL(0))));
  const auto x_sym = symbol_table.CreateSymbol("x", true);
  any->identifier_->MapTo(x_sym);
  ident_x->MapTo(x_sym);
  auto value = Eval(any);
  EXPECT_TRUE(value.ValueBool());
}

TEST_F(ExpressionEvaluatorTest, FunctionAnyNullElementInList2) {
  AstStorage storage;
  auto *ident_x = IDENT("x");
  auto *any = ANY("x", LIST(LITERAL(1), LITERAL(TypedValue())), WHERE(EQ(ident_x, LITERAL(0))));
  const auto x_sym = symbol_table.CreateSymbol("x", true);
  any->identifier_->MapTo(x_sym);
  ident_x->MapTo(x_sym);
  auto value = Eval(any);
  EXPECT_FALSE(value.ValueBool());
}

TEST_F(ExpressionEvaluatorTest, FunctionAnyWhereWrongType) {
  AstStorage storage;
  auto *any = ANY("x", LIST(LITERAL(1)), WHERE(LITERAL(2)));
  const auto x_sym = symbol_table.CreateSymbol("x", true);
  any->identifier_->MapTo(x_sym);
  EXPECT_THROW(Eval(any), ExpressionRuntimeException);
}

TEST_F(ExpressionEvaluatorTest, FunctionNone1) {
  AstStorage storage;
  auto *ident_x = IDENT("x");
  auto *none = NONE("x", LIST(LITERAL(1), LITERAL(2)), WHERE(EQ(ident_x, LITERAL(0))));
  const auto x_sym = symbol_table.CreateSymbol("x", true);
  none->identifier_->MapTo(x_sym);
  ident_x->MapTo(x_sym);
  auto value = Eval(none);
  ASSERT_TRUE(value.IsBool());
  EXPECT_TRUE(value.ValueBool());
}

TEST_F(ExpressionEvaluatorTest, FunctionNone2) {
  AstStorage storage;
  auto *ident_x = IDENT("x");
  auto *none = NONE("x", LIST(LITERAL(1), LITERAL(2)), WHERE(EQ(ident_x, LITERAL(1))));
  const auto x_sym = symbol_table.CreateSymbol("x", true);
  none->identifier_->MapTo(x_sym);
  ident_x->MapTo(x_sym);
  auto value = Eval(none);
  ASSERT_TRUE(value.IsBool());
  EXPECT_FALSE(value.ValueBool());
}

TEST_F(ExpressionEvaluatorTest, FunctionNoneNullList) {
  AstStorage storage;
  auto *none = NONE("x", LITERAL(TypedValue()), WHERE(LITERAL(true)));
  const auto x_sym = symbol_table.CreateSymbol("x", true);
  none->identifier_->MapTo(x_sym);
  auto value = Eval(none);
  EXPECT_TRUE(value.IsNull());
}

TEST_F(ExpressionEvaluatorTest, FunctionNoneNullElementInList1) {
  AstStorage storage;
  auto *ident_x = IDENT("x");
  auto *any = NONE("x", LIST(LITERAL(1), LITERAL(TypedValue())), WHERE(EQ(ident_x, LITERAL(0))));
  const auto x_sym = symbol_table.CreateSymbol("x", true);
  any->identifier_->MapTo(x_sym);
  ident_x->MapTo(x_sym);
  auto value = Eval(any);
  EXPECT_TRUE(value.ValueBool());
}

TEST_F(ExpressionEvaluatorTest, FunctionNoneNullElementInList2) {
  AstStorage storage;
  auto *ident_x = IDENT("x");
  auto *none = NONE("x", LIST(LITERAL(0), LITERAL(TypedValue())), WHERE(EQ(ident_x, LITERAL(0))));
  const auto x_sym = symbol_table.CreateSymbol("x", true);
  none->identifier_->MapTo(x_sym);
  ident_x->MapTo(x_sym);
  auto value = Eval(none);
  EXPECT_FALSE(value.ValueBool());
}

TEST_F(ExpressionEvaluatorTest, FunctionNoneWhereWrongType) {
  AstStorage storage;
  auto *none = NONE("x", LIST(LITERAL(1)), WHERE(LITERAL(2)));
  const auto x_sym = symbol_table.CreateSymbol("x", true);
  none->identifier_->MapTo(x_sym);
  EXPECT_THROW(Eval(none), ExpressionRuntimeException);
}

TEST_F(ExpressionEvaluatorTest, FunctionReduce) {
  AstStorage storage;
  auto *ident_sum = IDENT("sum");
  auto *ident_x = IDENT("x");
  auto *reduce = REDUCE("sum", LITERAL(0), "x", LIST(LITERAL(1), LITERAL(2)), ADD(ident_sum, ident_x));
  const auto sum_sym = symbol_table.CreateSymbol("sum", true);
  reduce->accumulator_->MapTo(sum_sym);
  ident_sum->MapTo(sum_sym);
  const auto x_sym = symbol_table.CreateSymbol("x", true);
  reduce->identifier_->MapTo(x_sym);
  ident_x->MapTo(x_sym);
  auto value = Eval(reduce);
  ASSERT_TRUE(value.IsInt());
  EXPECT_EQ(value.ValueInt(), 3);
}

TEST_F(ExpressionEvaluatorTest, FunctionExtract) {
  AstStorage storage;
  auto *ident_x = IDENT("x");
  auto *extract = EXTRACT("x", LIST(LITERAL(1), LITERAL(2), LITERAL(TypedValue())), ADD(ident_x, LITERAL(1)));
  const auto x_sym = symbol_table.CreateSymbol("x", true);
  extract->identifier_->MapTo(x_sym);
  ident_x->MapTo(x_sym);
  auto value = Eval(extract);
  EXPECT_TRUE(value.IsList());
  ;
  auto result = value.ValueList();
  EXPECT_EQ(result[0].ValueInt(), 2);
  EXPECT_EQ(result[1].ValueInt(), 3);
  EXPECT_TRUE(result[2].IsNull());
}

TEST_F(ExpressionEvaluatorTest, FunctionExtractNull) {
  AstStorage storage;
  auto *ident_x = IDENT("x");
  auto *extract = EXTRACT("x", LITERAL(TypedValue()), ADD(ident_x, LITERAL(1)));
  const auto x_sym = symbol_table.CreateSymbol("x", true);
  extract->identifier_->MapTo(x_sym);
  ident_x->MapTo(x_sym);
  auto value = Eval(extract);
  EXPECT_TRUE(value.IsNull());
}

TEST_F(ExpressionEvaluatorTest, FunctionExtractExceptions) {
  AstStorage storage;
  auto *ident_x = IDENT("x");
  auto *extract = EXTRACT("x", LITERAL("bla"), ADD(ident_x, LITERAL(1)));
  const auto x_sym = symbol_table.CreateSymbol("x", true);
  extract->identifier_->MapTo(x_sym);
  ident_x->MapTo(x_sym);
  EXPECT_THROW(Eval(extract), ExpressionRuntimeException);
}

TEST_F(ExpressionEvaluatorTest, RegexMatchInvalidArguments) {
  EXPECT_TRUE(Eval(storage.Create<RegexMatch>(LITERAL(TypedValue()), LITERAL("regex"))).IsNull());
  EXPECT_TRUE(Eval(storage.Create<RegexMatch>(LITERAL(3), LITERAL("regex"))).IsNull());
  EXPECT_TRUE(Eval(storage.Create<RegexMatch>(LIST(LITERAL("string")), LITERAL("regex"))).IsNull());
  EXPECT_TRUE(Eval(storage.Create<RegexMatch>(LITERAL("string"), LITERAL(TypedValue()))).IsNull());
  EXPECT_THROW(Eval(storage.Create<RegexMatch>(LITERAL("string"), LITERAL(42))), ExpressionRuntimeException);
  EXPECT_THROW(Eval(storage.Create<RegexMatch>(LITERAL("string"), LIST(LITERAL("regex")))), ExpressionRuntimeException);
}

TEST_F(ExpressionEvaluatorTest, RegexMatchInvalidRegex) {
  EXPECT_THROW(Eval(storage.Create<RegexMatch>(LITERAL("text"), LITERAL("*ext"))), ExpressionRuntimeException);
  EXPECT_THROW(Eval(storage.Create<RegexMatch>(LITERAL("text"), LITERAL("[ext"))), ExpressionRuntimeException);
}

TEST_F(ExpressionEvaluatorTest, RegexMatch) {
  EXPECT_FALSE(Eval(storage.Create<RegexMatch>(LITERAL("text"), LITERAL(".*ex"))).ValueBool());
  EXPECT_TRUE(Eval(storage.Create<RegexMatch>(LITERAL("text"), LITERAL(".*ext"))).ValueBool());
  EXPECT_FALSE(Eval(storage.Create<RegexMatch>(LITERAL("text"), LITERAL("[ext]"))).ValueBool());
  EXPECT_TRUE(Eval(storage.Create<RegexMatch>(LITERAL("text"), LITERAL(".+[ext]"))).ValueBool());
}

class ExpressionEvaluatorPropertyLookup : public ExpressionEvaluatorTest {
 protected:
  std::pair<std::string, memgraph::storage::v3::PropertyId> prop_age =
      std::make_pair("age", request_router->NameToProperty("age"));
  std::pair<std::string, memgraph::storage::v3::PropertyId> prop_height =
      std::make_pair("height", request_router->NameToProperty("height"));
  Identifier *identifier = storage.Create<Identifier>("element");
  Symbol symbol = symbol_table.CreateSymbol("element", true);

  void SetUp() { identifier->MapTo(symbol); }

  auto Value(std::pair<std::string, memgraph::storage::v3::PropertyId> property) {
    auto *op = storage.Create<PropertyLookup>(identifier, storage.GetPropertyIx(property.first));
    return Eval(op);
  }
};

TEST_F(ExpressionEvaluatorPropertyLookup, Null) {
  frame[symbol] = TypedValue();
  EXPECT_TRUE(Value(prop_age).IsNull());
}

TEST_F(ExpressionEvaluatorPropertyLookup, MapLiteral) {
  frame[symbol] = TypedValue(std::map<std::string, TypedValue>{{prop_age.first, TypedValue(10)}});
  EXPECT_EQ(Value(prop_age).ValueInt(), 10);
  EXPECT_TRUE(Value(prop_height).IsNull());
}

class FunctionTest : public ExpressionEvaluatorTest {
 protected:
  std::vector<Expression *> ExpressionsFromTypedValues(const std::vector<TypedValue> &tvs) {
    std::vector<Expression *> expressions;
    expressions.reserve(tvs.size());

    for (size_t i = 0; i < tvs.size(); ++i) {
      auto *ident = storage.Create<Identifier>("arg_" + std::to_string(i), true);
      auto sym = symbol_table.CreateSymbol("arg_" + std::to_string(i), true);
      ident->MapTo(sym);
      frame[sym] = tvs[i];
      expressions.push_back(ident);
    }

    return expressions;
  }

  TypedValue EvaluateFunctionWithExprs(const std::string &function_name, const std::vector<Expression *> &expressions) {
    auto *op = storage.Create<Function>(function_name, expressions);
    return Eval(op);
  }

  template <class... TArgs>
  TypedValue EvaluateFunction(const std::string &function_name, std::tuple<TArgs...> args) {
    std::vector<TypedValue> tv_args;
    tv_args.reserve(args.size());
    for (auto &arg : args) tv_args.emplace_back(std::move(arg));
    return EvaluateFunctionWithExprs(function_name, ExpressionsFromTypedValues(tv_args));
  }

  template <class... TArgs>
  TypedValue EvaluateFunction(const std::string &function_name, TArgs &&...args) {
    return EvaluateFunctionWithExprs(function_name,
                                     ExpressionsFromTypedValues(std::vector<TypedValue>{TypedValue(args)...}));
  }
};

template <class... TArgs>
static TypedValue MakeTypedValueList(TArgs &&...args) {
  return TypedValue(std::vector<TypedValue>{TypedValue(args)...});
}

TEST_F(FunctionTest, EndNode) {
  ASSERT_THROW(EvaluateFunction("ENDNODE"), FunctionRuntimeException);
  ASSERT_TRUE(EvaluateFunction("ENDNODE", TypedValue()).IsNull());
  Label l{request_router->NameToLabel("label1")};
  EdgeId e_id{10};
  VertexId dst{l, {msgs::Value(static_cast<int64_t>(2))}};
  auto e = CreateEdge({}, request_router.get(), e_id, {}, dst);
  const auto res = EvaluateFunction("ENDNODE", e).ValueVertex().Id();
  ASSERT_EQ(res, dst);
  ASSERT_THROW(EvaluateFunction("ENDNODE", 2), FunctionRuntimeException);
}

TEST_F(FunctionTest, Head) {
  ASSERT_THROW(EvaluateFunction("HEAD"), FunctionRuntimeException);
  ASSERT_TRUE(EvaluateFunction("HEAD", TypedValue()).IsNull());
  auto argument = MakeTypedValueList(3, 4, 5);
  ASSERT_EQ(EvaluateFunction("HEAD", argument).ValueInt(), 3);
  argument.ValueList().clear();
  ASSERT_TRUE(EvaluateFunction("HEAD", argument).IsNull());
  ASSERT_THROW(EvaluateFunction("HEAD", 2), FunctionRuntimeException);
}

TEST_F(FunctionTest, Properties) {
  ASSERT_THROW(EvaluateFunction("PROPERTIES"), FunctionRuntimeException);
  ASSERT_TRUE(EvaluateFunction("PROPERTIES", TypedValue()).IsNull());
  const auto height = request_router->NameToProperty("height");
  const auto age = request_router->NameToProperty("age");
  auto v1 = CreateVertex({{height, Value(static_cast<int64_t>(5))}, {age, Value(static_cast<int64_t>(10))}},
                         request_router.get());
  auto e = CreateEdge({{height, Value(static_cast<int64_t>(3))}, {age, Value(static_cast<int64_t>(15))}},
                      request_router.get());

  auto prop_values_to_int = [](TypedValue t) {
    std::unordered_map<std::string, int> properties;
    for (auto property : t.ValueMap()) {
      properties[std::string(property.first)] = property.second.ValueInt();
    }
    return properties;
  };

  ASSERT_THAT(prop_values_to_int(EvaluateFunction("PROPERTIES", v1)),
              UnorderedElementsAre(testing::Pair("height", 5), testing::Pair("age", 10)));
  ASSERT_THAT(prop_values_to_int(EvaluateFunction("PROPERTIES", e)),
              UnorderedElementsAre(testing::Pair("height", 3), testing::Pair("age", 15)));
  ASSERT_THROW(EvaluateFunction("PROPERTIES", 2), FunctionRuntimeException);
}

TEST_F(FunctionTest, Last) {
  ASSERT_THROW(EvaluateFunction("LAST"), FunctionRuntimeException);
  ASSERT_TRUE(EvaluateFunction("LAST", TypedValue()).IsNull());
  auto argument = MakeTypedValueList(3, 4, 5);
  ASSERT_EQ(EvaluateFunction("LAST", argument).ValueInt(), 5);
  argument.ValueList().clear();
  ASSERT_TRUE(EvaluateFunction("LAST", argument).IsNull());
  ASSERT_THROW(EvaluateFunction("LAST", 5), FunctionRuntimeException);
}

TEST_F(FunctionTest, Size) {
  ASSERT_THROW(EvaluateFunction("SIZE"), FunctionRuntimeException);
  ASSERT_TRUE(EvaluateFunction("SIZE", TypedValue()).IsNull());
  auto argument = MakeTypedValueList(3, 4, 5);
  ASSERT_EQ(EvaluateFunction("SIZE", argument).ValueInt(), 3);
  ASSERT_EQ(EvaluateFunction("SIZE", "john").ValueInt(), 4);
  ASSERT_EQ(EvaluateFunction("SIZE",
                             std::map<std::string, TypedValue>{
                                 {"a", TypedValue(5)}, {"b", TypedValue(true)}, {"c", TypedValue("123")}})
                .ValueInt(),
            3);
  ASSERT_THROW(EvaluateFunction("SIZE", 5), FunctionRuntimeException);
}

TEST_F(FunctionTest, StartNode) {
  ASSERT_THROW(EvaluateFunction("STARTNODE"), FunctionRuntimeException);
  ASSERT_TRUE(EvaluateFunction("STARTNODE", TypedValue()).IsNull());
  Label l{request_router->NameToLabel("label1")};
  EdgeId e_id{5};
  VertexId src{l, {msgs::Value(static_cast<int64_t>(4))}};
  auto e = CreateEdge({}, request_router.get(), e_id, src);
  const auto res = EvaluateFunction("STARTNODE", e).ValueVertex().Id();
  ASSERT_EQ(res, src);
  ASSERT_THROW(EvaluateFunction("STARTNODE", 2), FunctionRuntimeException);
}

TEST_F(FunctionTest, ToBoolean) {
  ASSERT_THROW(EvaluateFunction("TOBOOLEAN"), FunctionRuntimeException);
  ASSERT_TRUE(EvaluateFunction("TOBOOLEAN", TypedValue()).IsNull());
  ASSERT_EQ(EvaluateFunction("TOBOOLEAN", 123).ValueBool(), true);
  ASSERT_EQ(EvaluateFunction("TOBOOLEAN", -213).ValueBool(), true);
  ASSERT_EQ(EvaluateFunction("TOBOOLEAN", 0).ValueBool(), false);
  ASSERT_EQ(EvaluateFunction("TOBOOLEAN", " trUE \n\t").ValueBool(), true);
  ASSERT_EQ(EvaluateFunction("TOBOOLEAN", "\n\tFalsE").ValueBool(), false);
  ASSERT_TRUE(EvaluateFunction("TOBOOLEAN", "\n\tFALSEA ").IsNull());
  ASSERT_EQ(EvaluateFunction("TOBOOLEAN", true).ValueBool(), true);
  ASSERT_EQ(EvaluateFunction("TOBOOLEAN", false).ValueBool(), false);
}

TEST_F(FunctionTest, ToFloat) {
  ASSERT_THROW(EvaluateFunction("TOFLOAT"), FunctionRuntimeException);
  ASSERT_TRUE(EvaluateFunction("TOFLOAT", TypedValue()).IsNull());
  ASSERT_EQ(EvaluateFunction("TOFLOAT", " -3.5 \n\t").ValueDouble(), -3.5);
  ASSERT_EQ(EvaluateFunction("TOFLOAT", "\n\t0.5e-1").ValueDouble(), 0.05);
  ASSERT_TRUE(EvaluateFunction("TOFLOAT", "\n\t3.4e-3X ").IsNull());
  ASSERT_EQ(EvaluateFunction("TOFLOAT", -3.5).ValueDouble(), -3.5);
  ASSERT_EQ(EvaluateFunction("TOFLOAT", -3).ValueDouble(), -3.0);
  ASSERT_THROW(EvaluateFunction("TOFLOAT", true), FunctionRuntimeException);
}

TEST_F(FunctionTest, ToInteger) {
  ASSERT_THROW(EvaluateFunction("TOINTEGER"), FunctionRuntimeException);
  ASSERT_TRUE(EvaluateFunction("TOINTEGER", TypedValue()).IsNull());
  ASSERT_EQ(EvaluateFunction("TOINTEGER", false).ValueInt(), 0);
  ASSERT_EQ(EvaluateFunction("TOINTEGER", true).ValueInt(), 1);
  ASSERT_EQ(EvaluateFunction("TOINTEGER", "\n\t3").ValueInt(), 3);
  ASSERT_EQ(EvaluateFunction("TOINTEGER", " -3.5 \n\t").ValueInt(), -3);
  ASSERT_TRUE(EvaluateFunction("TOINTEGER", "\n\t3X ").IsNull());
  ASSERT_EQ(EvaluateFunction("TOINTEGER", -3.5).ValueInt(), -3);
  ASSERT_EQ(EvaluateFunction("TOINTEGER", 3.5).ValueInt(), 3);
}

TEST_F(FunctionTest, Type) {
  ASSERT_THROW(EvaluateFunction("TYPE"), FunctionRuntimeException);
  ASSERT_TRUE(EvaluateFunction("TYPE", TypedValue()).IsNull());
  auto e = CreateEdge({}, request_router.get());
  ASSERT_EQ(EvaluateFunction("TYPE", e).ValueString(), "edge_type");
  ASSERT_THROW(EvaluateFunction("TYPE", 2), FunctionRuntimeException);
}

TEST_F(FunctionTest, ValueType) {
  ASSERT_THROW(EvaluateFunction("VALUETYPE"), FunctionRuntimeException);
  ASSERT_THROW(EvaluateFunction("VALUETYPE", TypedValue(), TypedValue()), FunctionRuntimeException);
  ASSERT_EQ(EvaluateFunction("VALUETYPE", TypedValue()).ValueString(), "NULL");
  ASSERT_EQ(EvaluateFunction("VALUETYPE", TypedValue(true)).ValueString(), "BOOLEAN");
  ASSERT_EQ(EvaluateFunction("VALUETYPE", TypedValue(1)).ValueString(), "INTEGER");
  ASSERT_EQ(EvaluateFunction("VALUETYPE", TypedValue(1.1)).ValueString(), "FLOAT");
  ASSERT_EQ(EvaluateFunction("VALUETYPE", TypedValue("test")).ValueString(), "STRING");
  ASSERT_EQ(
      EvaluateFunction("VALUETYPE", TypedValue(std::vector<TypedValue>{TypedValue(1), TypedValue(2)})).ValueString(),
      "LIST");
  ASSERT_EQ(EvaluateFunction("VALUETYPE", TypedValue(std::map<std::string, TypedValue>{{"test", TypedValue(1)}}))
                .ValueString(),
            "MAP");
  auto v1 = CreateVertex({}, request_router.get());
  ASSERT_EQ(EvaluateFunction("VALUETYPE", v1).ValueString(), "NODE");
  auto e = CreateEdge({}, request_router.get());
  ASSERT_EQ(EvaluateFunction("VALUETYPE", e).ValueString(), "RELATIONSHIP");
}

TEST_F(FunctionTest, Labels) {
  ASSERT_THROW(EvaluateFunction("LABELS"), FunctionRuntimeException);
  ASSERT_TRUE(EvaluateFunction("LABELS", TypedValue()).IsNull());
  Label label{request_router->NameToLabel("label1")};
  auto v = CreateVertex({}, request_router.get(), {{}, {label}});
  std::vector<std::string> labels;
  auto evaluated_labels = EvaluateFunction("LABELS", v).ValueList();
  labels.reserve(evaluated_labels.size());
  for (auto label : evaluated_labels) {
    labels.emplace_back(label.ValueString());
  }
  ASSERT_THAT(labels, UnorderedElementsAre("label1"));
  ASSERT_THROW(EvaluateFunction("LABELS", 2), FunctionRuntimeException);
}

TEST_F(FunctionTest, Range) {
  EXPECT_THROW(EvaluateFunction("RANGE"), FunctionRuntimeException);
  EXPECT_TRUE(EvaluateFunction("RANGE", 1, 2, TypedValue()).IsNull());
  EXPECT_THROW(EvaluateFunction("RANGE", 1, TypedValue(), 1.3), FunctionRuntimeException);
  EXPECT_THROW(EvaluateFunction("RANGE", 1, 2, 0), FunctionRuntimeException);
  EXPECT_THAT(ToIntList(EvaluateFunction("RANGE", 1, 3)), ElementsAre(1, 2, 3));
  EXPECT_THAT(ToIntList(EvaluateFunction("RANGE", -1, 5, 2)), ElementsAre(-1, 1, 3, 5));
  EXPECT_THAT(ToIntList(EvaluateFunction("RANGE", 2, 10, 3)), ElementsAre(2, 5, 8));
  EXPECT_THAT(ToIntList(EvaluateFunction("RANGE", 2, 2, 2)), ElementsAre(2));
  EXPECT_THAT(ToIntList(EvaluateFunction("RANGE", 3, 0, 5)), ElementsAre());
  EXPECT_THAT(ToIntList(EvaluateFunction("RANGE", 5, 1, -2)), ElementsAre(5, 3, 1));
  EXPECT_THAT(ToIntList(EvaluateFunction("RANGE", 6, 1, -2)), ElementsAre(6, 4, 2));
  EXPECT_THAT(ToIntList(EvaluateFunction("RANGE", 2, 2, -3)), ElementsAre(2));
  EXPECT_THAT(ToIntList(EvaluateFunction("RANGE", -2, 4, -1)), ElementsAre());
}

TEST_F(FunctionTest, Tail) {
  ASSERT_THROW(EvaluateFunction("TAIL"), FunctionRuntimeException);
  ASSERT_TRUE(EvaluateFunction("TAIL", TypedValue()).IsNull());
  auto argument = MakeTypedValueList();
  ASSERT_EQ(EvaluateFunction("TAIL", argument).ValueList().size(), 0U);
  argument = MakeTypedValueList(3, 4, true, "john");
  auto list = EvaluateFunction("TAIL", argument).ValueList();
  ASSERT_EQ(list.size(), 3U);
  ASSERT_EQ(list[0].ValueInt(), 4);
  ASSERT_EQ(list[1].ValueBool(), true);
  ASSERT_EQ(list[2].ValueString(), "john");
  ASSERT_THROW(EvaluateFunction("TAIL", 2), FunctionRuntimeException);
}

TEST_F(FunctionTest, UniformSample) {
  ASSERT_THROW(EvaluateFunction("UNIFORMSAMPLE"), FunctionRuntimeException);
  ASSERT_TRUE(EvaluateFunction("UNIFORMSAMPLE", TypedValue(), TypedValue()).IsNull());
  ASSERT_TRUE(EvaluateFunction("UNIFORMSAMPLE", TypedValue(), 1).IsNull());
  ASSERT_TRUE(EvaluateFunction("UNIFORMSAMPLE", MakeTypedValueList(), TypedValue()).IsNull());
  ASSERT_TRUE(EvaluateFunction("UNIFORMSAMPLE", MakeTypedValueList(), 1).IsNull());
  ASSERT_THROW(EvaluateFunction("UNIFORMSAMPLE", MakeTypedValueList(1, 2, 3), -1), FunctionRuntimeException);
  ASSERT_EQ(EvaluateFunction("UNIFORMSAMPLE", MakeTypedValueList(1, 2, 3), 0).ValueList().size(), 0);
  ASSERT_EQ(EvaluateFunction("UNIFORMSAMPLE", MakeTypedValueList(1, 2, 3), 2).ValueList().size(), 2);
  ASSERT_EQ(EvaluateFunction("UNIFORMSAMPLE", MakeTypedValueList(1, 2, 3), 3).ValueList().size(), 3);
  ASSERT_EQ(EvaluateFunction("UNIFORMSAMPLE", MakeTypedValueList(1, 2, 3), 5).ValueList().size(), 5);
}

TEST_F(FunctionTest, Abs) {
  ASSERT_THROW(EvaluateFunction("ABS"), FunctionRuntimeException);
  ASSERT_TRUE(EvaluateFunction("ABS", TypedValue()).IsNull());
  ASSERT_EQ(EvaluateFunction("ABS", -2).ValueInt(), 2);
  ASSERT_EQ(EvaluateFunction("ABS", -2.5).ValueDouble(), 2.5);
  ASSERT_THROW(EvaluateFunction("ABS", true), FunctionRuntimeException);
}

//// Test if log works. If it does then all functions wrapped with
//// WRAP_CMATH_FLOAT_FUNCTION macro should work and are not gonna be tested for
//// correctnes..
TEST_F(FunctionTest, Log) {
  ASSERT_THROW(EvaluateFunction("LOG"), FunctionRuntimeException);
  ASSERT_TRUE(EvaluateFunction("LOG", TypedValue()).IsNull());
  ASSERT_DOUBLE_EQ(EvaluateFunction("LOG", 2).ValueDouble(), log(2));
  ASSERT_DOUBLE_EQ(EvaluateFunction("LOG", 1.5).ValueDouble(), log(1.5));
  // Not portable, but should work on most platforms.
  ASSERT_TRUE(std::isnan(EvaluateFunction("LOG", -1.5).ValueDouble()));
  ASSERT_THROW(EvaluateFunction("LOG", true), FunctionRuntimeException);
}

//// Function Round wraps round from cmath and will work if FunctionTest.Log test
//// passes. This test is used to show behavior of round since it differs from
//// neo4j's round.
TEST_F(FunctionTest, Round) {
  ASSERT_THROW(EvaluateFunction("ROUND"), FunctionRuntimeException);
  ASSERT_TRUE(EvaluateFunction("ROUND", TypedValue()).IsNull());
  ASSERT_EQ(EvaluateFunction("ROUND", -2).ValueDouble(), -2);
  ASSERT_EQ(EvaluateFunction("ROUND", -2.4).ValueDouble(), -2);
  ASSERT_EQ(EvaluateFunction("ROUND", -2.5).ValueDouble(), -3);
  ASSERT_EQ(EvaluateFunction("ROUND", -2.6).ValueDouble(), -3);
  ASSERT_EQ(EvaluateFunction("ROUND", 2.4).ValueDouble(), 2);
  ASSERT_EQ(EvaluateFunction("ROUND", 2.5).ValueDouble(), 3);
  ASSERT_EQ(EvaluateFunction("ROUND", 2.6).ValueDouble(), 3);
  ASSERT_THROW(EvaluateFunction("ROUND", true), FunctionRuntimeException);
}

// Check if wrapped functions are callable (check if everything was spelled
// correctly...). Wrapper correctnes is checked in FunctionTest.Log function
// test.
TEST_F(FunctionTest, WrappedMathFunctions) {
  for (auto function_name :
       {"FLOOR", "CEIL", "ROUND", "EXP", "LOG", "LOG10", "SQRT", "ACOS", "ASIN", "ATAN", "COS", "SIN", "TAN"}) {
    EvaluateFunction(function_name, 0.5);
  }
}

TEST_F(FunctionTest, Atan2) {
  ASSERT_THROW(EvaluateFunction("ATAN2"), FunctionRuntimeException);
  ASSERT_TRUE(EvaluateFunction("ATAN2", TypedValue(), 1).IsNull());
  ASSERT_TRUE(EvaluateFunction("ATAN2", 1, TypedValue()).IsNull());
  ASSERT_DOUBLE_EQ(EvaluateFunction("ATAN2", 2, -1.0).ValueDouble(), atan2(2, -1));
  ASSERT_THROW(EvaluateFunction("ATAN2", 3.0, true), FunctionRuntimeException);
}

TEST_F(FunctionTest, Sign) {
  ASSERT_THROW(EvaluateFunction("SIGN"), FunctionRuntimeException);
  ASSERT_TRUE(EvaluateFunction("SIGN", TypedValue()).IsNull());
  ASSERT_EQ(EvaluateFunction("SIGN", -2).ValueInt(), -1);
  ASSERT_EQ(EvaluateFunction("SIGN", -0.2).ValueInt(), -1);
  ASSERT_EQ(EvaluateFunction("SIGN", 0.0).ValueInt(), 0);
  ASSERT_EQ(EvaluateFunction("SIGN", 2.5).ValueInt(), 1);
  ASSERT_THROW(EvaluateFunction("SIGN", true), FunctionRuntimeException);
}

TEST_F(FunctionTest, E) {
  ASSERT_THROW(EvaluateFunction("E", 1), FunctionRuntimeException);
  ASSERT_DOUBLE_EQ(EvaluateFunction("E").ValueDouble(), M_E);
}

TEST_F(FunctionTest, Pi) {
  ASSERT_THROW(EvaluateFunction("PI", 1), FunctionRuntimeException);
  ASSERT_DOUBLE_EQ(EvaluateFunction("PI").ValueDouble(), M_PI);
}

TEST_F(FunctionTest, Rand) {
  ASSERT_THROW(EvaluateFunction("RAND", 1), FunctionRuntimeException);
  ASSERT_GE(EvaluateFunction("RAND").ValueDouble(), 0.0);
  ASSERT_LT(EvaluateFunction("RAND").ValueDouble(), 1.0);
}

using memgraph::functions::kStartsWith;
TEST_F(FunctionTest, StartsWith) {
  EXPECT_THROW(EvaluateFunction(kStartsWith), FunctionRuntimeException);
  EXPECT_TRUE(EvaluateFunction(kStartsWith, "a", TypedValue()).IsNull());
  EXPECT_THROW(EvaluateFunction(kStartsWith, TypedValue(), 1.3), FunctionRuntimeException);
  EXPECT_TRUE(EvaluateFunction(kStartsWith, "abc", "abc").ValueBool());
  EXPECT_TRUE(EvaluateFunction(kStartsWith, "abcdef", "abc").ValueBool());
  EXPECT_FALSE(EvaluateFunction(kStartsWith, "abcdef", "aBc").ValueBool());
  EXPECT_FALSE(EvaluateFunction(kStartsWith, "abc", "abcd").ValueBool());
}

using memgraph::functions::kEndsWith;
TEST_F(FunctionTest, EndsWith) {
  EXPECT_THROW(EvaluateFunction(kEndsWith), FunctionRuntimeException);
  EXPECT_TRUE(EvaluateFunction(kEndsWith, "a", TypedValue()).IsNull());
  EXPECT_THROW(EvaluateFunction(kEndsWith, TypedValue(), 1.3), FunctionRuntimeException);
  EXPECT_TRUE(EvaluateFunction(kEndsWith, "abc", "abc").ValueBool());
  EXPECT_TRUE(EvaluateFunction(kEndsWith, "abcdef", "def").ValueBool());
  EXPECT_FALSE(EvaluateFunction(kEndsWith, "abcdef", "dEf").ValueBool());
  EXPECT_FALSE(EvaluateFunction(kEndsWith, "bcd", "abcd").ValueBool());
}

using memgraph::functions::kContains;
TEST_F(FunctionTest, Contains) {
  EXPECT_THROW(EvaluateFunction(kContains), FunctionRuntimeException);
  EXPECT_TRUE(EvaluateFunction(kContains, "a", TypedValue()).IsNull());
  EXPECT_THROW(EvaluateFunction(kContains, TypedValue(), 1.3), FunctionRuntimeException);
  EXPECT_TRUE(EvaluateFunction(kContains, "abc", "abc").ValueBool());
  EXPECT_TRUE(EvaluateFunction(kContains, "abcde", "bcd").ValueBool());
  EXPECT_FALSE(EvaluateFunction(kContains, "cde", "abcdef").ValueBool());
  EXPECT_FALSE(EvaluateFunction(kContains, "abcdef", "dEf").ValueBool());
}

TEST_F(FunctionTest, Assert) {
  // Invalid calls.
  ASSERT_THROW(EvaluateFunction("ASSERT"), FunctionRuntimeException);
  ASSERT_THROW(EvaluateFunction("ASSERT", false, false), FunctionRuntimeException);
  ASSERT_THROW(EvaluateFunction("ASSERT", "string", false), FunctionRuntimeException);
  ASSERT_THROW(EvaluateFunction("ASSERT", false, "reason", true), FunctionRuntimeException);

  // Valid calls, assertion fails.
  ASSERT_THROW(EvaluateFunction("ASSERT", false), FunctionRuntimeException);
  ASSERT_THROW(EvaluateFunction("ASSERT", false, "message"), FunctionRuntimeException);
  try {
    EvaluateFunction("ASSERT", false, "bbgba");
  } catch (FunctionRuntimeException &e) {
    ASSERT_TRUE(std::string(e.what()).find("bbgba") != std::string::npos);
  }

  // Valid calls, assertion passes.
  ASSERT_TRUE(EvaluateFunction("ASSERT", true).ValueBool());
  ASSERT_TRUE(EvaluateFunction("ASSERT", true, "message").ValueBool());
}

TEST_F(FunctionTest, Counter) {
  EXPECT_THROW(EvaluateFunction("COUNTER"), FunctionRuntimeException);
  EXPECT_THROW(EvaluateFunction("COUNTER", "a"), FunctionRuntimeException);
  EXPECT_THROW(EvaluateFunction("COUNTER", "a", "b"), FunctionRuntimeException);
  EXPECT_THROW(EvaluateFunction("COUNTER", "a", "b", "c"), FunctionRuntimeException);

  EXPECT_EQ(EvaluateFunction("COUNTER", "c1", 0).ValueInt(), 0);
  EXPECT_EQ(EvaluateFunction("COUNTER", "c1", 0).ValueInt(), 1);
  EXPECT_EQ(EvaluateFunction("COUNTER", "c2", 0).ValueInt(), 0);
  EXPECT_EQ(EvaluateFunction("COUNTER", "c1", 0).ValueInt(), 2);
  EXPECT_EQ(EvaluateFunction("COUNTER", "c2", 0).ValueInt(), 1);

  EXPECT_EQ(EvaluateFunction("COUNTER", "c3", -1).ValueInt(), -1);
  EXPECT_EQ(EvaluateFunction("COUNTER", "c3", -1).ValueInt(), 0);
  EXPECT_EQ(EvaluateFunction("COUNTER", "c3", -1).ValueInt(), 1);

  EXPECT_EQ(EvaluateFunction("COUNTER", "c4", 0, 5).ValueInt(), 0);
  EXPECT_EQ(EvaluateFunction("COUNTER", "c4", 0, 5).ValueInt(), 5);
  EXPECT_EQ(EvaluateFunction("COUNTER", "c4", 0, 5).ValueInt(), 10);

  EXPECT_EQ(EvaluateFunction("COUNTER", "c5", 0, -5).ValueInt(), 0);
  EXPECT_EQ(EvaluateFunction("COUNTER", "c5", 0, -5).ValueInt(), -5);
  EXPECT_EQ(EvaluateFunction("COUNTER", "c5", 0, -5).ValueInt(), -10);

  EXPECT_THROW(EvaluateFunction("COUNTER", "c6", 0, 0), FunctionRuntimeException);
}

TEST_F(FunctionTest, Id) {
  auto v = CreateVertex({}, request_router.get());
  EXPECT_THROW(EvaluateFunction("ID", v), FunctionRuntimeException);
  auto e = CreateEdge({}, request_router.get(), EdgeId{10});
  EXPECT_EQ(EvaluateFunction("ID", e).ValueInt(), 10);
}

TEST_F(FunctionTest, ToStringNull) { EXPECT_TRUE(EvaluateFunction("TOSTRING", TypedValue()).IsNull()); }

TEST_F(FunctionTest, ToStringString) {
  EXPECT_EQ(EvaluateFunction("TOSTRING", "").ValueString(), "");
  EXPECT_EQ(EvaluateFunction("TOSTRING", "this is a string").ValueString(), "this is a string");
}

TEST_F(FunctionTest, ToStringInteger) {
  EXPECT_EQ(EvaluateFunction("TOSTRING", -23321312).ValueString(), "-23321312");
  EXPECT_EQ(EvaluateFunction("TOSTRING", 0).ValueString(), "0");
  EXPECT_EQ(EvaluateFunction("TOSTRING", 42).ValueString(), "42");
}

TEST_F(FunctionTest, ToStringDouble) {
  EXPECT_EQ(EvaluateFunction("TOSTRING", -42.42).ValueString(), "-42.420000");
  EXPECT_EQ(EvaluateFunction("TOSTRING", 0.0).ValueString(), "0.000000");
  EXPECT_EQ(EvaluateFunction("TOSTRING", 238910.2313217).ValueString(), "238910.231322");
}

TEST_F(FunctionTest, ToStringBool) {
  EXPECT_EQ(EvaluateFunction("TOSTRING", true).ValueString(), "true");
  EXPECT_EQ(EvaluateFunction("TOSTRING", false).ValueString(), "false");
}

TEST_F(FunctionTest, ToStringExceptions) {
  EXPECT_THROW(EvaluateFunction("TOSTRING", 1, 2, 3), FunctionRuntimeException);
}

TEST_F(FunctionTest, Left) {
  EXPECT_THROW(EvaluateFunction("LEFT"), FunctionRuntimeException);

  EXPECT_TRUE(EvaluateFunction("LEFT", TypedValue(), TypedValue()).IsNull());
  EXPECT_TRUE(EvaluateFunction("LEFT", TypedValue(), 10).IsNull());
  EXPECT_THROW(EvaluateFunction("LEFT", TypedValue(), -10), FunctionRuntimeException);

  EXPECT_EQ(EvaluateFunction("LEFT", "memgraph", 0).ValueString(), "");
  EXPECT_EQ(EvaluateFunction("LEFT", "memgraph", 3).ValueString(), "mem");
  EXPECT_EQ(EvaluateFunction("LEFT", "memgraph", 1000).ValueString(), "memgraph");
  EXPECT_THROW(EvaluateFunction("LEFT", "memgraph", -10), FunctionRuntimeException);
  EXPECT_THROW(EvaluateFunction("LEFT", "memgraph", "graph"), FunctionRuntimeException);

  EXPECT_THROW(EvaluateFunction("LEFT", 132, 10), FunctionRuntimeException);
}

TEST_F(FunctionTest, Right) {
  EXPECT_THROW(EvaluateFunction("RIGHT"), FunctionRuntimeException);

  EXPECT_TRUE(EvaluateFunction("RIGHT", TypedValue(), TypedValue()).IsNull());
  EXPECT_TRUE(EvaluateFunction("RIGHT", TypedValue(), 10).IsNull());
  EXPECT_THROW(EvaluateFunction("RIGHT", TypedValue(), -10), FunctionRuntimeException);

  EXPECT_EQ(EvaluateFunction("RIGHT", "memgraph", 0).ValueString(), "");
  EXPECT_EQ(EvaluateFunction("RIGHT", "memgraph", 3).ValueString(), "aph");
  EXPECT_EQ(EvaluateFunction("RIGHT", "memgraph", 1000).ValueString(), "memgraph");
  EXPECT_THROW(EvaluateFunction("RIGHT", "memgraph", -10), FunctionRuntimeException);
  EXPECT_THROW(EvaluateFunction("RIGHT", "memgraph", "graph"), FunctionRuntimeException);

  EXPECT_THROW(EvaluateFunction("RIGHT", 132, 10), FunctionRuntimeException);
}

TEST_F(FunctionTest, Trimming) {
  EXPECT_TRUE(EvaluateFunction("LTRIM", TypedValue()).IsNull());
  EXPECT_TRUE(EvaluateFunction("RTRIM", TypedValue()).IsNull());
  EXPECT_TRUE(EvaluateFunction("TRIM", TypedValue()).IsNull());

  EXPECT_EQ(EvaluateFunction("LTRIM", "  abc    ").ValueString(), "abc    ");
  EXPECT_EQ(EvaluateFunction("RTRIM", " abc ").ValueString(), " abc");
  EXPECT_EQ(EvaluateFunction("TRIM", "abc").ValueString(), "abc");

  EXPECT_THROW(EvaluateFunction("LTRIM", "x", "y"), FunctionRuntimeException);
  EXPECT_THROW(EvaluateFunction("RTRIM", "x", "y"), FunctionRuntimeException);
  EXPECT_THROW(EvaluateFunction("TRIM", "x", "y"), FunctionRuntimeException);
}

TEST_F(FunctionTest, Reverse) {
  EXPECT_TRUE(EvaluateFunction("REVERSE", TypedValue()).IsNull());
  EXPECT_EQ(EvaluateFunction("REVERSE", "abc").ValueString(), "cba");
  EXPECT_THROW(EvaluateFunction("REVERSE", "x", "y"), FunctionRuntimeException);
}

TEST_F(FunctionTest, Replace) {
  EXPECT_THROW(EvaluateFunction("REPLACE"), FunctionRuntimeException);
  EXPECT_TRUE(EvaluateFunction("REPLACE", TypedValue(), "l", "w").IsNull());
  EXPECT_TRUE(EvaluateFunction("REPLACE", "hello", TypedValue(), "w").IsNull());
  EXPECT_TRUE(EvaluateFunction("REPLACE", "hello", "l", TypedValue()).IsNull());
  EXPECT_EQ(EvaluateFunction("REPLACE", "hello", "l", "w").ValueString(), "hewwo");

  EXPECT_THROW(EvaluateFunction("REPLACE", 1, "l", "w"), FunctionRuntimeException);
  EXPECT_THROW(EvaluateFunction("REPLACE", "hello", 1, "w"), FunctionRuntimeException);
  EXPECT_THROW(EvaluateFunction("REPLACE", "hello", "l", 1), FunctionRuntimeException);
}

TEST_F(FunctionTest, Split) {
  EXPECT_THROW(EvaluateFunction("SPLIT"), FunctionRuntimeException);
  EXPECT_THROW(EvaluateFunction("SPLIT", "one,two", 1), FunctionRuntimeException);
  EXPECT_THROW(EvaluateFunction("SPLIT", 1, "one,two"), FunctionRuntimeException);

  EXPECT_TRUE(EvaluateFunction("SPLIT", TypedValue(), TypedValue()).IsNull());
  EXPECT_TRUE(EvaluateFunction("SPLIT", "one,two", TypedValue()).IsNull());
  EXPECT_TRUE(EvaluateFunction("SPLIT", TypedValue(), ",").IsNull());

  auto result = EvaluateFunction("SPLIT", "one,two", ",");
  EXPECT_TRUE(result.IsList());
  EXPECT_EQ(result.ValueList()[0].ValueString(), "one");
  EXPECT_EQ(result.ValueList()[1].ValueString(), "two");
}

TEST_F(FunctionTest, Substring) {
  EXPECT_THROW(EvaluateFunction("SUBSTRING"), FunctionRuntimeException);

  EXPECT_TRUE(EvaluateFunction("SUBSTRING", TypedValue(), 0, 10).IsNull());
  EXPECT_THROW(EvaluateFunction("SUBSTRING", TypedValue(), TypedValue()), FunctionRuntimeException);
  EXPECT_THROW(EvaluateFunction("SUBSTRING", TypedValue(), -10), FunctionRuntimeException);
  EXPECT_THROW(EvaluateFunction("SUBSTRING", TypedValue(), 0, TypedValue()), FunctionRuntimeException);
  EXPECT_THROW(EvaluateFunction("SUBSTRING", TypedValue(), 0, -10), FunctionRuntimeException);

  EXPECT_EQ(EvaluateFunction("SUBSTRING", "hello", 2).ValueString(), "llo");
  EXPECT_EQ(EvaluateFunction("SUBSTRING", "hello", 10).ValueString(), "");
  EXPECT_EQ(EvaluateFunction("SUBSTRING", "hello", 2, 0).ValueString(), "");
  EXPECT_EQ(EvaluateFunction("SUBSTRING", "hello", 1, 3).ValueString(), "ell");
  EXPECT_EQ(EvaluateFunction("SUBSTRING", "hello", 1, 4).ValueString(), "ello");
  EXPECT_EQ(EvaluateFunction("SUBSTRING", "hello", 1, 10).ValueString(), "ello");
}

TEST_F(FunctionTest, ToLower) {
  EXPECT_THROW(EvaluateFunction("TOLOWER"), FunctionRuntimeException);
  EXPECT_TRUE(EvaluateFunction("TOLOWER", TypedValue()).IsNull());
  EXPECT_EQ(EvaluateFunction("TOLOWER", "Ab__C").ValueString(), "ab__c");
}

TEST_F(FunctionTest, ToUpper) {
  EXPECT_THROW(EvaluateFunction("TOUPPER"), FunctionRuntimeException);
  EXPECT_TRUE(EvaluateFunction("TOUPPER", TypedValue()).IsNull());
  EXPECT_EQ(EvaluateFunction("TOUPPER", "Ab__C").ValueString(), "AB__C");
}

TEST_F(FunctionTest, ToByteString) {
  EXPECT_THROW(EvaluateFunction("TOBYTESTRING"), FunctionRuntimeException);
  EXPECT_THROW(EvaluateFunction("TOBYTESTRING", 42), FunctionRuntimeException);
  EXPECT_THROW(EvaluateFunction("TOBYTESTRING", TypedValue()), FunctionRuntimeException);
  EXPECT_THROW(EvaluateFunction("TOBYTESTRING", "", 42), FunctionRuntimeException);
  EXPECT_THROW(EvaluateFunction("TOBYTESTRING", "ff"), FunctionRuntimeException);
  EXPECT_THROW(EvaluateFunction("TOBYTESTRING", "00"), FunctionRuntimeException);
  EXPECT_THROW(EvaluateFunction("TOBYTESTRING", "0xG"), FunctionRuntimeException);
  EXPECT_EQ(EvaluateFunction("TOBYTESTRING", "").ValueString(), "");
  EXPECT_EQ(EvaluateFunction("TOBYTESTRING", "0x").ValueString(), "");
  EXPECT_EQ(EvaluateFunction("TOBYTESTRING", "0X").ValueString(), "");
  EXPECT_EQ(EvaluateFunction("TOBYTESTRING", "0x0123456789aAbBcCdDeEfF").ValueString(),
            "\x01\x23\x45\x67\x89\xAA\xBB\xCC\xDD\xEE\xFF");
  EXPECT_EQ(EvaluateFunction("TOBYTESTRING", "0x042").ValueString().size(), 2);
  EXPECT_EQ(EvaluateFunction("TOBYTESTRING", "0x042").ValueString(),
            memgraph::utils::pmr::string("\x00\x42", 2, memgraph::utils::NewDeleteResource()));
}

TEST_F(FunctionTest, FromByteString) {
  EXPECT_THROW(EvaluateFunction("FROMBYTESTRING"), FunctionRuntimeException);
  EXPECT_THROW(EvaluateFunction("FROMBYTESTRING", 42), FunctionRuntimeException);
  EXPECT_THROW(EvaluateFunction("FROMBYTESTRING", TypedValue()), FunctionRuntimeException);
  EXPECT_EQ(EvaluateFunction("FROMBYTESTRING", "").ValueString(), "");
  auto bytestring = EvaluateFunction("TOBYTESTRING", "0x123456789aAbBcCdDeEfF");
  EXPECT_EQ(EvaluateFunction("FROMBYTESTRING", bytestring).ValueString(), "0x0123456789aabbccddeeff");
  EXPECT_EQ(EvaluateFunction("FROMBYTESTRING", std::string("\x00\x42", 2)).ValueString(), "0x0042");
}

}  // namespace memgraph::query::v2::tests