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

#include "chrome/browser/sync/test/integration/wallet_helper.h"

#include <stddef.h>

#include "base/functional/bind.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "chrome/browser/autofill/personal_data_manager_factory.h"
#include "chrome/browser/sync/test/integration/sync_datatype_helper.h"
#include "chrome/browser/sync/test/integration/sync_test.h"
#include "chrome/browser/webdata_services/web_data_service_factory.h"
#include "components/autofill/core/browser/data_manager/payments/payments_data_manager.h"
#include "components/autofill/core/browser/data_manager/personal_data_manager.h"
#include "components/autofill/core/browser/data_model/payments/payments_metadata.h"
#include "components/autofill/core/browser/data_quality/autofill_data_util.h"
#include "components/autofill/core/browser/payments/payments_customer_data.h"
#include "components/autofill/core/browser/webdata/autofill_sync_metadata_table.h"
#include "components/autofill/core/browser/webdata/payments/payments_autofill_table.h"
#include "components/autofill/core/common/credit_card_network_identifiers.h"
#include "components/keyed_service/core/service_access_type.h"
#include "components/sync/engine/loopback_server/persistent_unique_client_entity.h"
#include "components/sync/model/metadata_batch.h"
#include "components/sync/protocol/autofill_wallet_credential_specifics.pb.h"
#include "components/sync/protocol/data_type_progress_marker.pb.h"
#include "components/sync/protocol/data_type_state.pb.h"
#include "components/sync/test/fake_server.h"

using autofill::AutofillWebDataService;
using autofill::CreditCard;
using autofill::CreditCardCloudTokenData;
using autofill::PaymentsAutofillTable;
using autofill::PaymentsCustomerData;
using autofill::PaymentsDataManager;
using autofill::PaymentsMetadata;
using autofill::PersonalDataManager;
using autofill::ServerCvc;
using autofill::data_util::TruncateUTF8;
using sync_datatype_helper::test;

