// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/sessions/core/session_command.h"

#include <memory>
#include <string>
#include <string_view>
#include <vector>

#include "base/compiler_specific.h"
#include "base/memory/scoped_refptr.h"
#include "base/numerics/byte_conversions.h"
#include "base/pickle.h"
#include "components/os_crypt/async/browser/test_utils.h"
#include "components/os_crypt/async/common/encryptor.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace sessions {
namespace {

using id_type = SessionCommand::id_type;
using size_type = SessionCommand::size_type;

TEST(SessionCommandTest, BasicConstructor) {
  const id_type id = 42;
  const size_type size = 10;
  SessionCommand command(id, size);

  EXPECT_EQ(id, command.id());
  EXPECT_EQ(size, command.contents().size());

  // Check that contents are initialized to 0.
  for (uint8_t byte : command.contents()) {
    EXPECT_EQ(0u, byte);
  }
}

TEST(SessionCommandTest, PickleConstructor) {
  const id_type id = 42;
  base::Pickle pickle;
  pickle.WriteString("hello");
  pickle.WriteInt(123);

  SessionCommand command(id, pickle);

  EXPECT_EQ(id, command.id());
  EXPECT_EQ(pickle.size(), command.contents().size());
  // Verify contents match pickle data.
  EXPECT_EQ(pickle.AsBytes(), command.contents());
}

TEST(SessionCommandTest, GetContents) {
  const id_type id = 42;
  const size_type size = 5;
  SessionCommand command(id, size);
  command.contents().copy_from(base::span<const uint8_t>({1, 2, 3, 4, 5}));

  std::vector<uint8_t> dest2(size);
  EXPECT_TRUE(command.GetContents(base::as_writable_byte_span(dest2)));

  EXPECT_EQ(1u, dest2[0]);
  EXPECT_EQ(2u, dest2[1]);
  EXPECT_EQ(3u, dest2[2]);
  EXPECT_EQ(4u, dest2[3]);
  EXPECT_EQ(5u, dest2[4]);
}

class SessionCommandParamTest : public testing::TestWithParam<bool> {
 protected:
  bool encrypted() const { return GetParam(); }
  scoped_refptr<os_crypt_async::Encryptor> encryptor() {
    if (encrypted()) {
      if (!encryptor_) {
        encryptor_ = os_crypt_async::GetTestEncryptorForTesting();
      }
      return encryptor_;
    } else {
      return nullptr;
    }
  }

