//  Copyright (c) Meta Platforms, Inc. and affiliates.
//  This source code is licensed under both the GPLv2 (found in the
//  COPYING file in the root directory) and Apache 2.0 License
//  (found in the LICENSE.Apache file in the root directory).

#include "db/wide/wide_column_serialization.h"

#include "db/wide/wide_columns_helper.h"
#include "test_util/testharness.h"
#include "util/coding.h"

namespace ROCKSDB_NAMESPACE {

TEST(WideColumnSerializationTest, Construct) {
  constexpr char foo[] = "foo";
  constexpr char bar[] = "bar";

  const std::string foo_str(foo);
  const std::string bar_str(bar);

  const Slice foo_slice(foo_str);
  const Slice bar_slice(bar_str);

  {
    WideColumn column(foo, bar);
    ASSERT_EQ(column.name(), foo);
    ASSERT_EQ(column.value(), bar);
  }

  {
    WideColumn column(foo_str, bar);
    ASSERT_EQ(column.name(), foo_str);
    ASSERT_EQ(column.value(), bar);
  }

  {
    WideColumn column(foo_slice, bar);
    ASSERT_EQ(column.name(), foo_slice);
    ASSERT_EQ(column.value(), bar);
  }

  {
    WideColumn column(foo, bar_str);
    ASSERT_EQ(column.name(), foo);
    ASSERT_EQ(column.value(), bar_str);
  }

  {
    WideColumn column(foo_str, bar_str);
    ASSERT_EQ(column.name(), foo_str);
    ASSERT_EQ(column.value(), bar_str);
  }

  {
    WideColumn column(foo_slice, bar_str);
    ASSERT_EQ(column.name(), foo_slice);
    ASSERT_EQ(column.value(), bar_str);
  }

  {
    WideColumn column(foo, bar_slice);
    ASSERT_EQ(column.name(), foo);
    ASSERT_EQ(column.value(), bar_slice);
  }

  {
    WideColumn column(foo_str, bar_slice);
    ASSERT_EQ(column.name(), foo_str);
    ASSERT_EQ(column.value(), bar_slice);
  }

  {
    WideColumn column(foo_slice, bar_slice);
    ASSERT_EQ(column.name(), foo_slice);
    ASSERT_EQ(column.value(), bar_slice);
  }

  {
    constexpr char foo_name[] = "foo_name";
    constexpr char bar_value[] = "bar_value";

    WideColumn column(std::piecewise_construct,
                      std::forward_as_tuple(foo_name, sizeof(foo) - 1),
                      std::forward_as_tuple(bar_value, sizeof(bar) - 1));
    ASSERT_EQ(column.name(), foo);
    ASSERT_EQ(column.value(), bar);
  }
}

TEST(WideColumnSerializationTest, SerializeDeserialize) {
  WideColumns columns{{"foo", "bar"}, {"hello", "world"}};
  std::string output;

  ASSERT_OK(WideColumnSerialization::Serialize(columns, output));

  Slice input(output);
  WideColumns deserialized_columns;

  ASSERT_OK(WideColumnSerialization::Deserialize(input, deserialized_columns));
  ASSERT_EQ(columns, deserialized_columns);

  {
    const auto it = WideColumnsHelper::Find(deserialized_columns.cbegin(),
                                            deserialized_columns.cend(), "foo");
    ASSERT_NE(it, deserialized_columns.cend());
    ASSERT_EQ(*it, deserialized_columns.front());
  }

  {
    const auto it = WideColumnsHelper::Find(
        deserialized_columns.cbegin(), deserialized_columns.cend(), "hello");
    ASSERT_NE(it, deserialized_columns.cend());
    ASSERT_EQ(*it, deserialized_columns.back());
  }

  {
    const auto it = WideColumnsHelper::Find(
        deserialized_columns.cbegin(), deserialized_columns.cend(), "fubar");
    ASSERT_EQ(it, deserialized_columns.cend());
  }

  {
    const auto it = WideColumnsHelper::Find(
        deserialized_columns.cbegin(), deserialized_columns.cend(), "snafu");
    ASSERT_EQ(it, deserialized_columns.cend());
  }
}

TEST(WideColumnSerializationTest, SerializeDuplicateError) {
  WideColumns columns{{"foo", "bar"}, {"foo", "baz"}};
  std::string output;

  ASSERT_TRUE(
      WideColumnSerialization::Serialize(columns, output).IsCorruption());
}

TEST(WideColumnSerializationTest, SerializeOutOfOrderError) {
  WideColumns columns{{"hello", "world"}, {"foo", "bar"}};
  std::string output;

  ASSERT_TRUE(
      WideColumnSerialization::Serialize(columns, output).IsCorruption());
}

TEST(WideColumnSerializationTest, DeserializeVersionError) {
  // Can't decode version

  std::string buf;

  Slice input(buf);
  WideColumns columns;

  const Status s = WideColumnSerialization::Deserialize(input, columns);
  ASSERT_TRUE(s.IsCorruption());
  ASSERT_TRUE(std::strstr(s.getState(), "version"));
}

TEST(WideColumnSerializationTest, DeserializeUnsupportedVersion) {
  // Unsupported version
  constexpr uint32_t future_version = 1000;

  std::string buf;
  PutVarint32(&buf, future_version);

  Slice input(buf);
  WideColumns columns;

  const Status s = WideColumnSerialization::Deserialize(input, columns);
  ASSERT_TRUE(s.IsNotSupported());
  ASSERT_TRUE(std::strstr(s.getState(), "version"));
}

TEST(WideColumnSerializationTest, DeserializeNumberOfColumnsError) {
  // Can't decode number of columns

  std::string buf;
  PutVarint32(&buf, WideColumnSerialization::kCurrentVersion);

  Slice input(buf);
  WideColumns columns;

  const Status s = WideColumnSerialization::Deserialize(input, columns);
  ASSERT_TRUE(s.IsCorruption());
  ASSERT_TRUE(std::strstr(s.getState(), "number"));
}

TEST(WideColumnSerializationTest, DeserializeColumnsError) {
  std::string buf;

  PutVarint32(&buf, WideColumnSerialization::kCurrentVersion);

  constexpr uint32_t num_columns = 2;
  PutVarint32(&buf, num_columns);

  // Can't decode the first column name
  {
    Slice input(buf);
    WideColumns columns;

    const Status s = WideColumnSerialization::Deserialize(input, columns);
    ASSERT_TRUE(s.IsCorruption());
    ASSERT_TRUE(std::strstr(s.getState(), "name"));
  }

  constexpr char first_column_name[] = "foo";
  PutLengthPrefixedSlice(&buf, first_column_name);

  // Can't decode the size of the first column value
  {
    Slice input(buf);
    WideColumns columns;

    const Status s = WideColumnSerialization::Deserialize(input, columns);
    ASSERT_TRUE(s.IsCorruption());
    ASSERT_TRUE(std::strstr(s.getState(), "value size"));
  }

  constexpr uint32_t first_value_size = 16;
  PutVarint32(&buf, first_value_size);

  // Can't decode the second column name
  {
    Slice input(buf);
    WideColumns columns;

    const Status s = WideColumnSerialization::Deserialize(input, columns);
    ASSERT_TRUE(s.IsCorruption());
    ASSERT_TRUE(std::strstr(s.getState(), "name"));
  }

  constexpr char second_column_name[] = "hello";
  PutLengthPrefixedSlice(&buf, second_column_name);

  // Can't decode the size of the second column value
  {
    Slice input(buf);
    WideColumns columns;

    const Status s = WideColumnSerialization::Deserialize(input, columns);
    ASSERT_TRUE(s.IsCorruption());
    ASSERT_TRUE(std::strstr(s.getState(), "value size"));
  }

  constexpr uint32_t second_value_size = 64;
  PutVarint32(&buf, second_value_size);

  // Can't decode the payload of the first column
  {
    Slice input(buf);
    WideColumns columns;

    const Status s = WideColumnSerialization::Deserialize(input, columns);
    ASSERT_TRUE(s.IsCorruption());
    ASSERT_TRUE(std::strstr(s.getState(), "payload"));
  }

  buf.append(first_value_size, '0');

  // Can't decode the payload of the second column
  {
    Slice input(buf);
    WideColumns columns;

    const Status s = WideColumnSerialization::Deserialize(input, columns);
    ASSERT_TRUE(s.IsCorruption());
    ASSERT_TRUE(std::strstr(s.getState(), "payload"));
  }

  buf.append(second_value_size, 'x');

  // Success
  {
    Slice input(buf);
    WideColumns columns;

    ASSERT_OK(WideColumnSerialization::Deserialize(input, columns));
  }
}

TEST(WideColumnSerializationTest, DeserializeColumnsOutOfOrder) {
  std::string buf;

  PutVarint32(&buf, WideColumnSerialization::kCurrentVersion);

  constexpr uint32_t num_columns = 2;
  PutVarint32(&buf, num_columns);

  constexpr char first_column_name[] = "b";
  PutLengthPrefixedSlice(&buf, first_column_name);

  constexpr uint32_t first_value_size = 16;
  PutVarint32(&buf, first_value_size);

  constexpr char second_column_name[] = "a";
  PutLengthPrefixedSlice(&buf, second_column_name);

  Slice input(buf);
  WideColumns columns;

  const Status s = WideColumnSerialization::Deserialize(input, columns);
  ASSERT_TRUE(s.IsCorruption());
  ASSERT_TRUE(std::strstr(s.getState(), "order"));
}

}  // namespace ROCKSDB_NAMESPACE

int main(int argc, char** argv) {
  ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
  ::testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}