namespace {

// Constants for the credit card.
const int kDefaultCardExpMonth = 8;
const int kDefaultCardExpYear = 2087;
const std::u16string kDefaultCardCvc = u"098";
const int kDefaultCardInstrumentId = 1;
const char kDefaultCardLastFour[] = "1234";
const char16_t kDefaultCardLastFour16[] = u"1234";
const char kDefaultCardName[] = "Patrick Valenzuela";
const char16_t kDefaultCardName16[] = u"Patrick Valenzuela";
const sync_pb::WalletMaskedCreditCard_WalletCardType kDefaultCardType =
    sync_pb::WalletMaskedCreditCard::AMEX;

template <class Item>
bool ListsMatch(int profile_a,
                const std::vector<Item*>& list_a,
                int profile_b,
                const std::vector<Item*>& list_b) {
  std::map<std::string, const Item*> list_a_map;
  for (const Item* item : list_a) {
    list_a_map[item->server_id()] = item;
  }

  // This seems to be a transient state that will eventually be rectified by
  // data type logic. We don't need to check b for duplicates directly because
  // after the first is erased from |autofill_profiles_a_map| the second will
  // not be found.
  if (list_a.size() != list_a_map.size()) {
    DVLOG(1) << "Profile " << profile_a << " contains duplicate server_ids.";
    return false;
  }

  for (const Item* item : list_b) {
    if (!list_a_map.count(item->server_id())) {
      DVLOG(1) << "GUID " << item->server_id() << " not found in profile "
               << profile_b << ".";
      return false;
    }
    const Item* expected_item = list_a_map[item->server_id()];
    if (expected_item->Compare(*item) != 0 ||
        expected_item->usage_history().use_count() !=
            item->usage_history().use_count() ||
        expected_item->usage_history().use_date() !=
            item->usage_history().use_date()) {
      DVLOG(1) << "Mismatch in profile with server_id " << item->server_id()
               << ".";
      return false;
    }
    list_a_map.erase(item->server_id());
  }

  if (!list_a_map.empty()) {
    DVLOG(1) << "Entries present in profile " << profile_a
             << " but not in profile" << profile_b << ".";
    return false;
  }
  return true;
}

template <class Item>
void LogLists(const std::vector<Item*>& list_a,
              const std::vector<Item*>& list_b) {
  int x = 0;
  for (Item* item : list_a) {
    DVLOG(1) << "A#" << x++ << " " << *item;
  }
  x = 0;
  for (Item* item : list_b) {
    DVLOG(1) << "B#" << x++ << " " << *item;
  }
}

template <class Item, base::RawPtrTraits Traits = base::RawPtrTraits::kEmpty>
void LogLists(const std::vector<raw_ptr<Item, Traits>>& list_a,
              const std::vector<raw_ptr<Item, Traits>>& list_b) {
  int x = 0;
  for (Item* item : list_a) {
    DVLOG(1) << "A#" << x++ << " " << *item;
  }
  x = 0;
  for (Item* item : list_b) {
    DVLOG(1) << "B#" << x++ << " " << *item;
  }
}

template <class Item, base::RawPtrTraits Traits = base::RawPtrTraits::kEmpty>
bool ListsMatch(int profile_a,
                const std::vector<raw_ptr<Item, Traits>>& list_a,
                int profile_b,
                const std::vector<raw_ptr<Item, Traits>>& list_b) {
  std::map<std::string, Item> list_a_map;
  for (Item* item : list_a) {
    list_a_map[item->server_id()] = *item;
  }

  // This seems to be a transient state that will eventually be rectified by
  // data type logic. We don't need to check b for duplicates directly because
  // after the first is erased from |autofill_profiles_a_map| the second will
  // not be found.
  if (list_a.size() != list_a_map.size()) {
    DVLOG(1) << "Profile " << profile_a << " contains duplicate server_ids.";
    return false;
  }

  for (Item* item : list_b) {
    if (!list_a_map.count(item->server_id())) {
      DVLOG(1) << "GUID " << item->server_id() << " not found in profile "
               << profile_b << ".";
      return false;
    }
    Item* expected_item = &list_a_map[item->server_id()];
    if (expected_item->Compare(*item) != 0 ||
        expected_item->use_count() != item->use_count() ||
        expected_item->use_date() != item->use_date()) {
      DVLOG(1) << "Mismatch in profile with server_id " << item->server_id()
               << ".";
      return false;
    }
    list_a_map.erase(item->server_id());
  }

  if (!list_a_map.empty()) {
    DVLOG(1) << "Entries present in profile " << profile_a
             << " but not in profile" << profile_b << ".";
    return false;
  }
  return true;
}

bool WalletDataAndMetadataMatch(
    int profile_a,
    const std::vector<const CreditCard*>& server_cards_a,
    int profile_b,
    const std::vector<const CreditCard*>& server_cards_b) {
  if (!ListsMatch(profile_a, server_cards_a, profile_b, server_cards_b)) {
    LogLists(server_cards_a, server_cards_b);
    return false;
  }
  return true;
}

void WaitForCurrentTasksToComplete(
    scoped_refptr<base::SequencedTaskRunner> task_runner) {
  // We are fine with the UI thread getting blocked. If using RunLoop here, in
  // some uses of this functions, we would get nested RunLoops that tend to
  // cause troubles. This is a more robust solution.
  base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL,
                            base::WaitableEvent::InitialState::NOT_SIGNALED);
  task_runner->PostTask(FROM_HERE, base::BindOnce(&base::WaitableEvent::Signal,
                                                  base::Unretained(&event)));
  event.Wait();
}

void WaitForPDMToRefresh(int profile) {
  PersonalDataManager* pdm = wallet_helper::GetPersonalDataManager(profile);
  // Get the latest values from the DB: (1) post tasks on the DB sequence; (2)
  // wait for the DB sequence tasks to finish; (3) wait for the UI sequence
  // tasks to finish (that update the in-memory cache in PDM).
  pdm->Refresh();
  WaitForCurrentTasksToComplete(
      wallet_helper::GetProfileWebDataService(profile)->GetDBTaskRunner());
  WaitForCurrentTasksToComplete(
      wallet_helper::GetAccountWebDataService(profile)->GetDBTaskRunner());
  base::RunLoop().RunUntilIdle();
}

