// Copyright 2023 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/webauthn/core/browser/passkey_sync_bridge.h"

#include <algorithm>
#include <iterator>
#include <memory>
#include <numeric>
#include <optional>
#include <string>
#include <variant>

#include "base/containers/flat_set.h"
#include "base/containers/flat_tree.h"
#include "base/containers/span.h"
#include "base/feature_list.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "base/time/clock.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "components/sync/base/data_type.h"
#include "components/sync/base/deletion_origin.h"
#include "components/sync/model/client_tag_based_data_type_processor.h"
#include "components/sync/model/data_type_controller_delegate.h"
#include "components/sync/model/data_type_store.h"
#include "components/sync/model/entity_change.h"
#include "components/sync/model/metadata_change_list.h"
#include "components/sync/model/mutable_data_batch.h"
#include "components/sync/protocol/webauthn_credential_specifics.pb.h"
#include "components/webauthn/core/browser/passkey_model.h"
#include "components/webauthn/core/browser/passkey_model_change.h"
#include "components/webauthn/core/browser/passkey_model_utils.h"
#include "components/webauthn/features.h"

namespace webauthn {
namespace {

std::unique_ptr<syncer::EntityData> CreateEntityData(
    const sync_pb::WebauthnCredentialSpecifics& specifics) {
  auto entity_data = std::make_unique<syncer::EntityData>();
  // Name must be UTF-8 decodable.
  entity_data->name = base::HexEncode(base::as_byte_span(specifics.sync_id()));
  *entity_data->specifics.mutable_webauthn_credential() = specifics;
  return entity_data;
}

std::optional<std::string> FindHeadOfShadowChain(
    const std::map<std::string, sync_pb::WebauthnCredentialSpecifics>& passkeys,
    const std::string& rp_id,
    const std::string& user_id) {
  // Collect all credentials for the user.id, rpid pair.
  std::vector<sync_pb::WebauthnCredentialSpecifics> rpid_passkeys;
  for (const auto& passkey : passkeys) {
    if (passkey.second.user_id() == user_id &&
        passkey.second.rp_id() == rp_id) {
      rpid_passkeys.emplace_back(passkey.second);
    }
  }
  // Filter the shadowed credentials.
  std::vector<sync_pb::WebauthnCredentialSpecifics> filtered =
      passkey_model_utils::FilterShadowedCredentials(rpid_passkeys);
  CHECK_LE(filtered.size(), 1u);
  return filtered.empty() ? std::nullopt
                          : std::make_optional(filtered.at(0).sync_id());
}

PasskeyModelChange::ChangeType ToPasskeyModelChangeType(
    syncer::EntityChange::ChangeType entity_change) {
  switch (entity_change) {
    case syncer::EntityChange::ACTION_ADD:
      return PasskeyModelChange::ChangeType::ADD;
    case syncer::EntityChange::ACTION_UPDATE:
      return PasskeyModelChange::ChangeType::UPDATE;
    case syncer::EntityChange::ACTION_DELETE:
      return PasskeyModelChange::ChangeType::REMOVE;
  }
}

}  // namespace

PasskeySyncBridge::PasskeySyncBridge(
    syncer::OnceDataTypeStoreFactory store_factory)
    : syncer::DataTypeSyncBridge(
          std::make_unique<syncer::ClientTagBasedDataTypeProcessor>(
              syncer::WEBAUTHN_CREDENTIAL,
              /*dump_stack=*/base::DoNothing())) {
  std::move(store_factory)
      .Run(syncer::WEBAUTHN_CREDENTIAL,
           base::BindOnce(&PasskeySyncBridge::OnCreateStore,
                          weak_ptr_factory_.GetWeakPtr()));
}

PasskeySyncBridge::~PasskeySyncBridge() {
  for (auto& observer : observers_) {
    observer.OnPasskeyModelShuttingDown();
  }
}

void PasskeySyncBridge::AddObserver(Observer* observer) {
  observers_.AddObserver(observer);
}

void PasskeySyncBridge::RemoveObserver(Observer* observer) {
  observers_.RemoveObserver(observer);
}

std::optional<syncer::ModelError> PasskeySyncBridge::MergeFullSyncData(
    std::unique_ptr<syncer::MetadataChangeList> metadata_changes,
    syncer::EntityChangeList entity_changes) {
  CHECK(std::ranges::all_of(entity_changes, [](const auto& change) {
    return change->type() == syncer::EntityChange::ACTION_ADD;
  }));

  // Google Password Manager passkeys are disabled when Sync is disabled so it
  // shouldn't be the case that there are any local entities when Sync starts.
  // But it can happen in corner cases. This code uploads any such entities to
  // the server. The string_views in `local_only_sync_ids` reference std::string
  // keys in `data_` and so remain valid until they are consumed below.
  auto local_only_sync_ids = base::MakeFlatSet<std::string_view>(
      data_, /*comp=*/{},
      [](const auto& it) { return std::string_view(it.first); });
  for (const auto& change : entity_changes) {
    local_only_sync_ids.erase(change->storage_key());
  }
  for (const auto& local_only_sync_id : local_only_sync_ids) {
    std::string sync_id(local_only_sync_id);
    change_processor()->Put(sync_id, CreateEntityData(data_.at(sync_id)),
                            metadata_changes.get());
  }

  return ApplyIncrementalSyncChanges(std::move(metadata_changes),
                                     std::move(entity_changes));
}

std::optional<syncer::ModelError>
PasskeySyncBridge::ApplyIncrementalSyncChanges(
    std::unique_ptr<syncer::MetadataChangeList> metadata_change_list,
    syncer::EntityChangeList entity_changes) {
  std::unique_ptr<syncer::DataTypeStore::WriteBatch> write_batch =
      store_->CreateWriteBatch(std::move(metadata_change_list));

  std::vector<PasskeyModelChange> changes;
  for (const auto& entity_change : entity_changes) {
    PasskeyModelChange::ChangeType change_type =
        ToPasskeyModelChangeType(entity_change->type());
    switch (entity_change->type()) {
      case syncer::EntityChange::ACTION_DELETE: {
        const auto passkey_it = data_.find(entity_change->storage_key());
        if (passkey_it != data_.end()) {
          changes.emplace_back(change_type, passkey_it->second);
          data_.erase(passkey_it);
        } else {
          DVLOG(1) << "Downloaded deletion for passkey not present locally";
        }
        write_batch->DeleteData(entity_change->storage_key());
        break;
      }
      case syncer::EntityChange::ACTION_ADD:
      case syncer::EntityChange::ACTION_UPDATE: {
        // No merging is done and remote changes override local changes.
        const sync_pb::WebauthnCredentialSpecifics& specifics =
            entity_change->data().specifics.webauthn_credential();
        changes.emplace_back(change_type, specifics);
        data_[entity_change->storage_key()] = specifics;
        write_batch->WriteData(entity_change->storage_key(),
                               specifics.SerializeAsString());
        break;
      }
    }
  }

  store_->CommitWriteBatch(
      std::move(write_batch),
      base::BindOnce(&PasskeySyncBridge::OnStoreCommitWriteBatch,
                     weak_ptr_factory_.GetWeakPtr()));
  if (!entity_changes.empty()) {
    NotifyPasskeysChanged(std::move(changes));
  }
  return std::nullopt;
}

std::unique_ptr<syncer::DataBatch> PasskeySyncBridge::GetDataForCommit(
    StorageKeyList storage_keys) {
  auto batch = std::make_unique<syncer::MutableDataBatch>();
  for (const std::string& sync_id : storage_keys) {
    if (auto it = data_.find(sync_id); it != data_.end()) {
      batch->Put(sync_id, CreateEntityData(it->second));
    }
  }
  return batch;
}

std::unique_ptr<syncer::DataBatch> PasskeySyncBridge::GetAllDataForDebugging() {
  auto batch = std::make_unique<syncer::MutableDataBatch>();
  for (const auto& [sync_id, specifics] : data_) {
    batch->Put(sync_id, CreateEntityData(specifics));
  }
  return batch;
}

sync_pb::EntitySpecifics
PasskeySyncBridge::TrimAllSupportedFieldsFromRemoteSpecifics(
    const sync_pb::EntitySpecifics& entity_specifics) const {
  // Clears all fields by default to avoid the memory and I/O overhead of an
  // additional copy of the data.
  return sync_pb::EntitySpecifics();
}

bool PasskeySyncBridge::IsEntityDataValid(
    const syncer::EntityData& entity_data) const {
  return passkey_model_utils::IsPasskeyValid(
      entity_data.specifics.webauthn_credential());
}

std::string PasskeySyncBridge::GetClientTag(
    const syncer::EntityData& entity_data) const {
  return GetStorageKey(entity_data);
}

std::string PasskeySyncBridge::GetStorageKey(
    const syncer::EntityData& entity_data) const {
  DCHECK(entity_data.specifics.has_webauthn_credential());
  return entity_data.specifics.webauthn_credential().sync_id();
}

void PasskeySyncBridge::ApplyDisableSyncChanges(
    std::unique_ptr<syncer::MetadataChangeList> delete_metadata_change_list) {
  CHECK(store_);
  store_->DeleteAllDataAndMetadata(std::move(delete_metadata_change_list),
                                   base::DoNothing());
  std::vector<PasskeyModelChange> changes;
  for (const auto& passkey : data_) {
    changes.emplace_back(PasskeyModelChange::ChangeType::REMOVE,
                         passkey.second);
  }
  data_.clear();
  NotifyPasskeysChanged(std::move(changes));
}

base::WeakPtr<syncer::DataTypeControllerDelegate>
PasskeySyncBridge::GetDataTypeControllerDelegate() {
  return change_processor()->GetControllerDelegate();
}

bool PasskeySyncBridge::IsReady() const {
  return ready_;
}

bool PasskeySyncBridge::IsEmpty() const {
  return data_.empty();
}

base::flat_set<std::string> PasskeySyncBridge::GetAllSyncIds() const {
  std::vector<std::string> sync_ids;
  std::ranges::transform(data_, std::back_inserter(sync_ids),
                         [](const auto& pair) { return pair.first; });
  return base::flat_set<std::string>(base::sorted_unique, std::move(sync_ids));
}

std::vector<sync_pb::WebauthnCredentialSpecifics>
PasskeySyncBridge::GetPasskeys(std::variant<AnyRp, std::string_view> rp_id,
                               ShadowedCredentials shadowed_credentials) const {
  std::vector<sync_pb::WebauthnCredentialSpecifics> passkeys;

  const std::string_view* specific_rp_id =
      std::get_if<std::string_view>(&rp_id);
  for (const auto& sync_id_and_passkey : data_) {
    const sync_pb::WebauthnCredentialSpecifics& passkey =
        sync_id_and_passkey.second;
    if (!specific_rp_id || passkey.rp_id() == *specific_rp_id) {
      passkeys.emplace_back(passkey);
    }
  }

  if (shadowed_credentials == PasskeyModel::ShadowedCredentials::kExclude) {
    return passkey_model_utils::FilterShadowedCredentials(passkeys);
  }

  return passkeys;
}

std::optional<sync_pb::WebauthnCredentialSpecifics>
PasskeySyncBridge::GetPasskey(std::variant<AnyRp, std::string_view> rp_id,
                              std::string_view credential_id,
                              ShadowedCredentials shadowed_credentials) const {
  for (const sync_pb::WebauthnCredentialSpecifics& passkey :
       GetPasskeys(rp_id, shadowed_credentials)) {
    if (passkey.credential_id() == credential_id) {
      return passkey;
    }
  }

  return std::nullopt;
}

bool PasskeySyncBridge::DeletePasskey(const std::string& credential_id,
                                      const base::Location& location) {
  // Find the credential with the given |credential_id|.
  const auto passkey_it =
      std::ranges::find_if(data_, [&credential_id](const auto& passkey) {
        return passkey.second.credential_id() == credential_id;
      });
  if (passkey_it == data_.end()) {
    DVLOG(1) << "Attempted to delete non existent passkey";
    return false;
  }
  std::string rp_id = passkey_it->second.rp_id();
  std::string user_id = passkey_it->second.user_id();
  std::optional<std::string> shadow_head_sync_id =
      FindHeadOfShadowChain(data_, rp_id, user_id);

  // There must be a head of the shadow chain. Otherwise, something is wrong
  // with the data. Bail out.
  if (!shadow_head_sync_id) {
    DVLOG(1) << "Could not find head of shadow chain";
    return false;
  }

  base::flat_set<std::string> sync_ids_to_delete;
  if (passkey_it->first == *shadow_head_sync_id) {
    // Remove all credentials for the user.id and rpid.
    for (const auto& passkey : data_) {
      if (passkey.second.rp_id() == rp_id &&
          passkey.second.user_id() == user_id) {
        sync_ids_to_delete.emplace(passkey.first);
      }
    }
  } else {
    // Remove only the passed credential.
    sync_ids_to_delete.emplace(passkey_it->first);
  }
  std::unique_ptr<syncer::DataTypeStore::WriteBatch> write_batch =
      store_->CreateWriteBatch();
  std::vector<PasskeyModelChange> changes;
  for (const std::string& sync_id : sync_ids_to_delete) {
    changes.emplace_back(PasskeyModelChange::ChangeType::REMOVE,
                         data_.at(sync_id));
    data_.erase(sync_id);
    change_processor()->Delete(sync_id,
                               syncer::DeletionOrigin::FromLocation(location),
                               write_batch->GetMetadataChangeList());
    write_batch->DeleteData(sync_id);
  }
  store_->CommitWriteBatch(
      std::move(write_batch),
      base::BindOnce(&PasskeySyncBridge::OnStoreCommitWriteBatch,
                     weak_ptr_factory_.GetWeakPtr()));
  NotifyPasskeysChanged(std::move(changes));
  return true;
}

bool PasskeySyncBridge::HidePasskey(const std::string& credential_id,
                                    base::Time hidden_time) {
  return UpdateSinglePasskey(
      credential_id,
      base::BindOnce(
          [](base::Time hidden_time,
             sync_pb::WebauthnCredentialSpecifics* passkey) -> bool {
            passkey->set_hidden(true);
            passkey->set_hidden_time(
                hidden_time.InMillisecondsSinceUnixEpoch());
            return true;
          },
          hidden_time));
}

bool PasskeySyncBridge::UnhidePasskey(const std::string& credential_id) {
  return UpdateSinglePasskey(
      credential_id,
      base::BindOnce([](sync_pb::WebauthnCredentialSpecifics* passkey) -> bool {
        passkey->set_hidden(false);
        passkey->clear_hidden_time();
        return true;
      }));
}

// The following implementation is more efficient than the simple one which
// would iterate over all passkeys and delete them one by one.
// Deleting all passkeys individually would also send out a notification to
// the observers for each individual deletion. This implementation only sends
// out a single notification for all deletions.
// Shadow chains are not handled separately since all passkeys are deleted
// anyway.
void PasskeySyncBridge::DeleteAllPasskeys() {
  CHECK(IsReady());

  std::unique_ptr<syncer::DataTypeStore::WriteBatch> write_batch =
      store_->CreateWriteBatch();
  std::vector<PasskeyModelChange> changes;
  for (const auto& [sync_id, passkey] : data_) {
    changes.emplace_back(PasskeyModelChange::ChangeType::REMOVE,
                         std::move(passkey));
    change_processor()->Delete(sync_id,
                               syncer::DeletionOrigin::FromLocation(FROM_HERE),
                               write_batch->GetMetadataChangeList());
    write_batch->DeleteData(sync_id);
  }
  data_.clear();
  store_->CommitWriteBatch(
      std::move(write_batch),
      base::BindOnce(&PasskeySyncBridge::OnStoreCommitWriteBatch,
                     weak_ptr_factory_.GetWeakPtr()));

  // Sends out only a single notification for all deleted passkeys.
  NotifyPasskeysChanged(std::move(changes));
}

bool PasskeySyncBridge::UpdatePasskey(const std::string& credential_id,
                                      PasskeyUpdate change,
                                      bool updated_by_user) {
  return UpdateSinglePasskey(
      credential_id,
      base::BindOnce(
          [](PasskeyUpdate change, bool updated_by_user,
             sync_pb::WebauthnCredentialSpecifics* passkey) -> bool {
            if (passkey->edited_by_user() && !updated_by_user) {
              // Respect the user's choice and do not change a passkey's user
              // data if explicitly set by the user previously.
              return false;
            }
            passkey->set_edited_by_user(updated_by_user);
            passkey->set_user_name(std::move(change.user_name));
            passkey->set_user_display_name(std::move(change.user_display_name));
            return true;
          },
          std::move(change), updated_by_user));
}

bool PasskeySyncBridge::UpdatePasskeyTimestamp(const std::string& credential_id,
                                               base::Time last_used_time) {
  return UpdateSinglePasskey(
      credential_id,
      base::BindOnce(
          [](base::Time last_used_time,
             sync_pb::WebauthnCredentialSpecifics* passkey) -> bool {
            passkey->set_last_used_time_windows_epoch_micros(
                last_used_time.ToDeltaSinceWindowsEpoch().InMicroseconds());
            return true;
          },
          last_used_time));
}

bool PasskeySyncBridge::UpdatePasskeyEncryptedBlob(
    const std::string& credential_id,
    const std::string& new_encrypted_blob) {
  return UpdateSinglePasskey(
      credential_id,
      base::BindOnce(
          [](const std::string& blob,
             sync_pb::WebauthnCredentialSpecifics* passkey) -> bool {
            passkey->set_encrypted(blob);
            return true;
          },
          new_encrypted_blob));
}

sync_pb::WebauthnCredentialSpecifics PasskeySyncBridge::CreatePasskey(
    std::string_view rp_id,
    const UserEntity& user_entity,
    base::span<const uint8_t> trusted_vault_key,
    int32_t trusted_vault_key_version,
    std::vector<uint8_t>* public_key_spki_der_out) {
  CHECK(IsReady());

  auto [specifics, public_key_spki_der] =
      webauthn::passkey_model_utils::GeneratePasskeyAndEncryptSecrets(
          rp_id, user_entity, trusted_vault_key, trusted_vault_key_version,
          /*extension_input_data=*/{}, /*extension_output_data=*/nullptr);

  AddShadowedCredentialIdsToNewPasskey(specifics);

  AddPasskeyInternal(specifics);

  if (public_key_spki_der_out != nullptr) {
    *public_key_spki_der_out = std::move(public_key_spki_der);
  }
  return specifics;
}

void PasskeySyncBridge::CreatePasskey(
    sync_pb::WebauthnCredentialSpecifics& passkey) {
  // TODO(crbug.com/349547003): make it sure that all the callers check for
  // that. If not, it's still safer to crash in this case to avoid losing the
  // passkey.
  CHECK(IsReady());

  CHECK(passkey_model_utils::IsPasskeyValid(passkey));

  std::string sync_id = passkey.sync_id();
  CHECK(!data_.contains(sync_id));

  AddShadowedCredentialIdsToNewPasskey(passkey);
  AddPasskeyInternal(passkey);
}

std::string PasskeySyncBridge::AddNewPasskeyForTesting(
    sync_pb::WebauthnCredentialSpecifics specifics) {
  const std::string sync_id = specifics.sync_id();
  AddPasskeyInternal(std::move(specifics));
  return sync_id;
}

void PasskeySyncBridge::AddPasskeyInternal(
    sync_pb::WebauthnCredentialSpecifics specifics) {
  CHECK(passkey_model_utils::IsPasskeyValid(specifics));
  CHECK(IsReady());
  CHECK(store_);

  std::string sync_id = specifics.sync_id();
  CHECK(!data_.contains(sync_id));

  std::unique_ptr<syncer::DataTypeStore::WriteBatch> write_batch =
      store_->CreateWriteBatch();
  change_processor()->Put(sync_id, CreateEntityData(specifics),
                          write_batch->GetMetadataChangeList());
  write_batch->WriteData(sync_id, specifics.SerializeAsString());
  store_->CommitWriteBatch(
      std::move(write_batch),
      base::BindOnce(&PasskeySyncBridge::OnStoreCommitWriteBatch,
                     weak_ptr_factory_.GetWeakPtr()));
  data_[sync_id] = specifics;
  NotifyPasskeysChanged({PasskeyModelChange(PasskeyModelChange::ChangeType::ADD,
                                            std::move(specifics))});
}

void PasskeySyncBridge::OnCreateStore(
    const std::optional<syncer::ModelError>& error,
    std::unique_ptr<syncer::DataTypeStore> store) {
  if (error) {
    change_processor()->ReportError(*error);
    return;
  }
  DCHECK(store);
  store_ = std::move(store);
  store_->ReadAllDataAndMetadata(
      base::BindOnce(&PasskeySyncBridge::OnStoreReadAllDataAndMetadata,
                     weak_ptr_factory_.GetWeakPtr()));
}

void PasskeySyncBridge::OnStoreReadAllDataAndMetadata(
    const std::optional<syncer::ModelError>& error,
    std::unique_ptr<syncer::DataTypeStore::RecordList> entries,
    std::unique_ptr<syncer::MetadataBatch> metadata_batch) {
  TRACE_EVENT0("sync", "PasskeySyncBridge::OnStoreReadAllDataAndMetadata");
  if (error) {
    change_processor()->ReportError(*error);
    // Notify observers that the model failed to become ready.
    NotifyPasskeyModelIsReady(ready_);
    return;
  }

  std::vector<PasskeyModelChange> changes;
  for (const syncer::DataTypeStore::Record& r : *entries) {
    sync_pb::WebauthnCredentialSpecifics specifics;
    if (!specifics.ParseFromString(r.value) || !specifics.has_sync_id()) {
      DVLOG(1) << "Invalid stored record: " << r.value;
      continue;
    }
    std::string storage_key = specifics.sync_id();
    changes.emplace_back(PasskeyModelChange::ChangeType::ADD, specifics);
    data_[std::move(storage_key)] = std::move(specifics);
  }
  ready_ = true;
  NotifyPasskeysChanged(std::move(changes));
  change_processor()->ModelReadyToSync(std::move(metadata_batch));
  NotifyPasskeyModelIsReady(ready_);

  // Trigger maintenance tasks now and periodically, for users who keep Chrome
  // open for long periods.
  if (base::FeatureList::IsEnabled(features::kDeleteOldHiddenPasskeys)) {
    DeleteOldHiddenPasskeys();
    delete_old_hidden_passkeys_timer_.Start(
        FROM_HERE, base::Hours(24),
        base::BindRepeating(&PasskeySyncBridge::DeleteOldHiddenPasskeys,
                            weak_ptr_factory_.GetWeakPtr()));
  }
}

void PasskeySyncBridge::OnStoreCommitWriteBatch(
    const std::optional<syncer::ModelError>& error) {
  if (error) {
    change_processor()->ReportError(*error);
    return;
  }
}

void PasskeySyncBridge::NotifyPasskeysChanged(
    const std::vector<PasskeyModelChange>& changes) {
  TRACE_EVENT0("sync", "PasskeySyncBridge::NotifyPasskeysChanged");
  for (auto& observer : observers_) {
    observer.OnPasskeysChanged(changes);
  }
}

void PasskeySyncBridge::NotifyPasskeyModelIsReady(bool is_ready) {
  TRACE_EVENT0("sync", "PasskeySyncBridge::NotifyPasskeyModelIsReady");
  for (auto& observer : observers_) {
    observer.OnPasskeyModelIsReady(is_ready);
  }
}

void PasskeySyncBridge::AddShadowedCredentialIdsToNewPasskey(
    sync_pb::WebauthnCredentialSpecifics& passkey) {
  for (const auto& [sync_id, existing_passkey] : data_) {
    if (passkey.rp_id() == existing_passkey.rp_id() &&
        passkey.user_id() == existing_passkey.user_id()) {
      passkey.add_newly_shadowed_credential_ids(
          existing_passkey.credential_id());
    }
  }
}

bool PasskeySyncBridge::UpdateSinglePasskey(
    const std::string& credential_id,
    base::OnceCallback<bool(sync_pb::WebauthnCredentialSpecifics*)>
        mutate_callback) {
  // Find the credential with the given |credential_id|.
  const auto passkey_it =
      std::ranges::find_if(data_, [&credential_id](const auto& passkey) {
        return passkey.second.credential_id() == credential_id;
      });
  if (passkey_it == data_.end()) {
    DVLOG(1) << "Attempted to update non existent passkey";
    return false;
  }
  if (!std::move(mutate_callback).Run(&passkey_it->second)) {
    return false;
  }
  std::unique_ptr<syncer::DataTypeStore::WriteBatch> write_batch =
      store_->CreateWriteBatch();
  change_processor()->Put(passkey_it->second.sync_id(),
                          CreateEntityData(passkey_it->second),
                          write_batch->GetMetadataChangeList());
  write_batch->WriteData(passkey_it->second.sync_id(),
                         passkey_it->second.SerializeAsString());
  store_->CommitWriteBatch(
      std::move(write_batch),
      base::BindOnce(&PasskeySyncBridge::OnStoreCommitWriteBatch,
                     weak_ptr_factory_.GetWeakPtr()));
  NotifyPasskeysChanged({PasskeyModelChange(
      PasskeyModelChange::ChangeType::UPDATE, passkey_it->second)});
  return true;
}

void PasskeySyncBridge::DeleteOldHiddenPasskeys() {
  std::vector<std::string> credential_ids_to_delete;
  base::Time date_cutoff = clock_->Now() - kHiddenPasskeyLifetime;
  for (const auto& passkey : data_) {
    if (!passkey.second.hidden() || !passkey.second.has_hidden_time()) {
      continue;
    }
    base::Time hidden_time = base::Time::FromMillisecondsSinceUnixEpoch(
        passkey.second.hidden_time());
    if (hidden_time < date_cutoff) {
      credential_ids_to_delete.emplace_back(passkey.second.credential_id());
    }
  }
  for (const std::string& credential_id : credential_ids_to_delete) {
    DeletePasskey(credential_id, FROM_HERE);
  }
}

}  // namespace webauthn
