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

#include "components/autofill/core/browser/webdata/autocomplete_sync_bridge.h"

#include <algorithm>
#include <memory>
#include <set>
#include <unordered_set>
#include <utility>
#include <vector>

#include "base/bind.h"
#include "base/strings/utf_string_conversions.h"
#include "components/autofill/core/browser/proto/autofill_sync.pb.h"
#include "components/autofill/core/browser/webdata/autofill_table.h"
#include "components/autofill/core/browser/webdata/autofill_webdata_backend.h"
#include "components/autofill/core/browser/webdata/autofill_webdata_service.h"
#include "components/sync/model/entity_data.h"
#include "components/sync/model/model_type_change_processor.h"
#include "components/sync/model/mutable_data_batch.h"
#include "components/sync/model_impl/client_tag_based_model_type_processor.h"
#include "components/sync/model_impl/sync_metadata_store_change_list.h"
#include "net/base/escape.h"

using base::Optional;
using base::Time;
using sync_pb::AutofillSpecifics;
using syncer::ClientTagBasedModelTypeProcessor;
using syncer::EntityChange;
using syncer::EntityChangeList;
using syncer::EntityData;
using syncer::MetadataChangeList;
using syncer::ModelError;
using syncer::ModelTypeChangeProcessor;
using syncer::ModelTypeSyncBridge;
using syncer::MutableDataBatch;

namespace autofill {

namespace {

const char kAutocompleteEntryNamespaceTag[] = "autofill_entry|";
const char kAutocompleteTagDelimiter[] = "|";

// Simplify checking for optional errors and returning only when present.
#define RETURN_IF_ERROR(x)                \
  if (Optional<ModelError> ret_val = x) { \
    return ret_val;                       \
  }

void* UserDataKey() {
  // Use the address of a static that COMDAT folding won't ever collide
  // with something else.
  static int user_data_key = 0;
  return reinterpret_cast<void*>(&user_data_key);
}

std::string EscapeIdentifiers(const AutofillSpecifics& specifics) {
  return net::EscapePath(specifics.name()) +
         std::string(kAutocompleteTagDelimiter) +
         net::EscapePath(specifics.value());
}

std::unique_ptr<EntityData> CreateEntityData(const AutofillEntry& entry) {
  auto entity_data = std::make_unique<EntityData>();
  AutofillSpecifics* autofill = entity_data->specifics.mutable_autofill();
  autofill->set_name(base::UTF16ToUTF8(entry.key().name()));
  autofill->set_value(base::UTF16ToUTF8(entry.key().value()));
  autofill->add_usage_timestamp(entry.date_created().ToInternalValue());
  if (entry.date_created() != entry.date_last_used())
    autofill->add_usage_timestamp(entry.date_last_used().ToInternalValue());
  entity_data->non_unique_name = EscapeIdentifiers(*autofill);
  return entity_data;
}

std::string BuildSerializedStorageKey(const std::string& name,
                                      const std::string& value) {
  AutofillSyncStorageKey proto;
  proto.set_name(name);
  proto.set_value(value);
  return proto.SerializeAsString();
}

std::string GetStorageKeyFromModel(const AutofillKey& key) {
  return BuildSerializedStorageKey(base::UTF16ToUTF8(key.name()),
                                   base::UTF16ToUTF8(key.value()));
}

AutofillEntry MergeEntryDates(const AutofillEntry& entry1,
                              const AutofillEntry& entry2) {
  DCHECK(entry1.key() == entry2.key());
  return AutofillEntry(
      entry1.key(), std::min(entry1.date_created(), entry2.date_created()),
      std::max(entry1.date_last_used(), entry2.date_last_used()));
}

bool ParseStorageKey(const std::string& storage_key, AutofillKey* out_key) {
  AutofillSyncStorageKey proto;
  if (proto.ParseFromString(storage_key)) {
    *out_key = AutofillKey(base::UTF8ToUTF16(proto.name()),
                           base::UTF8ToUTF16((proto.value())));
    return true;
  }
  return false;
}

AutofillEntry CreateAutofillEntry(const AutofillSpecifics& autofill_specifics) {
  AutofillKey key(base::UTF8ToUTF16(autofill_specifics.name()),
                  base::UTF8ToUTF16(autofill_specifics.value()));
  Time date_created, date_last_used;
  const google::protobuf::RepeatedField<int64_t>& timestamps =
      autofill_specifics.usage_timestamp();
  if (!timestamps.empty()) {
    auto iter_pair = std::minmax_element(timestamps.begin(), timestamps.end());
    date_created = Time::FromInternalValue(*iter_pair.first);
    date_last_used = Time::FromInternalValue(*iter_pair.second);
  }
  return AutofillEntry(key, date_created, date_last_used);
}

// This is used to respond to ApplySyncChanges() and MergeSyncData(). Attempts
// to lazily load local data, and then react to sync data by maintaining
// internal state until flush calls are made, at which point the applicable
// modification should be sent towards local and sync directions.
class SyncDifferenceTracker {
 public:
  explicit SyncDifferenceTracker(AutofillTable* table) : table_(table) {}