void SetServerCardsOnDBSequence(AutofillWebDataService* wds,
                                const std::vector<CreditCard>& credit_cards) {
  DCHECK(wds->GetDBTaskRunner()->RunsTasksInCurrentSequence());
  PaymentsAutofillTable::FromWebDatabase(wds->GetDatabase())
      ->SetServerCreditCards(credit_cards);
}

void SetPaymentsCustomerDataOnDBSequence(
    AutofillWebDataService* wds,
    const PaymentsCustomerData& customer_data) {
  DCHECK(wds->GetDBTaskRunner()->RunsTasksInCurrentSequence());
  PaymentsAutofillTable::FromWebDatabase(wds->GetDatabase())
      ->SetPaymentsCustomerData(&customer_data);
}

void SetCreditCardCloudTokenDataOnDBSequence(
    AutofillWebDataService* wds,
    const std::vector<CreditCardCloudTokenData>& cloud_token_data) {
  DCHECK(wds->GetDBTaskRunner()->RunsTasksInCurrentSequence());
  PaymentsAutofillTable::FromWebDatabase(wds->GetDatabase())
      ->SetCreditCardCloudTokenData(cloud_token_data);
}

void GetServerCardsMetadataOnDBSequence(
    AutofillWebDataService* wds,
    std::vector<PaymentsMetadata>* cards_metadata) {
  DCHECK(wds->GetDBTaskRunner()->RunsTasksInCurrentSequence());
  PaymentsAutofillTable::FromWebDatabase(wds->GetDatabase())
      ->GetServerCardsMetadata(*cards_metadata);
}

void GetDataTypeStateOnDBSequence(syncer::DataType data_type,
                                  AutofillWebDataService* wds,
                                  sync_pb::DataTypeState* data_type_state) {
  DCHECK(wds->GetDBTaskRunner()->RunsTasksInCurrentSequence());
  syncer::MetadataBatch metadata_batch;
  autofill::AutofillSyncMetadataTable::FromWebDatabase(wds->GetDatabase())
      ->GetAllSyncMetadata(data_type, &metadata_batch);
  *data_type_state = metadata_batch.GetDataTypeState();
}

}  // namespace

