// 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.

#pragma once

#include <chrono>
#include <iostream>
#include <map>
#include <memory>
#include <optional>
#include <unordered_map>
#include <utility>
#include <variant>
#include <vector>

#include "coordinator/hybrid_logical_clock.hpp"
#include "storage/v3/id_types.hpp"
#include "storage/v3/property_value.hpp"

namespace memgraph::msgs {

using coordinator::Hlc;
using storage::v3::LabelId;

struct Value;

struct Label {
  LabelId id;
  friend bool operator==(const Label &lhs, const Label &rhs) { return lhs.id == rhs.id; }
};

// TODO(kostasrim) update this with CompoundKey, same for the rest of the file.
using PrimaryKey = std::vector<Value>;
using VertexId = std::pair<Label, PrimaryKey>;

inline bool operator==(const VertexId &lhs, const VertexId &rhs) {
  return (lhs.first == rhs.first) && (lhs.second == rhs.second);
}

using Gid = size_t;
using PropertyId = memgraph::storage::v3::PropertyId;
using EdgeTypeId = memgraph::storage::v3::EdgeTypeId;

struct EdgeType {
  uint64_t id;
  friend bool operator==(const EdgeType &lhs, const EdgeType &rhs) = default;
};

struct EdgeId {
  Gid gid;
};

struct Edge {
  VertexId src;
  VertexId dst;
  std::optional<std::vector<std::pair<PropertyId, Value>>> properties;
  EdgeId id;
  EdgeType type;
  friend bool operator==(const Edge &lhs, const Edge &rhs) {
    return (lhs.src == rhs.src) && (lhs.dst == rhs.dst) && (lhs.type == rhs.type);
  }
};

struct Vertex {
  VertexId id;
  std::vector<Label> labels;
  friend bool operator==(const Vertex &lhs, const Vertex &rhs) { return lhs.id == rhs.id; }
};

struct PathPart {
  Vertex dst;
  Gid edge;
};

struct Path {
  Vertex src;
  std::vector<PathPart> parts;
};

struct Null {};

struct Value {
  Value() : null_v{} {}

  explicit Value(const bool val) : type(Type::Bool), bool_v(val) {}
  explicit Value(const int64_t val) : type(Type::Int64), int_v(val) {}
  explicit Value(const double val) : type(Type::Double), double_v(val) {}

  explicit Value(const Vertex val) : type(Type::Vertex), vertex_v(val) {}
  explicit Value(const Edge val) : type(Type::Edge), edge_v(val) {}

  explicit Value(const std::string &val) : type(Type::String) { new (&string_v) std::string(val); }
  explicit Value(const char *val) : type(Type::String) { new (&string_v) std::string(val); }

  explicit Value(const std::vector<Value> &val) : type(Type::List) { new (&list_v) std::vector<Value>(val); }

  explicit Value(const std::map<std::string, Value> &val) : type(Type::Map) {
    new (&map_v) std::map<std::string, Value>(val);
  }

  explicit Value(std::string &&val) noexcept : type(Type::String) { new (&string_v) std::string(std::move(val)); }

  explicit Value(std::vector<Value> &&val) noexcept : type(Type::List) {
    new (&list_v) std::vector<Value>(std::move(val));
  }
  explicit Value(std::map<std::string, Value> &&val) noexcept : type(Type::Map) {
    new (&map_v) std::map<std::string, Value>(std::move(val));
  }

  ~Value() { DestroyValue(); }

  void DestroyValue() noexcept {
    switch (type) {
      case Type::Null:
      case Type::Bool:
      case Type::Int64:
      case Type::Double:
        return;

      case Type::String:
        std::destroy_at(&string_v);
        return;
      case Type::List:
        std::destroy_at(&list_v);
        return;
      case Type::Map:
        std::destroy_at(&map_v);
        return;

      case Type::Vertex:
        std::destroy_at(&vertex_v);
        return;
      case Type::Path:
        std::destroy_at(&path_v);
        return;
      case Type::Edge:
        std::destroy_at(&edge_v);
    }
  }