  Optional<ModelError> IncorporateRemoteSpecifics(
      const std::string& storage_key,
      const AutofillSpecifics& specifics) {
    if (!specifics.has_value()) {
      // A long time ago autofill had a different format, and it's possible we
      // could encounter some of that legacy data. It is not useful to us,
      // because an autofill entry with no value will not place any text in a
      // form for the user. So drop all of these on the floor.
      DVLOG(1) << "Dropping old-style autofill profile change.";
      return {};
    }

    const AutofillEntry remote = CreateAutofillEntry(specifics);
    DCHECK_EQ(storage_key, GetStorageKeyFromModel(remote.key()));

    Optional<AutofillEntry> local;
    if (!ReadEntry(remote.key(), &local))
      return ModelError(FROM_HERE, "Failed reading from WebDatabase.");

    if (!local) {
      save_to_local_.push_back(remote);
    } else {
      unique_to_local_.erase(local.value());
      if (remote != local.value()) {
        if (specifics.usage_timestamp().empty()) {
          // Skip merging if there are no timestamps. We don't want to wipe out
          // a local value of |date_created| if the remote copy is oddly formed.
          save_to_sync_.push_back(local.value());
        } else {
          const AutofillEntry merged = MergeEntryDates(local.value(), remote);
          save_to_local_.push_back(merged);
          save_to_sync_.push_back(merged);
        }
      }
    }
    return {};
  }

  Optional<ModelError> IncorporateRemoteDelete(const std::string& storage_key) {
    AutofillKey key;
    if (!ParseStorageKey(storage_key, &key)) {
      return ModelError(FROM_HERE, "Failed parsing storage key.");
    }
    delete_from_local_.insert(key);
    return {};
  }

  Optional<ModelError> FlushToLocal(AutofillWebDataBackend* web_data_backend) {
    for (const AutofillKey& key : delete_from_local_) {
      if (!table_->RemoveFormElement(key.name(), key.value())) {
        return ModelError(FROM_HERE, "Failed deleting from WebDatabase");
      }
    }
    if (!table_->UpdateAutofillEntries(save_to_local_)) {
      return ModelError(FROM_HERE, "Failed updating WebDatabase");
    }
    if (!delete_from_local_.empty() || !save_to_local_.empty()) {
      web_data_backend->NotifyOfMultipleAutofillChanges();
    }
    return {};
  }