namespace wallet_helper {

PaymentsDataManager* GetPaymentsDataManager(int index) {
  PersonalDataManager* pdm = GetPersonalDataManager(index);
  return pdm ? &pdm->payments_data_manager() : nullptr;
}

PersonalDataManager* GetPersonalDataManager(int index) {
  return autofill::PersonalDataManagerFactory::GetForBrowserContext(
      test()->GetProfile(index));
}

scoped_refptr<AutofillWebDataService> GetProfileWebDataService(int index) {
  return WebDataServiceFactory::GetAutofillWebDataForProfile(
      test()->GetProfile(index), ServiceAccessType::EXPLICIT_ACCESS);
}

scoped_refptr<AutofillWebDataService> GetAccountWebDataService(int index) {
  return WebDataServiceFactory::GetAutofillWebDataForAccount(
      test()->GetProfile(index), ServiceAccessType::EXPLICIT_ACCESS);
}

scoped_refptr<AutofillWebDataService> GetWebDataService(int profile,
                                                        StoreType store_type) {
  return (store_type == StoreType::kAccountStore)
             ? GetAccountWebDataService(profile)
             : GetProfileWebDataService(profile);
}

void SetServerCreditCards(int profile,
                          const std::vector<autofill::CreditCard>& credit_cards,
                          StoreType store_type) {
  scoped_refptr<AutofillWebDataService> wds =
      GetWebDataService(profile, store_type);
  wds->GetDBTaskRunner()->PostTask(
      FROM_HERE, base::BindOnce(&SetServerCardsOnDBSequence,
                                base::Unretained(wds.get()), credit_cards));
  WaitForCurrentTasksToComplete(wds->GetDBTaskRunner());
}

void SetPaymentsCustomerData(
    int profile,
    const autofill::PaymentsCustomerData& customer_data,
    StoreType store_type) {
  scoped_refptr<AutofillWebDataService> wds =
      GetWebDataService(profile, store_type);
  wds->GetDBTaskRunner()->PostTask(
      FROM_HERE, base::BindOnce(&SetPaymentsCustomerDataOnDBSequence,
                                base::Unretained(wds.get()), customer_data));
  WaitForCurrentTasksToComplete(wds->GetDBTaskRunner());
}

void SetCreditCardCloudTokenData(
    int profile,
    const std::vector<autofill::CreditCardCloudTokenData>& cloud_token_data,
    StoreType store_type) {
  scoped_refptr<AutofillWebDataService> wds =
      GetWebDataService(profile, store_type);
  wds->GetDBTaskRunner()->PostTask(
      FROM_HERE, base::BindOnce(&SetCreditCardCloudTokenDataOnDBSequence,
                                base::Unretained(wds.get()), cloud_token_data));
}

void SetServerCardCredentialData(int profile,
                                 const CreditCard& credit_card,
                                 StoreType store_type) {
  scoped_refptr<AutofillWebDataService> wds =
      GetWebDataService(profile, store_type);
  wds->AddServerCvc(credit_card.instrument_id(), credit_card.cvc());
  WaitForCurrentTasksToComplete(wds->GetDBTaskRunner());
}

void RemoveServerCardCredentialData(int profile,
                                    const CreditCard& credit_card,
                                    StoreType store_type) {
  scoped_refptr<AutofillWebDataService> wds =
      GetWebDataService(profile, store_type);
  wds->RemoveServerCvc(credit_card.instrument_id());
  WaitForCurrentTasksToComplete(wds->GetDBTaskRunner());
}

void UpdateServerCardCredentialData(int profile,
                                    const CreditCard& credit_card,
                                    StoreType store_type) {
  scoped_refptr<AutofillWebDataService> wds =
      GetWebDataService(profile, store_type);
  wds->UpdateServerCvc(credit_card.instrument_id(), credit_card.cvc());
  WaitForCurrentTasksToComplete(wds->GetDBTaskRunner());
}

void UpdateServerCardMetadata(int profile,
                              const CreditCard& credit_card,
                              StoreType store_type) {
  scoped_refptr<AutofillWebDataService> wds =
      GetWebDataService(profile, store_type);
  wds->UpdateServerCardMetadata(credit_card);
  WaitForCurrentTasksToComplete(wds->GetDBTaskRunner());
}

std::vector<PaymentsMetadata> GetServerCardsMetadata(int profile,
                                                     StoreType store_type) {
  std::vector<PaymentsMetadata> cards_metadata;
  scoped_refptr<AutofillWebDataService> wds =
      GetWebDataService(profile, store_type);
  wds->GetDBTaskRunner()->PostTask(
      FROM_HERE, base::BindOnce(&GetServerCardsMetadataOnDBSequence,
                                base::Unretained(wds.get()), &cards_metadata));
  WaitForCurrentTasksToComplete(wds->GetDBTaskRunner());
  return cards_metadata;
}

sync_pb::DataTypeState GetWalletDataTypeState(syncer::DataType data_type,
                                              int profile,
                                              StoreType store_type) {
  DCHECK(data_type == syncer::AUTOFILL_WALLET_DATA ||
         data_type == syncer::AUTOFILL_WALLET_OFFER);
  sync_pb::DataTypeState result;
  scoped_refptr<AutofillWebDataService> wds =
      GetWebDataService(profile, store_type);
  wds->GetDBTaskRunner()->PostTask(
      FROM_HERE, base::BindOnce(&GetDataTypeStateOnDBSequence, data_type,
                                base::Unretained(wds.get()), &result));
  WaitForCurrentTasksToComplete(wds->GetDBTaskRunner());
  return result;
}

void SetWalletData(fake_server::FakeServer* fake_server,
                   const std::vector<sync_pb::SyncEntity>& wallet_entities) {
  fake_server->DeleteAllEntitiesForDataType(syncer::AUTOFILL_WALLET_DATA);
  for (const sync_pb::SyncEntity& entity : wallet_entities) {
    fake_server->InjectEntity(
        syncer::PersistentUniqueClientEntity::CreateFromSpecificsForTesting(
            /*non_unique_name=*/entity.name(),
            /*client_tag=*/entity.id_string(), entity.specifics(),
            /*creation_time=*/entity.ctime(),
            /*last_modified_time=*/entity.mtime()));
  }
}

sync_pb::SyncEntity CreateDefaultSyncWalletCard() {
  return CreateSyncWalletCard(kDefaultCardID, kDefaultCardLastFour,
                              kDefaultBillingAddressID);
}

sync_pb::SyncEntity CreateSyncWalletCard(const std::string& name,
                                         const std::string& last_four,
                                         const std::string& billing_address_id,
                                         const std::string& nickname,
                                         int64_t instrument_id) {
  sync_pb::SyncEntity entity;
  entity.set_name(name);
  entity.set_id_string(name);
  entity.set_version(0);  // Will be overridden by the fake server.
  entity.set_ctime(12345);
  entity.set_mtime(12345);
  sync_pb::AutofillWalletSpecifics* wallet_specifics =
      entity.mutable_specifics()->mutable_autofill_wallet();
  wallet_specifics->set_type(
      sync_pb::AutofillWalletSpecifics::MASKED_CREDIT_CARD);

  sync_pb::WalletMaskedCreditCard* credit_card =
      wallet_specifics->mutable_masked_card();
  credit_card->set_id(name);
  credit_card->set_exp_month(kDefaultCardExpMonth);
  credit_card->set_exp_year(kDefaultCardExpYear);
  credit_card->set_last_four(last_four);
  credit_card->set_name_on_card(kDefaultCardName);
  credit_card->set_type(kDefaultCardType);
  credit_card->set_instrument_id(instrument_id);
  if (!billing_address_id.empty()) {
    credit_card->set_billing_address_id(billing_address_id);
  }
  if (!nickname.empty()) {
    credit_card->set_nickname(nickname);
  }
  return entity;
}

sync_pb::SyncEntity CreateSyncPaymentsCustomerData(
    const std::string& customer_id) {
  sync_pb::SyncEntity entity;
  entity.set_name(customer_id);
  entity.set_id_string(customer_id);
  entity.set_version(0);  // Will be overridden by the fake server.
  entity.set_ctime(12345);
  entity.set_mtime(12345);
  sync_pb::AutofillWalletSpecifics* wallet_specifics =
      entity.mutable_specifics()->mutable_autofill_wallet();
  wallet_specifics->set_type(sync_pb::AutofillWalletSpecifics::CUSTOMER_DATA);

  sync_pb::PaymentsCustomerData* customer_data =
      wallet_specifics->mutable_customer_data();
  customer_data->set_id(customer_id);
  return entity;
}

sync_pb::SyncEntity CreateDefaultSyncPaymentsCustomerData() {
  return CreateSyncPaymentsCustomerData(kDefaultCustomerID);
}

CreditCard GetDefaultCreditCard() {
  CreditCard card(CreditCard::RecordType::kMaskedServerCard, kDefaultCardID);
  card.SetExpirationMonth(kDefaultCardExpMonth);
  card.SetExpirationYear(kDefaultCardExpYear);
  card.SetNumber(kDefaultCardLastFour16);
  card.SetRawInfo(autofill::CREDIT_CARD_NAME_FULL, kDefaultCardName16);
  card.SetNetworkForMaskedCard(autofill::kAmericanExpressCard);
  card.set_billing_address_id(kDefaultBillingAddressID);
  return card;
}

sync_pb::SyncEntity CreateSyncCreditCardCloudTokenData(
    const std::string& cloud_token_data_id) {
  sync_pb::SyncEntity entity;
  entity.set_name(cloud_token_data_id);
  entity.set_id_string(cloud_token_data_id);
  entity.set_version(0);  // Will be overridden by the fake server.
  entity.set_ctime(12345);
  entity.set_mtime(12345);
  sync_pb::AutofillWalletSpecifics* wallet_specifics =
      entity.mutable_specifics()->mutable_autofill_wallet();
  wallet_specifics->set_type(
      sync_pb::AutofillWalletSpecifics::CREDIT_CARD_CLOUD_TOKEN_DATA);
  sync_pb::WalletCreditCardCloudTokenData* cloud_token_data =
      wallet_specifics->mutable_cloud_token_data();
  cloud_token_data->set_instrument_token(cloud_token_data_id);
  return entity;
}

sync_pb::SyncEntity CreateDefaultSyncCreditCardCloudTokenData() {
  return CreateSyncCreditCardCloudTokenData(kDefaultCreditCardCloudTokenDataID);
}

sync_pb::SyncEntity CreateDefaultSyncWalletCredential() {
  return CreateSyncWalletCredential(
      ServerCvc{kDefaultCardInstrumentId, kDefaultCardCvc,
                /*last_updated_timestamp=*/base::Time::UnixEpoch() +
                    base::Milliseconds(25000)});
}

sync_pb::SyncEntity CreateSyncWalletCredential(const ServerCvc& server_cvc) {
  sync_pb::SyncEntity entity;
  entity.set_name(base::NumberToString(server_cvc.instrument_id));
  entity.set_id_string(base::NumberToString(server_cvc.instrument_id));
  entity.set_version(0);  // Will be overridden by the fake server.
  entity.set_ctime(12345);
  entity.set_mtime(12345);

  sync_pb::AutofillWalletCredentialSpecifics* wallet_credential_specifics =
      entity.mutable_specifics()->mutable_autofill_wallet_credential();

  wallet_credential_specifics->set_instrument_id(
      base::NumberToString(server_cvc.instrument_id));
  wallet_credential_specifics->set_cvc(base::UTF16ToUTF8(server_cvc.cvc));
  wallet_credential_specifics->set_last_updated_time_unix_epoch_millis(
      (server_cvc.last_updated_timestamp - base::Time::UnixEpoch())
          .InMilliseconds());

  return entity;
}

void ExpectDefaultCreditCardValues(const CreditCard& card) {
  EXPECT_EQ(CreditCard::RecordType::kMaskedServerCard, card.record_type());
  EXPECT_EQ(kDefaultCardID, card.server_id());
  EXPECT_EQ(kDefaultCardLastFour16, card.LastFourDigits());
  EXPECT_EQ(autofill::kAmericanExpressCard, card.network());
  EXPECT_EQ(kDefaultCardExpMonth, card.expiration_month());
  EXPECT_EQ(kDefaultCardExpYear, card.expiration_year());
  EXPECT_EQ(kDefaultCardName16,
            card.GetRawInfo(autofill::FieldType::CREDIT_CARD_NAME_FULL));
  EXPECT_EQ(kDefaultBillingAddressID, card.billing_address_id());
}

void ExpectDefaultWalletCredentialValues(const CreditCard& card) {
  EXPECT_EQ(kDefaultCardInstrumentId, card.instrument_id());
  EXPECT_EQ(kDefaultCardCvc, card.cvc());
}

std::vector<const CreditCard*> GetServerCreditCards(int profile) {
  WaitForPDMToRefresh(profile);
  PersonalDataManager* pdm = GetPersonalDataManager(profile);
  return pdm->payments_data_manager().GetServerCreditCards();
}

}  // namespace wallet_helper

