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

#ifndef COMPONENTS_SYNC_ENGINE_LOOPBACK_SERVER_LOOPBACK_SERVER_H_
#define COMPONENTS_SYNC_ENGINE_LOOPBACK_SERVER_LOOPBACK_SERVER_H_

#include <stdint.h>

#include <map>
#include <memory>
#include <optional>
#include <string>
#include <vector>

#include "base/files/file_path.h"
#include "base/files/important_file_writer.h"
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/sequence_checker.h"
#include "base/values.h"
#include "components/sync/base/data_type.h"
#include "components/sync/engine/loopback_server/loopback_server_entity.h"
#include "components/sync/protocol/sync.pb.h"
#include "net/http/http_status_code.h"

namespace sync_pb {
class DeletionOrigin;
class LoopbackServerProto;
class EntitySpecifics;
class SyncEntity;
}  // namespace sync_pb

namespace fake_server {
class FakeServer;
}

namespace syncer {

// A loopback version of the Sync server used for local profile serialization.
class LoopbackServer : public base::ImportantFileWriter::DataSerializer {
 public:
  class ObserverForTests {
   public:
    virtual ~ObserverForTests() = default;

    // Called after the server has processed a successful commit. The types
    // updated as part of the commit are passed in `committed_data_types`.
    virtual void OnCommit(syncer::DataTypeSet committed_data_types) = 0;

    // Called when a committed tombstone includes a deletion origin.
    virtual void OnCommittedDeletionOrigin(
        syncer::DataType type,
        const sync_pb::DeletionOrigin& deletion_origin) = 0;
  };

  explicit LoopbackServer(const base::FilePath& persistent_file);
  ~LoopbackServer() override;

  // Handles a /command POST (with the given `message`) to the server.
  // `response` must not be null.
  net::HttpStatusCode HandleCommand(
      const sync_pb::ClientToServerMessage& message,
      sync_pb::ClientToServerResponse* response);

  // Enables strong consistency model (i.e. server detects conflicts).
  void EnableStrongConsistencyWithConflictDetectionModel();

  enum class UpdateMode {
    kIncremental,
    kFull,
  };

  // Configures the update mode for `data_type`. When set to
  // `UpdateMode::kFull`, the server will respond with a full update and a GC
  // directive whenever there are new or updated entities for `data_type`.
  void SetUpdateMode(DataType data_type, UpdateMode update_mode);

  // Sets a maximum batch size for GetUpdates requests.
  void SetMaxGetUpdatesBatchSize(int batch_size) {
    max_get_updates_batch_size_ = batch_size;
  }

  void SetBagOfChipsForTesting(const sync_pb::ChipBag& bag_of_chips) {
    bag_of_chips_ = bag_of_chips;
  }

  void TriggerMigrationForTesting(DataTypeSet data_types);

  // Enables using GarbageCollectionDirective (clear_metadata) instead of
  // MIGRATION_DONE error to trigger migration/re-sync on the client.
  void EnableGcDirectiveForMigration();

  int GetMigrationVersion(DataType type) const;

  static int GetMigrationVersionFromProgressTokenForTesting(
      const std::string& token);

  const std::vector<std::vector<uint8_t>>& GetKeystoreKeysForTesting() const {
    return keystore_keys_;
  }

  void AddNewKeystoreKeyForTesting();

  void SetThrottledTypesForTesting(DataTypeSet data_types) {
    throttled_types_ = data_types;
  }

 private:
  // Allow the FakeServer decorator to inspect the internals of this class.
  friend class fake_server::FakeServer;

  using EntityMap =
      std::map<std::string, std::unique_ptr<LoopbackServerEntity>>;

  using ResponseTypeProvider =
      base::RepeatingCallback<sync_pb::CommitResponse::ResponseType(
          const LoopbackServerEntity& entity)>;

  void FlushToDisk();

  // ImportantFileWriter::DataSerializer:
  std::optional<std::string> SerializeData() override;

  // Gets LoopbackServer ready for syncing.
  void Init();

  // Processes a GetUpdates call.
  bool HandleGetUpdatesRequest(const sync_pb::GetUpdatesMessage& get_updates,
                               const std::string& store_birthday,
                               const std::string& invalidator_client_id,
                               sync_pb::GetUpdatesResponse* response,
                               std::vector<DataType>* datatypes_to_migrate);

  void PopulateGcDirectiveMigrationResponse(
      const sync_pb::GetUpdatesMessage& get_updates,
      const std::vector<DataType>& datatypes_to_migrate,
      sync_pb::GetUpdatesResponse* response);

  // Processes a Commit call.
  bool HandleCommitRequest(const sync_pb::CommitMessage& message,
                           const std::string& invalidator_client_id,
                           sync_pb::CommitResponse* response,
                           DataTypeSet* throttled_datatypes_in_request);

  void ClearServerData();

  void DeleteAllEntitiesForDataType(DataType data_type);

  // Creates and saves a permanent folder for Bookmarks (e.g., Bookmark Bar).
  bool CreatePermanentBookmarkFolder(const std::string& server_tag,
                                     const std::string& name);

  // Returns a pointer to the permanent bookmark folder with `server_tag` if it
  // exists, or nullptr otherwise.
  const LoopbackServerEntity* FindPermanentBookmarkFolder(
      const std::string& server_tag) const;

  // Inserts the default permanent items in `entities_`.
  bool CreateDefaultPermanentItems();

  // Returns generated key which may contain any bytes (not necessarily UTF-8).
  std::vector<uint8_t> GenerateNewKeystoreKey() const;