  Optional<ModelError> FlushToSync(
      bool include_local_only,
      std::unique_ptr<MetadataChangeList> metadata_change_list,
      ModelTypeChangeProcessor* change_processor) {
    for (const AutofillEntry& entry : save_to_sync_) {
      change_processor->Put(GetStorageKeyFromModel(entry.key()),
                            CreateEntityData(entry),
                            metadata_change_list.get());
    }
    if (include_local_only) {
      if (!InitializeIfNeeded()) {
        return ModelError(FROM_HERE, "Failed reading from WebDatabase.");
      }
      for (const AutofillEntry& entry : unique_to_local_) {
        // This should never be true because only ApplySyncChanges should be
        // calling IncorporateRemoteDelete, while only MergeSyncData should be
        // passing in true for |include_local_only|. If this requirement
        // changes, this DCHECK can change to act as a filter.
        DCHECK(delete_from_local_.find(entry.key()) ==
               delete_from_local_.end());
        change_processor->Put(GetStorageKeyFromModel(entry.key()),
                              CreateEntityData(entry),
                              metadata_change_list.get());
      }
    }
    return static_cast<syncer::SyncMetadataStoreChangeList*>(
               metadata_change_list.get())
        ->TakeError();
  }

 private:
  // There are three major outcomes of this method.
  // 1. An error is encountered reading from the db, false is returned.
  // 2. The entry is not found, |entry| will not be touched.
  // 3. The entry is found, |entry| will be set.
  bool ReadEntry(const AutofillKey& key, Optional<AutofillEntry>* entry) {
    if (!InitializeIfNeeded()) {
      return false;
    }
    auto iter = unique_to_local_.find(AutofillEntry(key, Time(), Time()));
    if (iter != unique_to_local_.end()) {
      *entry = *iter;
    }
    return true;
  }

  bool InitializeIfNeeded() {
    if (initialized_) {
      return true;
    }

    std::vector<AutofillEntry> vector;
    if (!table_->GetAllAutofillEntries(&vector)) {
      return false;
    }

    unique_to_local_ = std::set<AutofillEntry>(vector.begin(), vector.end());
    initialized_ = true;
    return true;
  }

  AutofillTable* table_;

  // This class attempts to lazily load data from |table_|. This field tracks
  // if that has happened or not yet. To facilitate this, the first usage of
  // |unique_to_local_| should typically be done through ReadEntry().
  bool initialized_ = false;

  // Important to note that because AutofillEntry's operator < simply compares
  // contained AutofillKeys, this acts as a map<AutofillKey, AutofillEntry>.
  // Shouldn't be accessed until either ReadEntry() or InitializeIfNeeded() is
  // called, afterward it will start with all the local data. As sync data is
  // encountered entries are removed from here, leaving only entries that exist
  // solely on the local client.
  std::set<AutofillEntry> unique_to_local_;

  std::set<AutofillKey> delete_from_local_;
  std::vector<AutofillEntry> save_to_local_;

  // Contains merged data for entries that existed on both sync and local sides
  // and need to be saved back to sync.
  std::vector<AutofillEntry> save_to_sync_;