AutofillWalletChecker::AutofillWalletChecker(int profile_a, int profile_b)
    : profile_a_(profile_a), profile_b_(profile_b) {
  wallet_helper::GetPaymentsDataManager(profile_a_)->AddObserver(this);
  wallet_helper::GetPaymentsDataManager(profile_b_)->AddObserver(this);
}

AutofillWalletChecker::~AutofillWalletChecker() {
  wallet_helper::GetPaymentsDataManager(profile_a_)->RemoveObserver(this);
  wallet_helper::GetPaymentsDataManager(profile_b_)->RemoveObserver(this);
}

void AutofillWalletChecker::WillStartWaiting() {
  // We need to make sure we are not reading before any locally instigated async
  // writes. This is run exactly one time before the first
  // IsExitConditionSatisfied() is called.
  WaitForPDMToRefresh(profile_a_);
  WaitForPDMToRefresh(profile_b_);
}

bool AutofillWalletChecker::IsExitConditionSatisfied(std::ostream* os) {
  *os << "Waiting for matching autofill wallet cards and addresses";
  autofill::PaymentsDataManager* paydm_a =
      wallet_helper::GetPaymentsDataManager(profile_a_);
  autofill::PaymentsDataManager* paydm_b =
      wallet_helper::GetPaymentsDataManager(profile_b_);
  return WalletDataAndMetadataMatch(profile_a_, paydm_a->GetServerCreditCards(),
                                    profile_b_,
                                    paydm_b->GetServerCreditCards());
}