  Value(const Value &other) : type(other.type) {
    switch (other.type) {
      case Type::Null:
        return;
      case Type::Bool:
        this->bool_v = other.bool_v;
        return;
      case Type::Int64:
        this->int_v = other.int_v;
        return;
      case Type::Double:
        this->double_v = other.double_v;
        return;
      case Type::String:
        new (&string_v) std::string(other.string_v);
        return;
      case Type::List:
        new (&list_v) std::vector<Value>(other.list_v);
        return;
      case Type::Map:
        new (&map_v) std::map<std::string, Value>(other.map_v);
        return;
      case Type::Vertex:
        new (&vertex_v) Vertex(other.vertex_v);
        return;
      case Type::Edge:
        new (&edge_v) Edge(other.edge_v);
        return;
      case Type::Path:
        new (&path_v) Path(other.path_v);
        return;
    }
  }

  Value(Value &&other) noexcept : type(other.type) {
    switch (other.type) {
      case Type::Null:
        break;
      case Type::Bool:
        this->bool_v = other.bool_v;
        break;
      case Type::Int64:
        this->int_v = other.int_v;
        break;
      case Type::Double:
        this->double_v = other.double_v;
        break;
      case Type::String:
        new (&string_v) std::string(std::move(other.string_v));
        break;
      case Type::List:
        new (&list_v) std::vector<Value>(std::move(other.list_v));
        break;
      case Type::Map:
        new (&map_v) std::map<std::string, Value>(std::move(other.map_v));
        break;
      case Type::Vertex:
        new (&vertex_v) Vertex(std::move(other.vertex_v));
        break;
      case Type::Edge:
        new (&edge_v) Edge(std::move(other.edge_v));
        break;
      case Type::Path:
        new (&path_v) Path(std::move(other.path_v));
        break;
    }

    other.DestroyValue();
    other.type = Type::Null;
  }

  Value &operator=(const Value &other) {
    if (this == &other) return *this;

    DestroyValue();
    type = other.type;

    switch (other.type) {
      case Type::Null:
        break;
      case Type::Bool:
        this->bool_v = other.bool_v;
        break;
      case Type::Int64:
        this->int_v = other.int_v;
        break;
      case Type::Double:
        this->double_v = other.double_v;
        break;
      case Type::String:
        new (&string_v) std::string(other.string_v);
        break;
      case Type::List:
        new (&list_v) std::vector<Value>(other.list_v);
        break;
      case Type::Map:
        new (&map_v) std::map<std::string, Value>(other.map_v);
        break;
      case Type::Vertex:
        new (&vertex_v) Vertex(other.vertex_v);
        break;
      case Type::Edge:
        new (&edge_v) Edge(other.edge_v);
        break;
      case Type::Path:
        new (&path_v) Path(other.path_v);
        break;
    }

    return *this;
  }

  Value &operator=(Value &&other) noexcept {
    if (this == &other) return *this;

    DestroyValue();
    type = other.type;

    switch (other.type) {
      case Type::Null:
        break;
      case Type::Bool:
        this->bool_v = other.bool_v;
        break;
      case Type::Int64:
        this->int_v = other.int_v;
        break;
      case Type::Double:
        this->double_v = other.double_v;
        break;
      case Type::String:
        new (&string_v) std::string(std::move(other.string_v));
        break;
      case Type::List:
        new (&list_v) std::vector<Value>(std::move(other.list_v));
        break;
      case Type::Map:
        new (&map_v) std::map<std::string, Value>(std::move(other.map_v));
        break;
      case Type::Vertex:
        new (&vertex_v) Vertex(std::move(other.vertex_v));
        break;
      case Type::Edge:
        new (&edge_v) Edge(std::move(other.edge_v));
        break;
      case Type::Path:
        new (&path_v) Path(std::move(other.path_v));
        break;
    }

    other.DestroyValue();
    other.type = Type::Null;

    return *this;
  }
  enum class Type : uint8_t { Null, Bool, Int64, Double, String, List, Map, Vertex, Edge, Path };
  Type type{Type::Null};
  union {
    Null null_v;
    bool bool_v;
    int64_t int_v;
    double double_v;
    std::string string_v;
    std::vector<Value> list_v;
    std::map<std::string, Value> map_v;
    Vertex vertex_v;
    Edge edge_v;
    Path path_v;
  };