  DISALLOW_COPY_AND_ASSIGN(SyncDifferenceTracker);
};

}  // namespace

// static
void AutocompleteSyncBridge::CreateForWebDataServiceAndBackend(
    AutofillWebDataService* web_data_service,
    AutofillWebDataBackend* web_data_backend) {
  web_data_service->GetDBUserData()->SetUserData(
      UserDataKey(),
      std::make_unique<AutocompleteSyncBridge>(
          web_data_backend,
          std::make_unique<ClientTagBasedModelTypeProcessor>(
              syncer::AUTOFILL, /*dump_stack=*/base::RepeatingClosure())));
}

// static
ModelTypeSyncBridge* AutocompleteSyncBridge::FromWebDataService(
    AutofillWebDataService* web_data_service) {
  return static_cast<AutocompleteSyncBridge*>(
      web_data_service->GetDBUserData()->GetUserData(UserDataKey()));
}

AutocompleteSyncBridge::AutocompleteSyncBridge(
    AutofillWebDataBackend* backend,
    std::unique_ptr<ModelTypeChangeProcessor> change_processor)
    : ModelTypeSyncBridge(std::move(change_processor)),
      web_data_backend_(backend),
      scoped_observer_(this) {
  DCHECK(web_data_backend_);

  scoped_observer_.Add(web_data_backend_);

  LoadMetadata();
}

AutocompleteSyncBridge::~AutocompleteSyncBridge() {
  DCHECK(thread_checker_.CalledOnValidThread());
}

std::unique_ptr<MetadataChangeList>
AutocompleteSyncBridge::CreateMetadataChangeList() {
  DCHECK(thread_checker_.CalledOnValidThread());
  return std::make_unique<syncer::SyncMetadataStoreChangeList>(
      GetAutofillTable(), syncer::AUTOFILL);
}

Optional<syncer::ModelError> AutocompleteSyncBridge::MergeSyncData(
    std::unique_ptr<MetadataChangeList> metadata_change_list,
    EntityChangeList entity_data) {
  DCHECK(thread_checker_.CalledOnValidThread());

  SyncDifferenceTracker tracker(GetAutofillTable());
  for (const auto& change : entity_data) {
    DCHECK(change.data().specifics.has_autofill());
    RETURN_IF_ERROR(tracker.IncorporateRemoteSpecifics(
        change.storage_key(), change.data().specifics.autofill()));
  }

  RETURN_IF_ERROR(tracker.FlushToLocal(web_data_backend_));
  RETURN_IF_ERROR(tracker.FlushToSync(true, std::move(metadata_change_list),
                                change_processor()));
  web_data_backend_->RemoveExpiredFormElements();
  web_data_backend_->NotifyThatSyncHasStarted(syncer::AUTOFILL);
  return {};
}

Optional<ModelError> AutocompleteSyncBridge::ApplySyncChanges(
    std::unique_ptr<MetadataChangeList> metadata_change_list,
    EntityChangeList entity_changes) {
  DCHECK(thread_checker_.CalledOnValidThread());

  SyncDifferenceTracker tracker(GetAutofillTable());
  for (const EntityChange& change : entity_changes) {
    if (change.type() == EntityChange::ACTION_DELETE) {
      RETURN_IF_ERROR(tracker.IncorporateRemoteDelete(change.storage_key()));
    } else {
      DCHECK(change.data().specifics.has_autofill());
      RETURN_IF_ERROR(tracker.IncorporateRemoteSpecifics(
          change.storage_key(), change.data().specifics.autofill()));
    }
  }

  RETURN_IF_ERROR(tracker.FlushToLocal(web_data_backend_));
  RETURN_IF_ERROR(tracker.FlushToSync(false, std::move(metadata_change_list),
                                      change_processor()));
  web_data_backend_->RemoveExpiredFormElements();
  return {};
}

void AutocompleteSyncBridge::AutocompleteSyncBridge::GetData(
    StorageKeyList storage_keys,
    DataCallback callback) {
  DCHECK(thread_checker_.CalledOnValidThread());
  std::vector<AutofillEntry> entries;
  if (!GetAutofillTable()->GetAllAutofillEntries(&entries)) {
    change_processor()->ReportError(
        {FROM_HERE, "Failed to load entries from table."});
    return;
  }

  std::unordered_set<std::string> keys_set(storage_keys.begin(),
                                           storage_keys.end());
  auto batch = std::make_unique<MutableDataBatch>();
  for (const AutofillEntry& entry : entries) {
    std::string key = GetStorageKeyFromModel(entry.key());
    if (keys_set.find(key) != keys_set.end()) {
      batch->Put(key, CreateEntityData(entry));
    }
  }
  std::move(callback).Run(std::move(batch));
}

void AutocompleteSyncBridge::GetAllDataForDebugging(DataCallback callback) {
  DCHECK(thread_checker_.CalledOnValidThread());

  std::vector<AutofillEntry> entries;
  if (!GetAutofillTable()->GetAllAutofillEntries(&entries)) {
    change_processor()->ReportError(
        {FROM_HERE, "Failed to load entries from table."});
    return;
  }

  auto batch = std::make_unique<MutableDataBatch>();
  for (const AutofillEntry& entry : entries) {
    batch->Put(GetStorageKeyFromModel(entry.key()), CreateEntityData(entry));
  }
  std::move(callback).Run(std::move(batch));
}

void AutocompleteSyncBridge::ActOnLocalChanges(
    const AutofillChangeList& changes) {
  if (!change_processor()->IsTrackingMetadata()) {
    return;
  }

  auto metadata_change_list =
      std::make_unique<syncer::SyncMetadataStoreChangeList>(GetAutofillTable(),
                                                            syncer::AUTOFILL);
  for (const auto& change : changes) {
    const std::string storage_key = GetStorageKeyFromModel(change.key());
    switch (change.type()) {
      case AutofillChange::ADD:
      case AutofillChange::UPDATE: {
        base::Time date_created, date_last_used;
        bool success = GetAutofillTable()->GetAutofillTimestamps(
            change.key().name(), change.key().value(), &date_created,
            &date_last_used);
        if (!success) {
          change_processor()->ReportError(
              {FROM_HERE, "Failed reading autofill entry from WebDatabase."});
          return;
        }

        const AutofillEntry entry(change.key(), date_created, date_last_used);
        change_processor()->Put(storage_key, CreateEntityData(entry),
                                metadata_change_list.get());
        break;
      }
      case AutofillChange::REMOVE: {
        change_processor()->Delete(storage_key, metadata_change_list.get());
        break;
      }
    }
  }

  if (Optional<ModelError> error = metadata_change_list->TakeError())
    change_processor()->ReportError(*error);
}

void AutocompleteSyncBridge::LoadMetadata() {
  if (!web_data_backend_ || !web_data_backend_->GetDatabase() ||
      !GetAutofillTable()) {
    change_processor()->ReportError(
        {FROM_HERE, "Failed to load AutofillWebDatabase."});
    return;
  }

  auto batch = std::make_unique<syncer::MetadataBatch>();
  if (!GetAutofillTable()->GetAllSyncMetadata(syncer::AUTOFILL, batch.get())) {
    change_processor()->ReportError(
        {FROM_HERE, "Failed reading autofill metadata from WebDatabase."});
    return;
  }
  change_processor()->ModelReadyToSync(std::move(batch));
}

std::string AutocompleteSyncBridge::GetClientTag(
    const EntityData& entity_data) {
  DCHECK(entity_data.specifics.has_autofill());
  // Must have the format "autofill_entry|$name|$value" where $name and $value
  // are URL escaped. This is to maintain compatibility with the previous sync
  // integration (Directory and SyncableService).
  return std::string(kAutocompleteEntryNamespaceTag) +
         EscapeIdentifiers(entity_data.specifics.autofill());
}

std::string AutocompleteSyncBridge::GetStorageKey(
    const EntityData& entity_data) {
  DCHECK(entity_data.specifics.has_autofill());
  // Marginally more space efficient than GetClientTag() by omitting the
  // kAutocompleteEntryNamespaceTag prefix and using protobuf serialization
  // instead of URL escaping for Unicode characters.
  const AutofillSpecifics specifics = entity_data.specifics.autofill();
  return BuildSerializedStorageKey(specifics.name(), specifics.value());
}

void AutocompleteSyncBridge::AutofillEntriesChanged(
    const AutofillChangeList& changes) {
  DCHECK(thread_checker_.CalledOnValidThread());
  ActOnLocalChanges(changes);
}

AutofillTable* AutocompleteSyncBridge::GetAutofillTable() const {
  return AutofillTable::FromWebDatabase(web_data_backend_->GetDatabase());
}

}  // namespace autofill
