// Copyright 2015 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/autofill_wallet_metadata_syncable_service.h"

#include <stddef.h>

#include <utility>

#include "base/base64.h"
#include "base/bind.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/numerics/safe_conversions.h"
#include "base/time/time.h"
#include "components/autofill/core/browser/autofill_data_model.h"
#include "components/autofill/core/browser/autofill_profile.h"
#include "components/autofill/core/browser/credit_card.h"
#include "components/autofill/core/browser/webdata/autofill_change.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/sync_change.h"
#include "components/sync/model/sync_change_processor.h"
#include "components/sync/model/sync_data.h"
#include "components/sync/model/sync_error_factory.h"
#include "components/sync/protocol/sync.pb.h"

namespace autofill {

namespace {

// The length of the GUIDs used for local autofill data. It is different than
// the length used for server autofill data.
const int kLocalGuidSize = 36;

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

// Sets the common syncable |metadata| for the |local_data_model|.
void SetCommonMetadata(sync_pb::WalletMetadataSpecifics::Type type,
                       const std::string& server_id,
                       const AutofillDataModel& local_data_model,
                       sync_pb::WalletMetadataSpecifics* metadata) {
  metadata->set_type(type);
  metadata->set_id(server_id);
  metadata->set_use_count(local_data_model.use_count());
  metadata->set_use_date(local_data_model.use_date().ToInternalValue());
}

// Returns syncable metadata for the |local_profile|.
syncer::SyncData BuildSyncData(sync_pb::WalletMetadataSpecifics::Type type,
                               const std::string& server_id,
                               const AutofillProfile& local_profile) {
  sync_pb::EntitySpecifics entity;
  sync_pb::WalletMetadataSpecifics* metadata = entity.mutable_wallet_metadata();
  SetCommonMetadata(type, server_id, local_profile, metadata);
  metadata->set_address_has_converted(local_profile.has_converted());
  std::string sync_tag = "address-" + server_id;

  return syncer::SyncData::CreateLocalData(sync_tag, sync_tag, entity);
}

// Returns syncable metadata for the |local_card|.
syncer::SyncData BuildSyncData(sync_pb::WalletMetadataSpecifics::Type type,
                               const std::string& server_id,
                               const CreditCard& local_card) {
  sync_pb::EntitySpecifics entity;
  sync_pb::WalletMetadataSpecifics* metadata = entity.mutable_wallet_metadata();
  SetCommonMetadata(type, server_id, local_card, metadata);
  // The strings must be in valid UTF-8 to sync.
  std::string billing_address_id;
  base::Base64Encode(local_card.billing_address_id(), &billing_address_id);
  metadata->set_card_billing_address_id(billing_address_id);
  std::string sync_tag = "card-" + server_id;

  return syncer::SyncData::CreateLocalData(sync_tag, sync_tag, entity);
}

// If the metadata exists locally, undelete it on the sync server.
template <class DataType>
void UndeleteMetadataIfExisting(
    const std::string& server_id,
    const sync_pb::WalletMetadataSpecifics::Type& metadata_type,
    std::unordered_map<std::string, std::unique_ptr<DataType>>* locals,
    syncer::SyncChangeList* changes_to_sync) {
  const auto& it = locals->find(server_id);
  if (it != locals->end()) {
    std::unique_ptr<DataType> local_metadata = std::move(it->second);
    locals->erase(it);
    changes_to_sync->push_back(syncer::SyncChange(
        FROM_HERE, syncer::SyncChange::ACTION_ADD,
        BuildSyncData(metadata_type, server_id, *local_metadata)));
  }
}

syncer::SyncDataList::iterator FindServerIdAndTypeInCache(
    const std::string& server_id,
    const sync_pb::WalletMetadataSpecifics::Type& type,
    syncer::SyncDataList* cache) {
  for (auto it = cache->begin(); it != cache->end(); ++it) {
    if (server_id == it->GetSpecifics().wallet_metadata().id() &&
        type == it->GetSpecifics().wallet_metadata().type()) {
      return it;
    }
  }

  return cache->end();
}

syncer::SyncDataList::iterator FindItemInCache(const syncer::SyncData& item,
                                               syncer::SyncDataList* cache) {
  return FindServerIdAndTypeInCache(
      item.GetSpecifics().wallet_metadata().id(),
      item.GetSpecifics().wallet_metadata().type(), cache);
}

void RemoveItemFromCache(const syncer::SyncData& item,
                         syncer::SyncDataList* cache) {
  auto it = FindItemInCache(item, cache);
  if (it != cache->end())
    cache->erase(it);
}

void AddOrUpdateItemInCache(const syncer::SyncData& item,
                            syncer::SyncDataList* cache) {
  auto it = FindItemInCache(item, cache);
  if (it != cache->end())
    *it = item;
  else
    cache->push_back(item);
}

void ApplyChangesToCache(const syncer::SyncChangeList& changes,
                         syncer::SyncDataList* cache) {
  for (const syncer::SyncChange& change : changes) {
    switch (change.change_type()) {
      case syncer::SyncChange::ACTION_ADD:
      // Intentional fall through.
      case syncer::SyncChange::ACTION_UPDATE:
        AddOrUpdateItemInCache(change.sync_data(), cache);
        break;

      case syncer::SyncChange::ACTION_DELETE:
        RemoveItemFromCache(change.sync_data(), cache);
        break;

      case syncer::SyncChange::ACTION_INVALID:
        NOTREACHED();
        break;
    }
  }
}

// Merges the metadata of the remote and local versions of the data model.
void MergeCommonMetadata(
    const sync_pb::WalletMetadataSpecifics& remote_metadata,
    AutofillDataModel* local_model,
    bool* is_remote_outdated,
    bool* is_local_modified) {
  size_t remote_use_count =
      base::checked_cast<size_t>(remote_metadata.use_count());
  base::Time remote_use_date =
      base::Time::FromInternalValue(remote_metadata.use_date());

  // If the two models have the same metadata, do nothing.
  if (local_model->use_count() == remote_use_count &&
      local_model->use_date() == remote_use_date) {
    return;
  }

  // Special case for local models with a use_count of one. This means the local
  // model was only created, never used. The remote model should always be
  // preferred.
  // This situation can happen for new Chromium instances where there is no data
  // yet on disk, making the use_date artifically high. Once the metadata sync
  // kicks in, we should use that value.
  if (local_model->use_count() == 1) {
    local_model->set_use_date(remote_use_date);
    local_model->set_use_count(remote_use_count);
    *is_local_modified = true;
  } else {
    // Otherwise, just keep the most recent use date and biggest use count.
    if (local_model->use_date() < remote_use_date) {
      local_model->set_use_date(remote_use_date);
      *is_local_modified = true;
    } else if (local_model->use_date() > remote_use_date) {
      *is_remote_outdated = true;
    }

    if (local_model->use_count() < remote_use_count) {
      local_model->set_use_count(remote_use_count);
      *is_local_modified = true;
    } else if (local_model->use_count() > remote_use_count) {
      *is_remote_outdated = true;
    }
  }
}

// Merges the metadata of the remote and local versions of the profile.
void MergeMetadata(const sync_pb::WalletMetadataSpecifics& remote_metadata,
                   AutofillProfile* local_profile,
                   bool* is_remote_outdated,
                   bool* is_local_modified) {
  // Merge the has_converted status.
  if (local_profile->has_converted() !=
      remote_metadata.address_has_converted()) {
    if (!local_profile->has_converted()) {
      local_profile->set_has_converted(true);
      *is_local_modified = true;
    } else {
      *is_remote_outdated = true;
    }
  }

  // Merge the use_count and use_date.
  MergeCommonMetadata(remote_metadata, local_profile, is_remote_outdated,
                      is_local_modified);
}

// Whether the |current_billing_address_id| is considered outdated compared to
// the |proposed_billing_address_id|.
bool IsBillingAddressOutdated(const std::string& current_billing_address_id,
                              const std::string& proposed_billing_address_id) {
  DCHECK(current_billing_address_id != proposed_billing_address_id);

  // If the current billing address is empty, or if the current one refers to a
  // server address and the proposed one refers to a local address, the current
  // billing address is considered outdated.
  return current_billing_address_id.empty() ||
         (current_billing_address_id.size() != kLocalGuidSize &&
          proposed_billing_address_id.size() == kLocalGuidSize);
}

// Merges the metadata of the remote and local versions of the credit card.
void MergeMetadata(const sync_pb::WalletMetadataSpecifics& remote_metadata,
                   CreditCard* local_card,
                   bool* is_remote_outdated,
                   bool* is_local_modified) {
  // Merge the billing_address_id. Do this before updating the use_count
  // because it may be used to determine what id to keep.
  std::string remote_billing_address_id;
  base::Base64Decode(remote_metadata.card_billing_address_id(),
                     &remote_billing_address_id);

  if (local_card->billing_address_id() != remote_billing_address_id) {
    if (IsBillingAddressOutdated(local_card->billing_address_id(),
                                 remote_billing_address_id)) {
      local_card->set_billing_address_id(remote_billing_address_id);
      *is_local_modified = true;
    } else if (IsBillingAddressOutdated(remote_billing_address_id,
                                        local_card->billing_address_id())) {
      *is_remote_outdated = true;
    } else {
      // The cards have a different non-empty billing address id and both refer
      // to the same type of address. Keep the billing address id of the most
      // recently used card. If both have the same timestamp, the remote version
      // should be kept in order to stabilize the values.
      base::Time remote_use_date =
          base::Time::FromInternalValue(remote_metadata.use_date());
      if (local_card->use_date() <= remote_use_date) {
        local_card->set_billing_address_id(remote_billing_address_id);
        *is_local_modified = true;
      } else {
        *is_remote_outdated = true;
      }
    }
  }

  // Merge the use_count and use_date.
  MergeCommonMetadata(remote_metadata, local_card, is_remote_outdated,
                      is_local_modified);
}

// Merges |remote| metadata into a collection of metadata |locals|. Returns true
// if the corresponding local metadata was found.
//
// Stores an "update" in |changes_to_sync| if |remote| corresponds to an item in
// |locals| that has higher use count and later use date.
template <class DataType>
bool MergeRemote(
    const syncer::SyncData& remote,
    const base::Callback<bool(const DataType&)>& updater,
    std::unordered_map<std::string, std::unique_ptr<DataType>>* locals,
    syncer::SyncChangeList* changes_to_sync) {
  DCHECK(locals);
  DCHECK(changes_to_sync);

  const sync_pb::WalletMetadataSpecifics& remote_metadata =
      remote.GetSpecifics().wallet_metadata();
  auto it = locals->find(remote_metadata.id());
  if (it == locals->end())
    return false;

  std::unique_ptr<DataType> local_metadata = std::move(it->second);
  locals->erase(it);

  bool is_local_modified = false;
  bool is_remote_outdated = false;
  MergeMetadata(remote_metadata, local_metadata.get(), &is_remote_outdated,
                &is_local_modified);

  if (is_remote_outdated) {
    changes_to_sync->push_back(syncer::SyncChange(
        FROM_HERE, syncer::SyncChange::ACTION_UPDATE,
        BuildSyncData(remote_metadata.type(), remote_metadata.id(),
                      *local_metadata)));
  }

  if (is_local_modified)
    updater.Run(*local_metadata);

  return true;
}

template <typename DataType>
std::string GetServerId(const DataType& data) {
  std::string server_id;
  base::Base64Encode(data.server_id(), &server_id);
  return server_id;
}

}  // namespace

AutofillWalletMetadataSyncableService::
    ~AutofillWalletMetadataSyncableService() {}

syncer::SyncMergeResult
AutofillWalletMetadataSyncableService::MergeDataAndStartSyncing(
    syncer::ModelType type,
    const syncer::SyncDataList& initial_sync_data,
    std::unique_ptr<syncer::SyncChangeProcessor> sync_processor,
    std::unique_ptr<syncer::SyncErrorFactory> sync_error_factory) {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(!sync_processor_);
  DCHECK(!sync_error_factory_);
  DCHECK_EQ(syncer::AUTOFILL_WALLET_METADATA, type);

  sync_processor_ = std::move(sync_processor);
  sync_error_factory_ = std::move(sync_error_factory);

  cache_ = initial_sync_data;

  syncer::SyncMergeResult result = MergeData(initial_sync_data);
  if (web_data_backend_)
    web_data_backend_->NotifyThatSyncHasStarted(type);
  return result;
}

void AutofillWalletMetadataSyncableService::StopSyncing(
    syncer::ModelType type) {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK_EQ(syncer::AUTOFILL_WALLET_METADATA, type);

  sync_processor_.reset();
  sync_error_factory_.reset();
  cache_.clear();
}

syncer::SyncDataList AutofillWalletMetadataSyncableService::GetAllSyncData(
    syncer::ModelType type) const {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK_EQ(syncer::AUTOFILL_WALLET_METADATA, type);

  syncer::SyncDataList data_list;
  std::unordered_map<std::string, std::unique_ptr<AutofillProfile>> profiles;
  std::unordered_map<std::string, std::unique_ptr<CreditCard>> cards;
  if (GetLocalData(&profiles, &cards)) {
    for (const auto& it : profiles) {
      data_list.push_back(BuildSyncData(
          sync_pb::WalletMetadataSpecifics::ADDRESS, it.first, *it.second));
    }

    for (const auto& it : cards) {
      data_list.push_back(BuildSyncData(sync_pb::WalletMetadataSpecifics::CARD,
                                        it.first, *it.second));
    }
  }

  return data_list;
}

syncer::SyncError AutofillWalletMetadataSyncableService::ProcessSyncChanges(
    const base::Location& from_here,
    const syncer::SyncChangeList& changes_from_sync) {
  DCHECK(thread_checker_.CalledOnValidThread());

  ApplyChangesToCache(changes_from_sync, &cache_);

  std::unordered_map<std::string, std::unique_ptr<AutofillProfile>> profiles;
  std::unordered_map<std::string, std::unique_ptr<CreditCard>> cards;
  GetLocalData(&profiles, &cards);

  // base::Unretained is used because the callbacks are invoked synchronously.
  base::Callback<bool(const AutofillProfile&)> address_updater =
      base::Bind(&AutofillWalletMetadataSyncableService::UpdateAddressStats,
                 base::Unretained(this));
  base::Callback<bool(const CreditCard&)> card_updater =
      base::Bind(&AutofillWalletMetadataSyncableService::UpdateCardStats,
                 base::Unretained(this));

  syncer::SyncChangeList changes_to_sync;
  for (const syncer::SyncChange& change : changes_from_sync) {
    const sync_pb::WalletMetadataSpecifics& remote_metadata =
        change.sync_data().GetSpecifics().wallet_metadata();
    switch (change.change_type()) {
      case syncer::SyncChange::ACTION_ADD:
      // Intentional fall through.
      case syncer::SyncChange::ACTION_UPDATE:
        switch (remote_metadata.type()) {
          case sync_pb::WalletMetadataSpecifics::ADDRESS:
            MergeRemote(change.sync_data(), address_updater, &profiles,
                        &changes_to_sync);
            break;

          case sync_pb::WalletMetadataSpecifics::CARD:
            MergeRemote(change.sync_data(), card_updater, &cards,
                        &changes_to_sync);
            break;

          case sync_pb::WalletMetadataSpecifics::UNKNOWN:
            NOTREACHED();
            break;
        }
        break;

      // Metadata should only be deleted when the underlying data is deleted.
      case syncer::SyncChange::ACTION_DELETE:
        switch (remote_metadata.type()) {
          case sync_pb::WalletMetadataSpecifics::ADDRESS:
            UndeleteMetadataIfExisting(
                remote_metadata.id(), sync_pb::WalletMetadataSpecifics::ADDRESS,
                &profiles, &changes_to_sync);
            break;

          case sync_pb::WalletMetadataSpecifics::CARD:
            UndeleteMetadataIfExisting(remote_metadata.id(),
                                       sync_pb::WalletMetadataSpecifics::CARD,
                                       &cards, &changes_to_sync);
            break;

          case sync_pb::WalletMetadataSpecifics::UNKNOWN:
            NOTREACHED();
            break;
        }
        break;

      case syncer::SyncChange::ACTION_INVALID:
        NOTREACHED();
        break;
    }
  }

  syncer::SyncError status;
  if (!changes_to_sync.empty())
    status = SendChangesToSyncServer(changes_to_sync);

  return status;
}

void AutofillWalletMetadataSyncableService::AutofillProfileChanged(
    const AutofillProfileChange& change) {
  DCHECK(thread_checker_.CalledOnValidThread());

  if (sync_processor_ && change.data_model() &&
      change.data_model()->record_type() != AutofillProfile::LOCAL_PROFILE) {
    AutofillDataModelChanged(GetServerId(*change.data_model()),
                             sync_pb::WalletMetadataSpecifics::ADDRESS,
                             *change.data_model());
  }
}

void AutofillWalletMetadataSyncableService::CreditCardChanged(
    const CreditCardChange& change) {
  DCHECK(thread_checker_.CalledOnValidThread());

  if (sync_processor_ && change.data_model() &&
      change.data_model()->record_type() != CreditCard::LOCAL_CARD) {
    AutofillDataModelChanged(GetServerId(*change.data_model()),
                             sync_pb::WalletMetadataSpecifics::CARD,
                             *change.data_model());
  }
}

void AutofillWalletMetadataSyncableService::AutofillMultipleChanged() {
  if (sync_processor_)
    MergeData(cache_);
}

// static
void AutofillWalletMetadataSyncableService::CreateForWebDataServiceAndBackend(
    AutofillWebDataService* web_data_service,
    AutofillWebDataBackend* web_data_backend,
    const std::string& app_locale) {
  web_data_service->GetDBUserData()->SetUserData(
      UserDataKey(), base::WrapUnique(new AutofillWalletMetadataSyncableService(
                         web_data_backend, app_locale)));
}

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

AutofillWalletMetadataSyncableService::AutofillWalletMetadataSyncableService(
    AutofillWebDataBackend* web_data_backend,
    const std::string& app_locale)
    : web_data_backend_(web_data_backend), scoped_observer_(this) {
  scoped_observer_.Add(web_data_backend_);
}

bool AutofillWalletMetadataSyncableService::GetLocalData(
    std::unordered_map<std::string, std::unique_ptr<AutofillProfile>>* profiles,
    std::unordered_map<std::string, std::unique_ptr<CreditCard>>* cards) const {
  std::vector<std::unique_ptr<AutofillProfile>> profile_list;
  bool success =
      AutofillTable::FromWebDatabase(web_data_backend_->GetDatabase())
          ->GetServerProfiles(&profile_list);
  while (!profile_list.empty()) {
    auto server_id = GetServerId(*profile_list.front());
    (*profiles)[server_id] = std::move(profile_list.front());
    profile_list.erase(profile_list.begin());
  }

  std::vector<std::unique_ptr<CreditCard>> card_list;
  success &= AutofillTable::FromWebDatabase(web_data_backend_->GetDatabase())
                 ->GetServerCreditCards(&card_list);
  while (!card_list.empty()) {
    auto server_id = GetServerId(*card_list.front());
    (*cards)[server_id] = std::move(card_list.front());
    card_list.erase(card_list.begin());
  }

  return success;
}

bool AutofillWalletMetadataSyncableService::UpdateAddressStats(
    const AutofillProfile& profile) {
  return AutofillTable::FromWebDatabase(web_data_backend_->GetDatabase())
      ->UpdateServerAddressMetadata(profile);
}

bool AutofillWalletMetadataSyncableService::UpdateCardStats(
    const CreditCard& credit_card) {
  return AutofillTable::FromWebDatabase(web_data_backend_->GetDatabase())
      ->UpdateServerCardMetadata(credit_card);
}

syncer::SyncError
AutofillWalletMetadataSyncableService::SendChangesToSyncServer(
    const syncer::SyncChangeList& changes_to_sync) {
  DCHECK(sync_processor_);
  ApplyChangesToCache(changes_to_sync, &cache_);
  return sync_processor_->ProcessSyncChanges(FROM_HERE, changes_to_sync);
}

syncer::SyncMergeResult AutofillWalletMetadataSyncableService::MergeData(
    const syncer::SyncDataList& sync_data) {
  std::unordered_map<std::string, std::unique_ptr<AutofillProfile>> profiles;
  std::unordered_map<std::string, std::unique_ptr<CreditCard>> cards;
  GetLocalData(&profiles, &cards);

  syncer::SyncMergeResult result(syncer::AUTOFILL_WALLET_METADATA);
  result.set_num_items_before_association(profiles.size() + cards.size());

  // base::Unretained is used because the callbacks are invoked synchronously.
  base::Callback<bool(const AutofillProfile&)> address_updater =
      base::Bind(&AutofillWalletMetadataSyncableService::UpdateAddressStats,
                 base::Unretained(this));
  base::Callback<bool(const CreditCard&)> card_updater =
      base::Bind(&AutofillWalletMetadataSyncableService::UpdateCardStats,
                 base::Unretained(this));

  syncer::SyncChangeList changes_to_sync;
  for (const syncer::SyncData& remote : sync_data) {
    DCHECK(remote.IsValid());
    DCHECK_EQ(syncer::AUTOFILL_WALLET_METADATA, remote.GetDataType());
    switch (remote.GetSpecifics().wallet_metadata().type()) {
      case sync_pb::WalletMetadataSpecifics::ADDRESS:
        if (!MergeRemote(remote, address_updater, &profiles,
                         &changes_to_sync)) {
          changes_to_sync.push_back(syncer::SyncChange(
              FROM_HERE, syncer::SyncChange::ACTION_DELETE, remote));
        }
        break;

      case sync_pb::WalletMetadataSpecifics::CARD:
        if (!MergeRemote(remote, card_updater, &cards, &changes_to_sync)) {
          changes_to_sync.push_back(syncer::SyncChange(
              FROM_HERE, syncer::SyncChange::ACTION_DELETE, remote));
        }
        break;

      case sync_pb::WalletMetadataSpecifics::UNKNOWN:
        NOTREACHED();
        break;
    }
  }

  // The remainder of |profiles| were not listed in |sync_data|.
  for (const auto& it : profiles) {
    changes_to_sync.push_back(syncer::SyncChange(
        FROM_HERE, syncer::SyncChange::ACTION_ADD,
        BuildSyncData(sync_pb::WalletMetadataSpecifics::ADDRESS, it.first,
                      *it.second)));
  }

  // The remainder of |cards| were not listed in |sync_data|.
  for (const auto& it : cards) {
    changes_to_sync.push_back(
        syncer::SyncChange(FROM_HERE, syncer::SyncChange::ACTION_ADD,
                           BuildSyncData(sync_pb::WalletMetadataSpecifics::CARD,
                                         it.first, *it.second)));
  }

  // The only operation that is performed locally in response to a sync is an
  // update. Adds and deletes are performed in response to changes to the Wallet
  // data.
  result.set_num_items_after_association(result.num_items_before_association());
  result.set_num_items_added(0);
  result.set_num_items_deleted(0);

  if (!changes_to_sync.empty())
    result.set_error(SendChangesToSyncServer(changes_to_sync));

  return result;
}

template <class DataType>
void AutofillWalletMetadataSyncableService::AutofillDataModelChanged(
    const std::string& server_id,
    const sync_pb::WalletMetadataSpecifics::Type& type,
    const DataType& local) {
  auto it = FindServerIdAndTypeInCache(server_id, type, &cache_);
  if (it == cache_.end())
    return;

  const sync_pb::WalletMetadataSpecifics& remote =
      it->GetSpecifics().wallet_metadata();

  if (base::checked_cast<size_t>(remote.use_count()) < local.use_count() &&
      base::Time::FromInternalValue(remote.use_date()) < local.use_date()) {
    SendChangesToSyncServer(syncer::SyncChangeList(
        1, syncer::SyncChange(FROM_HERE, syncer::SyncChange::ACTION_UPDATE,
                              BuildSyncData(remote.type(), server_id, local))));
  }
}

}  // namespace autofill