  friend bool operator==(const Value &lhs, const Value &rhs) {
    if (lhs.type != rhs.type) {
      return false;
    }
    switch (lhs.type) {
      case Value::Type::Null:
        return true;
      case Value::Type::Bool:
        return lhs.bool_v == rhs.bool_v;
      case Value::Type::Int64:
        return lhs.int_v == rhs.int_v;
      case Value::Type::Double:
        return lhs.double_v == rhs.double_v;
      case Value::Type::String:
        return lhs.string_v == rhs.string_v;
      case Value::Type::List:
        return lhs.list_v == rhs.list_v;
      case Value::Type::Map:
        return lhs.map_v == rhs.map_v;
      case Value::Type::Vertex:
        return lhs.vertex_v == rhs.vertex_v;
      case Value::Type::Edge:
        return lhs.edge_v == rhs.edge_v;
      case Value::Type::Path:
        return true;
    }
  }
};

struct ValuesMap {
  std::unordered_map<PropertyId, Value> values_map;
};

struct MappedValues {
  std::vector<ValuesMap> values_map;
};

struct ListedValues {
  std::vector<std::vector<Value>> properties;
};

using Values = std::variant<ListedValues, MappedValues>;

struct Expression {
  std::string expression;
};

struct Filter {
  std::string filter_expression;
};

enum class OrderingDirection { ASCENDING = 1, DESCENDING = 2 };

struct OrderBy {
  Expression expression;
  OrderingDirection direction;
};

enum class StorageView { OLD = 0, NEW = 1 };

struct ScanVerticesRequest {
  Hlc transaction_id;
  VertexId start_id;
  std::optional<std::vector<PropertyId>> props_to_return;
  std::optional<size_t> batch_limit;
  StorageView storage_view{StorageView::NEW};

  std::optional<Label> label;
  std::optional<std::pair<PropertyId, std::string>> property_expression_pair;
  std::optional<std::vector<std::string>> filter_expressions;
};

struct ScanResultRow {
  Vertex vertex;
  // empty() is no properties returned
  std::vector<std::pair<PropertyId, Value>> props;
};

struct ScanVerticesResponse {
  bool success;
  std::optional<VertexId> next_start_id;
  std::vector<ScanResultRow> results;
};

using VertexOrEdgeIds = std::variant<VertexId, EdgeId>;

struct GetPropertiesRequest {
  Hlc transaction_id;
  VertexOrEdgeIds vertex_or_edge_ids;
  std::vector<PropertyId> property_ids;
  std::vector<Expression> expressions;
  bool only_unique = false;
  std::optional<std::vector<OrderBy>> order_by;
  std::optional<size_t> limit;
  std::optional<Filter> filter;
};

struct GetPropertiesResponse {
  bool success;
  Values values;
};

enum class EdgeDirection : uint8_t { OUT = 1, IN = 2, BOTH = 3 };

struct VertexEdgeId {
  VertexId vertex_id;
  std::optional<EdgeId> next_id;
};

struct ExpandOneRequest {
  Hlc transaction_id;
  std::vector<VertexId> src_vertices;
  std::vector<EdgeType> edge_types;
  EdgeDirection direction;
  bool only_unique_neighbor_rows = false;
  //  The empty optional means return all of the properties, while an empty
  //  list means do not return any properties
  //  TODO(antaljanosbenjamin): All of the special values should be communicated through a single vertex object
  //                            after schema is implemented
  //  Special values are accepted:
  //  * __mg__labels
  std::optional<std::vector<PropertyId>> src_vertex_properties;
  //  TODO(antaljanosbenjamin): All of the special values should be communicated through a single vertex object
  //                            after schema is implemented
  //  Special values are accepted:
  //  * __mg__dst_id (Vertex, but without labels)
  //  * __mg__type (binary)
  std::optional<std::vector<PropertyId>> edge_properties;
  //  QUESTION(antaljanosbenjamin): Maybe also add possibility to expressions evaluated on the source vertex?
  //  List of expressions evaluated on edges
  std::vector<Expression> expressions;
  std::optional<std::vector<OrderBy>> order_by;
  std::optional<size_t> limit;
  std::optional<Filter> filter;
};

struct ExpandOneResultRow {
  // NOTE: This struct could be a single Values with columns something like this:
  // src_vertex(Vertex), vertex_prop1(Value), vertex_prop2(Value), edges(list<Value>)
  // where edges might be a list of:
  // 1. list<Value> if only a defined list of edge properties are returned
  // 2. map<binary, Value> if all of the edge properties are returned
  // The drawback of this is currently the key of the map is always interpreted as a string in Value, not as an
  // integer, which should be in case of mapped properties.
  Vertex src_vertex;
  std::optional<std::map<PropertyId, Value>> src_vertex_properties;