 private:
  scoped_refptr<os_crypt_async::TestEncryptor> encryptor_;
};

TEST_P(SessionCommandParamTest, GetSerializedSize) {
  const id_type id = 42;
  const std::string contents = "session_data";
  SessionCommand command(id, contents.size());
  command.contents().copy_from(base::as_byte_span(contents));
  std::vector<uint8_t> serialized = command.Serialize(encryptor().get());

  // Not enough data for size (we need sizeof(uint32_t) for encrypted)
  const size_t size_field_size =
      encrypted() ? sizeof(uint32_t) : sizeof(uint16_t);
  EXPECT_EQ(std::nullopt, SessionCommand::GetSerializedSize(
                              base::span(serialized).first(size_field_size - 1),
                              encryptor().get()));

  // Enough data for size
  std::optional<size_t> size =
      SessionCommand::GetSerializedSize(serialized, encrypted());
  ASSERT_TRUE(size.has_value());
  EXPECT_EQ(serialized.size(), *size);

  // Exact size even if more data is available
  std::vector<uint8_t> padded_serialized = serialized;
  padded_serialized.push_back(0u);
  size = SessionCommand::GetSerializedSize(padded_serialized, encrypted());
  ASSERT_TRUE(size.has_value());
  EXPECT_EQ(serialized.size(), *size);
}

TEST(SessionCommandTest, GetContentsWithWrongSizeFeturnsFalse) {
  const id_type id = 42;
  const size_type size = 5;
  SessionCommand command(id, size);

  // Fill with known data.
  command.contents().copy_from(base::span<const uint8_t>({1, 2, 3, 4, 5}));

  // Wrong size should return false.
  std::vector<uint8_t> dest(size + 1);
  EXPECT_FALSE(command.GetContents(base::as_writable_byte_span(dest)));
}

TEST(SessionCommandTest, ContentsAsPickle) {
  base::Pickle pickle;
  pickle.WriteString("test_string");
  pickle.WriteInt(456);
  SessionCommand command(1, pickle);

  base::PickleIterator iter = command.ContentsAsPickle();
  std::string read_string;
  EXPECT_TRUE(iter.ReadString(&read_string));
  EXPECT_EQ("test_string", read_string);

  int read_int = 0;
  EXPECT_TRUE(iter.ReadInt(&read_int));
  EXPECT_EQ(456, read_int);
}

TEST(SessionCommandTest, Clone) {
  const id_type id = 42;
  const size_type size = 5;
  SessionCommand command(id, size);
  command.contents().copy_from(base::span<const uint8_t>({1, 2, 3, 4, 5}));

  std::unique_ptr<SessionCommand> clone = command.Clone();
  ASSERT_TRUE(clone);
  EXPECT_EQ(command.id(), clone->id());
  EXPECT_EQ(command.contents(), clone->contents());

  clone->contents()[0] = 99;
  EXPECT_NE(command.contents(), clone->contents());
}

TEST_P(SessionCommandParamTest, SerializeAndDeserialize) {
  const id_type id = 42;
  const std::string contents = "session_data";
  SessionCommand command(id, contents.size());
  command.contents().copy_from(base::as_byte_span(contents));

  std::vector<uint8_t> serialized = command.Serialize(encryptor().get());

  if (encrypted()) {
    // Size should be: sizeof(encrypted_size_type) + encrypted_contents_size.
    // We don't know the exact size, but it should be larger than
    // contents.size().
    EXPECT_GT(serialized.size(),
              sizeof(uint32_t) + sizeof(id_type) + contents.size());
  } else {
    // Size should be: sizeof(size_type) + sizeof(id_type) + contents_size.
    EXPECT_EQ(sizeof(size_type) + sizeof(id_type) + contents.size(),
              serialized.size());
  }

  std::unique_ptr<SessionCommand> deserialized =
      SessionCommand::Deserialize(serialized, encryptor().get());
  ASSERT_TRUE(deserialized);
  EXPECT_EQ(id, deserialized->id());
  EXPECT_EQ(contents.size(), deserialized->contents().size());
  EXPECT_EQ(base::as_byte_span(contents), deserialized->contents());
}

TEST_P(SessionCommandParamTest, SerializeAndDeserializeEmpty) {
  const id_type id = 42;
  SessionCommand command(id, 0);

  std::vector<uint8_t> serialized = command.Serialize(encryptor().get());
  std::unique_ptr<SessionCommand> deserialized =
      SessionCommand::Deserialize(serialized, encryptor().get());
  ASSERT_TRUE(deserialized);
  EXPECT_EQ(id, deserialized->id());
  EXPECT_EQ(0u, deserialized->contents().size());
}

TEST(SessionCommandTest, SerializeTruncatesLargeContents) {
  const id_type id = 42;
  const size_type exceeding_size = std::numeric_limits<size_type>::max();
  SessionCommand command(id, exceeding_size);
  EXPECT_EQ(exceeding_size, command.contents().size());

  std::vector<uint8_t> serialized = command.Serialize(nullptr);

  // contents should be truncated to kMaxContentSize
  const size_t expected_size =
      sizeof(size_type) + sizeof(id_type) + SessionCommand::kMaxContentSize;
  EXPECT_EQ(expected_size, serialized.size());

  // Deserializing it should yield kMaxContentSize bytes of content.
  std::unique_ptr<SessionCommand> deserialized =
      SessionCommand::Deserialize(serialized, nullptr);
  ASSERT_TRUE(deserialized);
  EXPECT_EQ(SessionCommand::kMaxContentSize, deserialized->contents().size());
}

std::string TestParamNameGenerator(
    const testing::TestParamInfo<bool>& param_info) {
  return param_info.param ? "Encrypted" : "Cleartext";
}

INSTANTIATE_TEST_SUITE_P(All,
                         SessionCommandParamTest,
                         ::testing::Bool(),
                         TestParamNameGenerator);

TEST(SessionCommandTest, DeserializeCleartextErrors) {
  // Too small to contain size_type.
  EXPECT_FALSE(
      SessionCommand::Deserialize(base::span<const uint8_t>({1}), nullptr));

  // Size field indicates more data than available.
  // size field = 10 (little endian), but only 2 bytes available.
  const uint8_t bad_size_data[] = {10, 0, 0};
  EXPECT_FALSE(SessionCommand::Deserialize(bad_size_data, nullptr));

  // Size field is smaller than sizeof(id_type).
  const uint8_t bad_size_data2[] = {0, 0};
  EXPECT_FALSE(SessionCommand::Deserialize(bad_size_data2, nullptr));
}

TEST(SessionCommandTest, DeserializeEncryptedErrors) {
  scoped_refptr<os_crypt_async::Encryptor> encryptor =
      os_crypt_async::GetTestEncryptorForTesting();

  // Too small to contain encrypted_size_type.
  EXPECT_FALSE(SessionCommand::Deserialize(base::span<const uint8_t>({1, 2, 3}),
                                           encryptor.get()));

  // Size field indicates more data than available.
  // size field = 10 (little endian), but only 2 bytes available.
  const uint8_t bad_size_data[] = {10, 0, 0, 0, 0, 0};
  EXPECT_FALSE(SessionCommand::Deserialize(bad_size_data, encryptor.get()));

  // Not decryptable data (valid size, but invalid ciphertext).
  const uint8_t bad_crypto_data[] = {4, 0, 0, 0, 1, 2, 3, 4};
  EXPECT_FALSE(SessionCommand::Deserialize(bad_crypto_data, encryptor.get()));
}

}  // namespace
}  // namespace sessions
