// Copyright 2014 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/sync/engine/loopback_server/loopback_server_entity.h"

#include <limits>
#include <string_view>
#include <vector>

#include "base/memory/ref_counted.h"
#include "base/notreached.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "components/sync/engine/loopback_server/persistent_bookmark_entity.h"
#include "components/sync/engine/loopback_server/persistent_permanent_entity.h"
#include "components/sync/engine/loopback_server/persistent_tombstone_entity.h"
#include "components/sync/engine/loopback_server/persistent_unique_client_entity.h"
#include "components/sync/protocol/loopback_server.pb.h"
#include "components/sync/protocol/sync_entity.pb.h"
#include "net/base/net_errors.h"
#include "net/http/http_status_code.h"

using std::string;
using std::vector;

using syncer::DataType;

namespace {
// The separator used when formatting IDs.
//
// We chose the underscore character because it doesn't conflict with the
// special characters used by base/base64.h's encoding, which is also used in
// the construction of some IDs.
constexpr char kIdSeparator[] = "_";
}  // namespace

namespace syncer {

LoopbackServerEntity::~LoopbackServerEntity() = default;

// static
std::unique_ptr<LoopbackServerEntity>
LoopbackServerEntity::CreateEntityFromProto(
    const sync_pb::LoopbackServerEntity& entity) {
  switch (entity.type()) {
    case sync_pb::LoopbackServerEntity_Type_TOMBSTONE:
      return PersistentTombstoneEntity::CreateFromEntity(entity.entity());
    case sync_pb::LoopbackServerEntity_Type_PERMANENT: {
      DataType data_type =
          syncer::GetDataTypeFromSpecifics(entity.entity().specifics());
      if (!IsRealDataType(data_type)) {
        return nullptr;
      }
      return std::make_unique<PersistentPermanentEntity>(
          entity.entity().id_string(), entity.entity().version(), data_type,
          entity.entity().name(), entity.entity().parent_id_string(),
          entity.entity().server_defined_unique_tag(),
          entity.entity().specifics());
    }
    case sync_pb::LoopbackServerEntity_Type_BOOKMARK:
      return PersistentBookmarkEntity::CreateFromEntity(entity.entity());
    case sync_pb::LoopbackServerEntity_Type_UNIQUE:
      return PersistentUniqueClientEntity::CreateFromEntity(
          entity.entity(), /*migration_version=*/0);
    case sync_pb::LoopbackServerEntity_Type_UNKNOWN:
      NOTREACHED() << "Unknown type encountered";
  }
  return nullptr;
}

const std::string& LoopbackServerEntity::GetId() const {
  return id_;
}

DataType LoopbackServerEntity::GetDataType() const {
  return data_type_;
}

int64_t LoopbackServerEntity::GetVersion() const {
  return version_;
}

void LoopbackServerEntity::SetVersion(int64_t version) {
  version_ = version;
}

const std::string& LoopbackServerEntity::GetName() const {
  return name_;
}

void LoopbackServerEntity::SetName(const std::string& name) {
  name_ = name;
}

void LoopbackServerEntity::SetSpecifics(
    const sync_pb::EntitySpecifics& updated_specifics) {
  specifics_ = updated_specifics;
}

sync_pb::EntitySpecifics LoopbackServerEntity::GetSpecifics() const {
  return specifics_;
}

bool LoopbackServerEntity::IsDeleted() const {
  return false;
}

bool LoopbackServerEntity::IsFolder() const {
  return false;
}

bool LoopbackServerEntity::IsPermanent() const {
  return false;
}

sync_pb::LoopbackServerEntity_Type
LoopbackServerEntity::GetLoopbackServerEntityType() const {
  NOTREACHED();
}

// static
string LoopbackServerEntity::CreateId(const DataType& data_type,
                                      const string& inner_id,
                                      int migration_version) {
  const int field_number = GetSpecificsFieldNumberFromDataType(data_type);
  if (migration_version > 0) {
    return base::StringPrintf("%d%s@%d%s%s", field_number, kIdSeparator,
                              migration_version, kIdSeparator, inner_id);
  }
  return base::StringPrintf("%d%s%s", field_number, kIdSeparator, inner_id);
}

// static
std::string LoopbackServerEntity::GetTopLevelId(const DataType& data_type,
                                                int migration_version) {
  return LoopbackServerEntity::CreateId(
      data_type, syncer::DataTypeToProtocolRootTag(data_type),
      migration_version);
}

// static
DataType LoopbackServerEntity::GetDataTypeFromId(const string& id) {
  const vector<std::string_view> tokens = base::SplitStringPiece(
      id, kIdSeparator, base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);

  if (tokens.size() < 2) {
    return syncer::UNSPECIFIED;
  }

  int field_number = 0;
  if (base::StringToInt(tokens[0], &field_number)) {
    return syncer::GetDataTypeFromSpecificsFieldNumber(field_number);
  }

  return syncer::UNSPECIFIED;
}

// static
std::string LoopbackServerEntity::GetInnerIdFromId(const std::string& id) {
  const vector<std::string_view> tokens = base::SplitStringPiece(
      id, kIdSeparator, base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);

  if (tokens.size() < 2) {
    return std::string();
  }

  if (tokens.size() == 2) {
    return std::string(tokens[1]);
  }

  if (base::StartsWith(tokens[1], "@", base::CompareCase::SENSITIVE)) {
    // It is a versioned ID. Tag starts at tokens[2].
    const std::vector<std::string_view> sub_tokens(tokens.begin() + 2,
                                                   tokens.end());
    return base::JoinString(sub_tokens, kIdSeparator);
  }
  // It is an unversioned ID with underscores within the inner ID itself. Tag
  // starts at tokens[1].
  const std::vector<std::string_view> sub_tokens(tokens.begin() + 1,
                                                 tokens.end());
  return base::JoinString(sub_tokens, kIdSeparator);
}

// static
int LoopbackServerEntity::GetMigrationVersionFromId(const std::string& id) {
  const vector<std::string_view> tokens = base::SplitStringPiece(
      id, kIdSeparator, base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);

  if (tokens.size() < 3) {
    return 0;
  }

  if (base::StartsWith(tokens[1], "@", base::CompareCase::SENSITIVE)) {
    int version = 0;
    if (base::StringToInt(tokens[1].substr(1), &version)) {
      return version;
    }
  }
  return 0;
}

LoopbackServerEntity::LoopbackServerEntity(const string& id,
                                           const DataType& data_type,
                                           int64_t version,
                                           const string& name)
    : id_(id), data_type_(data_type), version_(version), name_(name) {}

void LoopbackServerEntity::SerializeBaseProtoFields(
    sync_pb::SyncEntity* sync_entity) const {
  sync_pb::EntitySpecifics* specifics = sync_entity->mutable_specifics();
  specifics->CopyFrom(specifics_);

  // LoopbackServerEntity fields
  sync_entity->set_id_string(id_);
  sync_entity->set_version(version_);
  sync_entity->set_name(name_);

  // Data via accessors
  sync_entity->set_deleted(IsDeleted());
  sync_entity->set_folder(IsFolder());

  if (RequiresParentId()) {
    sync_entity->set_parent_id_string(GetParentId());
  }
}

void LoopbackServerEntity::SerializeAsLoopbackServerEntity(
    sync_pb::LoopbackServerEntity* entity) const {
  entity->set_type(GetLoopbackServerEntityType());
  entity->set_data_type(static_cast<int64_t>(GetDataType()));
  SerializeAsProto(entity->mutable_entity());
}

void LoopbackServerEntity::MigrateToNewVersionForTesting(int new_version) {
  id_ = CreateId(GetDataType(), GetInnerIdFromId(id_), new_version);
  // Start with entity version 1 (not to be confused with the migration version,
  // which is represented by `new_version`). This mimics MIGRATION_DONE causing
  // the server versions to decrease (as opposed to the usual
  // monotonically-increasing behavior), as an attempt to catch more bugs
  // related to making the incorrect assumption that entity versions are
  // monotonically increasing across MIGRATION_DONE.
  SetVersion(1);
}

}  // namespace syncer