  // Saves a `entity` to `entities_`.
  void SaveEntity(std::unique_ptr<LoopbackServerEntity> entity);

  // Commits a client-side SyncEntity to the server as a LoopbackServerEntity.
  // The client that sent the commit is identified via `client_guid`. The
  // parent ID string present in `client_entity` should be ignored in favor
  // of `parent_id`. If the commit is successful, the entity's server ID string
  // is returned and a new LoopbackServerEntity is saved in `entities_`.
  std::string CommitEntity(
      const sync_pb::SyncEntity& client_entity,
      sync_pb::CommitResponse_EntryResponse* entry_response,
      const std::string& client_guid,
      const std::string& parent_id);

  // Populates `entry_response` based on the stored entity identified by
  // `entity_id`. It is assumed that the entity identified by `entity_id` has
  // already been stored using SaveEntity.
  void BuildEntryResponseForSuccessfulCommit(
      const std::string& entity_id,
      sync_pb::CommitResponse_EntryResponse* entry_response);

  // Determines whether the SyncEntity with id_string `id` is a child of an
  // entity with id_string `potential_parent_id`.
  bool IsChild(const std::string& id, const std::string& potential_parent_id);

  // Creates and saves tombstones for all children of the entity with the given
  // `parent_id`. A tombstone is not created for the entity itself.
  void DeleteChildren(const std::string& parent_id);

  // Updates the `entity` to a new version and increments the version counter
  // that the server uses to assign versions.
  void UpdateEntityVersion(LoopbackServerEntity* entity);

  // Returns the store birthday.
  std::string GetStoreBirthday() const;

  // Returns all entities stored by the server of the given `data_type`.
  // Permanent entities are excluded. This method is only used in tests.
  std::vector<sync_pb::SyncEntity> GetSyncEntitiesByDataType(
      syncer::DataType data_type);

  // Returns a list of permanent entities of the given `data_type`. This method
  // is only used in tests.
  std::vector<sync_pb::SyncEntity> GetPermanentSyncEntitiesByDataType(
      syncer::DataType data_type);

  // Creates a `base::DictValue` representation of all entities present in the
  // server. The dictionary keys are the strings generated by
  // DataTypeToDebugString and the values are base::ListValues containing
  // StringValue versions of entity names. Permanent entities are excluded. Used
  // by test to verify the contents of the server state.
  base::DictValue GetEntitiesAsDictForTesting();

  // Modifies the entity on the server with the given `id`. The entity's
  // EntitySpecifics are replaced with `updated_specifics` and its version is
  // updated to n+1. If the given `id` does not exist or the DataType of
  // `updated_specifics` does not match the entity, false is returned.
  // Otherwise, true is returned to represent a successful modification.
  //
  // This method sometimes updates entity data beyond EntitySpecifics. For
  // example, in the case of a bookmark, changing the BookmarkSpecifics title
  // field will modify the top-level entity's name field.
  // This method is only used in tests.
  bool ModifyEntitySpecifics(const std::string& id,
                             const sync_pb::EntitySpecifics& updated_specifics);

  // This method is only used in tests.
  bool ModifyBookmarkEntity(const std::string& id,
                            const std::string& parent_id,
                            const sync_pb::EntitySpecifics& updated_specifics);

  // Use this callback to generate response types for entities. They will still
  // be "committed" and stored as normal, this only affects the response type
  // the client sees. This allows tests to still inspect what the client has
  // done, although not as useful of a mechanism for multi client tests. Care
  // should be taken when failing responses, as the client will go into
  // exponential backoff, which can cause tests to be slow or time out.
  // This method is only used in tests.
  void OverrideResponseType(ResponseTypeProvider response_type_override);

  // Serializes the server state to `proto`.
  void SerializeState(sync_pb::LoopbackServerProto* proto) const;

  // Populates the server state from `proto`. Returns true iff successful.
  bool DeSerializeState(const sync_pb::LoopbackServerProto& proto);

  // Schedules committing state to disk at some later time. Repeat calls are
  // batched together. Outstanding scheduled writes are committed at shutdown.
  // Returns true on success.
  bool ScheduleSaveStateToFile();

  // Loads all entities and server state from a protobuf file. Returns true on
  // success.
  bool LoadStateFromFile();

  void set_observer_for_tests(ObserverForTests* observer) {
    observer_for_tests_ = observer;
  }

  bool strong_consistency_model_enabled_ = false;

  // This is the last version number assigned to an entity. The next entity will
  // have a version number of version_ + 1.
  int64_t version_ = 0;

  int64_t store_birthday_ = 0;

  DataTypeSet throttled_types_;
  DataTypeSet full_update_types_;

  std::optional<sync_pb::ChipBag> bag_of_chips_;

  std::map<DataType, int> migration_versions_;

  bool use_gc_directive_for_migration_ = false;

  int max_get_updates_batch_size_ = 1000000;

  EntityMap entities_;
  std::vector<std::vector<uint8_t>> keystore_keys_;

  // The file used to store the local sync data.
  const base::FilePath persistent_file_;

  // Used to limit the rate of file rewrites due to updates.
  base::ImportantFileWriter writer_;

  // Used to verify that LoopbackServer is only used from one sequence.
  SEQUENCE_CHECKER(sequence_checker_);

  // Used to observe the completion of commit messages for the sake of testing.
  raw_ptr<ObserverForTests> observer_for_tests_ = nullptr;

  // Response type override callback used in tests.
  ResponseTypeProvider response_type_override_;
};

}  // namespace syncer

#endif  // COMPONENTS_SYNC_ENGINE_LOOPBACK_SERVER_LOOPBACK_SERVER_H_