void AutofillWalletChecker::OnPaymentsDataChanged() {
  CheckExitCondition();
}

FullUpdateTypeProgressMarkerChecker::FullUpdateTypeProgressMarkerChecker(
    base::Time min_required_progress_marker_timestamp,
    syncer::SyncService* service,
    syncer::DataType data_type)
    : min_required_progress_marker_timestamp_(
          min_required_progress_marker_timestamp),
      service_(service),
      data_type_(data_type) {
  scoped_observation_.Observe(service);

  const syncer::SyncCycleSnapshot snap =
      service_->GetLastCycleSnapshotForDebugging();
  const syncer::ProgressMarkerMap& progress_markers =
      snap.download_progress_markers();
  auto marker_it = progress_markers.find(data_type_);
  if (marker_it != progress_markers.end()) {
    sync_pb::DataTypeProgressMarker progress_marker;
    if (progress_marker.ParseFromString(marker_it->second)) {
      initial_token_ = progress_marker.token();
    }
  }
}

FullUpdateTypeProgressMarkerChecker::~FullUpdateTypeProgressMarkerChecker() =
    default;

bool FullUpdateTypeProgressMarkerChecker::IsExitConditionSatisfied(
    std::ostream* os) {
  // GetLastCycleSnapshot() returns by value, so make sure to capture it for
  // iterator use.
  const syncer::SyncCycleSnapshot snap =
      service_->GetLastCycleSnapshotForDebugging();
  const syncer::ProgressMarkerMap& progress_markers =
      snap.download_progress_markers();
  auto marker_it = progress_markers.find(data_type_);
  if (marker_it == progress_markers.end()) {
    *os << "Waiting for an updated progress marker timestamp "
        << min_required_progress_marker_timestamp_
        << "; actual: no progress marker in last sync cycle";
    return false;
  }

  sync_pb::DataTypeProgressMarker progress_marker;
  bool success = progress_marker.ParseFromString(marker_it->second);
  DCHECK(success);

  // If the token string has changed since this checker was created, a new
  // progress marker has arrived.
  if (initial_token_.has_value() &&
      progress_marker.token() != *initial_token_) {
    return true;
  }

  *os << "Waiting for an updated progress marker token (initial: "
      << initial_token_.value_or("") << ", current: " << progress_marker.token()
      << ")";

  return false;
}

// syncer::SyncServiceObserver implementation.
void FullUpdateTypeProgressMarkerChecker::OnSyncCycleCompleted(
    syncer::SyncService* sync) {
  CheckExitCondition();
}

void FullUpdateTypeProgressMarkerChecker::OnSyncShutdown(
    syncer::SyncService* sync) {
  NOTREACHED();
}