  // NOTE: If the desired edges are specified in the request,
  // edges_with_specific_properties will have a value and it will
  // return the properties as a vector of property values. The order
  // of the values returned should be the same as the PropertyIds
  // were defined in the request.
  std::optional<std::vector<std::tuple<VertexId, Gid, std::map<PropertyId, Value>>>> edges_with_all_properties;
  std::optional<std::vector<std::tuple<VertexId, Gid, std::vector<Value>>>> edges_with_specific_properties;
};

struct ExpandOneResponse {
  bool success;
  std::vector<ExpandOneResultRow> result;
};

struct UpdateVertexProp {
  PrimaryKey primary_key;
  std::vector<std::pair<PropertyId, Value>> property_updates;
};

struct UpdateEdgeProp {
  EdgeId edge_id;
  VertexId src;
  VertexId dst;
  std::vector<std::pair<PropertyId, Value>> property_updates;
};

/*
 * Vertices
 */
struct NewVertex {
  std::vector<Label> label_ids;
  PrimaryKey primary_key;
  std::vector<std::pair<PropertyId, Value>> properties;
};

struct NewVertexLabel {
  std::string label;
  PrimaryKey primary_key;
  std::vector<std::pair<PropertyId, Value>> properties;
};

struct CreateVerticesRequest {
  Hlc transaction_id;
  std::vector<NewVertex> new_vertices;
};

struct CreateVerticesResponse {
  bool success;
};

struct DeleteVerticesRequest {
  enum class DeletionType { DELETE, DETACH_DELETE };
  Hlc transaction_id;
  std::vector<std::vector<Value>> primary_keys;
  DeletionType deletion_type;
};

struct DeleteVerticesResponse {
  bool success;
};

struct UpdateVerticesRequest {
  Hlc transaction_id;
  std::vector<UpdateVertexProp> new_properties;
};

struct UpdateVerticesResponse {
  bool success;
};

/*
 * Edges
 */
struct CreateEdgesRequest {
  Hlc transaction_id;
  std::vector<Edge> edges;
};

struct CreateEdgesResponse {
  bool success;
};

struct DeleteEdgesRequest {
  Hlc transaction_id;
  std::vector<Edge> edges;
};

struct DeleteEdgesResponse {
  bool success;
};

struct UpdateEdgesRequest {
  Hlc transaction_id;
  std::vector<UpdateEdgeProp> new_properties;
};

struct UpdateEdgesResponse {
  bool success;
};

struct CommitRequest {
  Hlc transaction_id;
  Hlc commit_timestamp;
};

struct CommitResponse {
  bool success;
};

using ReadRequests = std::variant<ExpandOneRequest, GetPropertiesRequest, ScanVerticesRequest>;
using ReadResponses = std::variant<ExpandOneResponse, GetPropertiesResponse, ScanVerticesResponse>;

using WriteRequests = std::variant<CreateVerticesRequest, DeleteVerticesRequest, UpdateVerticesRequest,
                                   CreateEdgesRequest, DeleteEdgesRequest, UpdateEdgesRequest, CommitRequest>;
using WriteResponses = std::variant<CreateVerticesResponse, DeleteVerticesResponse, UpdateVerticesResponse,
                                    CreateEdgesResponse, DeleteEdgesResponse, UpdateEdgesResponse, CommitResponse>;

}  // namespace memgraph::msgs