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

#include "ash/quick_pair/repository/fast_pair_repository_impl.h"

#include <optional>

#include "ash/quick_pair/common/device.h"
#include "ash/quick_pair/common/fast_pair/fast_pair_metrics.h"
#include "ash/quick_pair/common/mock_quick_pair_browser_delegate.h"
#include "ash/quick_pair/common/protocol.h"
#include "ash/quick_pair/common/quick_pair_browser_delegate.h"
#include "ash/quick_pair/repository/fake_device_metadata_http_fetcher.h"
#include "ash/quick_pair/repository/fast_pair/device_address_map.h"
#include "ash/quick_pair/repository/fast_pair/device_image_store.h"
#include "ash/quick_pair/repository/fast_pair/device_metadata.h"
#include "ash/quick_pair/repository/fast_pair/device_metadata_fetcher.h"
#include "ash/quick_pair/repository/fast_pair/fake_footprints_fetcher.h"
#include "ash/quick_pair/repository/fast_pair/mock_fast_pair_image_decoder.h"
#include "ash/quick_pair/repository/fast_pair/pending_write_store.h"
#include "ash/quick_pair/repository/fast_pair/proto_conversions.h"
#include "ash/quick_pair/repository/fast_pair/saved_device_registry.h"
#include "ash/quick_pair/repository/fast_pair_repository.h"
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "base/base64.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_view_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/mock_callback.h"
#include "base/test/task_environment.h"
#include "chromeos/ash/components/network/network_handler.h"
#include "chromeos/ash/components/network/network_state_handler.h"
#include "chromeos/ash/components/network/network_state_test_helper.h"
#include "chromeos/ash/services/quick_pair/public/cpp/account_key_filter.h"
#include "components/prefs/testing_pref_service.h"
#include "crypto/hash.h"
#include "device/bluetooth/bluetooth_adapter_factory.h"
#include "device/bluetooth/public/cpp/bluetooth_address.h"
#include "device/bluetooth/test/mock_bluetooth_adapter.h"
#include "device/bluetooth/test/mock_bluetooth_device.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/image/image_unittest_util.h"

namespace {

constexpr int kBluetoothAddressSize = 6;
constexpr char kValidModelId[] = "abc";
constexpr char kInvalidModelId[] = "666";
constexpr char kDeviceDisplayName[] = "test_nickname";
constexpr char kDeviceDisplayName2[] = "second_test_nickname";
constexpr char kTestModelId[] = "test_model_id";
constexpr char kTestDeviceId[] = "test_ble_device_id";
constexpr char kTestBLEAddress[] = "00:11:22:33:45:11";
constexpr char kTestBLEAddress2[] = "00:11:22:33:45:77";
constexpr char kTestClassicAddress1[] = "00:11:22:33:44:55";
constexpr char kTestClassicAddress2[] = "00:11:22:33:44:66";
constexpr char kTestClassicAddress3[] = "04:CB:88:1E:56:19";
constexpr char kBase64ExpectedSha256Hash[] =
    "gVzzRtZjwYv8lO8xwWnWW2uw/stV6RdEUhv3cIN3nH4=";
constexpr char kBase64ForgetPatternSha256Hash[] =
    "8PDw8NZjwYv8lO8xwWnWW2uw/stV6RdEUhv3cIN3nH4=";
constexpr char kBase64AccountKey[] = "BAcDiEH56/Mq3hW7OKUctA==";
const std::vector<uint8_t> kAccountKey1{0x11, 0x22, 0x33, 0x44, 0x55, 0x66,
                                        0x77, 0x88, 0x99, 0x00, 0xAA, 0xBB,
                                        0xCC, 0xDD, 0xEE, 0xFF};
const std::vector<uint8_t> kAccountKey2{0x11, 0x11, 0x22, 0x22, 0x33, 0x33,
                                        0x44, 0x44, 0x55, 0x55, 0x66, 0x66,
                                        0x77, 0x77, 0x88, 0x88};
const std::vector<uint8_t> kFilterBytes1{0x0A, 0x42, 0x88, 0x10};
const uint8_t salt = 0xC7;

const char kSavedDeviceGetDevicesResultMetricName[] =
    "Bluetooth.ChromeOS.FastPair.SavedDevices.GetSavedDevices.Result";
constexpr char kRetroactiveSuccessFunnelMetric[] =
    "FastPair.RetroactivePairing";

// Computes and returns the Sha256 of the concatenation of the given
// |account_key| and |mac_address|.
std::string GenerateSha256AccountKeyMacAddress(const std::string& account_key,
                                               const std::string& mac_address) {
  std::vector<uint8_t> concat_bytes(account_key.begin(), account_key.end());
  std::vector<uint8_t> mac_address_bytes;
  mac_address_bytes.resize(kBluetoothAddressSize);
  device::ParseBluetoothAddress(mac_address, mac_address_bytes);

  concat_bytes.insert(concat_bytes.end(), mac_address_bytes.begin(),
                      mac_address_bytes.end());

  return std::string(base::as_string_view(crypto::hash::Sha256(concat_bytes)));
}

std::string Base64Decode(const std::string& encoded) {
  std::string decoded;
  base::Base64Decode(encoded, &decoded);
  return decoded;
}

}  // namespace

namespace ash {
namespace quick_pair {

using ::base::test::RunOnceCallback;
using ::testing::_;
using ::testing::Return;

class FastPairRepositoryImplTest : public AshTestBase {
 public:
  FastPairRepositoryImplTest()
      : AshTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME),
        adapter_(new testing::NiceMock<device::MockBluetoothAdapter>),
        ble_bluetooth_device_(adapter_.get(),
                              0,
                              "Test ble name",
                              kTestBLEAddress,
                              false,
                              true),
        classic_bluetooth_device_(adapter_.get(),
                                  0,
                                  "Test classic name",
                                  kTestClassicAddress1,
                                  false,
                                  true) {
    ON_CALL(ble_bluetooth_device_, GetIdentifier)
        .WillByDefault(Return(kTestDeviceId));
    ON_CALL(classic_bluetooth_device_, GetIdentifier)
        .WillByDefault(Return(kTestDeviceId));
    ON_CALL(ble_bluetooth_device_, IsPaired)
        .WillByDefault(testing::Return(true));
    ON_CALL(classic_bluetooth_device_, IsPaired)
        .WillByDefault(testing::Return(true));
    ON_CALL(*adapter_, GetDevices).WillByDefault(testing::Return(device_list_));
    ON_CALL(*adapter_, GetDevice(kTestBLEAddress))
        .WillByDefault(Return(&ble_bluetooth_device_));
    ON_CALL(*adapter_, GetDevice(kTestClassicAddress1))
        .WillByDefault(Return(&classic_bluetooth_device_));
  }

  void SetUp() override {
    AshTestBase::SetUp();
    NetworkHandler::Initialize();
    device::BluetoothAdapterFactory::SetAdapterForTesting(adapter_);
    device_ = base::MakeRefCounted<Device>(kTestModelId, kTestBLEAddress,
                                           Protocol::kFastPairInitial);
    device_->set_classic_address(kTestClassicAddress1);

    auto http_fetcher = std::make_unique<FakeDeviceMetadataHttpFetcher>();
    metadata_http_fetcher_ = http_fetcher.get();

    auto device_metadata_fetcher =
        std::make_unique<DeviceMetadataFetcher>(std::move(http_fetcher));
    device_metadata_fetcher_ = device_metadata_fetcher.get();

    auto footprints_fetcher = std::make_unique<FakeFootprintsFetcher>();
    footprints_fetcher_ = footprints_fetcher.get();

    auto image_decoder = std::make_unique<MockFastPairImageDecoder>();
    image_decoder_ = image_decoder.get();
    test_image_ = gfx::test::CreateImage(100, 100);
    ON_CALL(*image_decoder_, DecodeImage(_, _, _))
        .WillByDefault(base::test::RunOnceCallbackRepeatedly<2>(test_image_));

    auto device_address_map = std::make_unique<DeviceAddressMap>();
    device_address_map_ = device_address_map.get();

    auto device_image_store =
        std::make_unique<DeviceImageStore>(image_decoder_);
    device_image_store_ = device_image_store.get();

    auto pending_write_store = std::make_unique<PendingWriteStore>();
    pending_write_store_ = pending_write_store.get();

    auto saved_device_registry =
        std::make_unique<SavedDeviceRegistry>(adapter_.get());
    saved_device_registry_ = saved_device_registry.get();

    fast_pair_repository_ = std::make_unique<FastPairRepositoryImpl>(
        adapter_, std::move(device_metadata_fetcher),
        std::move(footprints_fetcher), std::move(image_decoder),
        std::move(device_address_map), std::move(device_image_store),
        std::move(saved_device_registry), std::move(pending_write_store));
    FastPairRepository::SetInstanceForTesting(fast_pair_repository_.get());
  }

  void TearDown() override {
    saved_device_registry_ = nullptr;
    pending_write_store_ = nullptr;
    device_image_store_ = nullptr;
    device_address_map_ = nullptr;
    image_decoder_ = nullptr;
    footprints_fetcher_ = nullptr;
    device_metadata_fetcher_ = nullptr;
    metadata_http_fetcher_ = nullptr;
    device_.reset();
    // reset last due to dependency to above
    fast_pair_repository_.reset();
    NetworkHandler::Shutdown();
    AshTestBase::TearDown();
  }

  void VerifyMetadata(base::OnceClosure on_complete,
                      DeviceMetadata* device_metadata,
                      bool should_retry) {
    EXPECT_NE(nullptr, device_metadata);
    std::move(on_complete).Run();
  }

  void VerifyMetadataFailure(base::OnceClosure on_complete,
                             bool expected_retry,
                             DeviceMetadata* device_metadata,
                             bool should_retry) {
    EXPECT_EQ(nullptr, device_metadata);
    EXPECT_EQ(expected_retry, should_retry);
    std::move(on_complete).Run();
  }

  void VerifyAccountKeyCheck(base::OnceClosure on_complete,
                             bool expected_result,
                             std::optional<PairingMetadata> pairing_metadata) {
    if (expected_result) {
      EXPECT_NE(std::nullopt, pairing_metadata);
    } else {
      EXPECT_EQ(std::nullopt, pairing_metadata);
    }
    std::move(on_complete).Run();
  }

  void GetSavedDevicesCallback(
      nearby::fastpair::OptInStatus status,
      std::vector<nearby::fastpair::FastPairDevice> devices) {
    status_ = status;
    devices_ = devices;
  }

  base::HistogramTester& histogram_tester() { return histogram_tester_; }

 protected:
  NetworkStateTestHelper helper_{/*use_default_devices_and_services=*/true};
  std::unique_ptr<FastPairRepositoryImpl> fast_pair_repository_;
  nearby::fastpair::OptInStatus status_;
  std::vector<nearby::fastpair::FastPairDevice> devices_;
  base::HistogramTester histogram_tester_;
  scoped_refptr<testing::NiceMock<device::MockBluetoothAdapter>> adapter_;
  testing::NiceMock<device::MockBluetoothDevice> ble_bluetooth_device_;
  testing::NiceMock<device::MockBluetoothDevice> classic_bluetooth_device_;
  device::BluetoothAdapter::ConstDeviceList device_list_{
      &ble_bluetooth_device_, &classic_bluetooth_device_};
  scoped_refptr<Device> device_;
  gfx::Image test_image_;

  raw_ptr<DeviceMetadataFetcher> device_metadata_fetcher_;
  raw_ptr<FakeDeviceMetadataHttpFetcher> metadata_http_fetcher_;
  raw_ptr<FakeFootprintsFetcher> footprints_fetcher_;
  raw_ptr<MockFastPairImageDecoder> image_decoder_;
  raw_ptr<DeviceAddressMap> device_address_map_;
  raw_ptr<DeviceImageStore> device_image_store_;
  raw_ptr<PendingWriteStore> pending_write_store_;
  raw_ptr<SavedDeviceRegistry> saved_device_registry_;

  base::WeakPtrFactory<FastPairRepositoryImplTest> weak_ptr_factory_{this};
};

TEST_F(FastPairRepositoryImplTest, GetDeviceMetadata) {
  {
    auto run_loop = base::RunLoop();
    fast_pair_repository_->GetDeviceMetadata(
        kValidModelId,
        base::BindOnce(&FastPairRepositoryImplTest::VerifyMetadata,
                       base::Unretained(this), run_loop.QuitClosure()));
    run_loop.Run();
  }

  EXPECT_EQ(1, metadata_http_fetcher_->num_gets());

  {
    auto run_loop = base::RunLoop();
    fast_pair_repository_->GetDeviceMetadata(
        kValidModelId,
        base::BindOnce(&FastPairRepositoryImplTest::VerifyMetadata,
                       base::Unretained(this), run_loop.QuitClosure()));
    run_loop.Run();
  }

  // Indicates that the cache was used instead of a second GET.
  EXPECT_EQ(1, metadata_http_fetcher_->num_gets());
}

TEST_F(FastPairRepositoryImplTest, GetDeviceMetadata_Failed_Retryable) {
  base::RunLoop run_loop;
  metadata_http_fetcher_->set_network_error(true);
  fast_pair_repository_->GetDeviceMetadata(
      kInvalidModelId,
      base::BindOnce(&FastPairRepositoryImplTest::VerifyMetadataFailure,
                     base::Unretained(this), run_loop.QuitClosure(),
                     /*expected_retry=*/true));
  run_loop.Run();
}

TEST_F(FastPairRepositoryImplTest, GetDeviceMetadata_Failed_NotRetryable) {
  base::RunLoop run_loop;
  fast_pair_repository_->GetDeviceMetadata(
      kInvalidModelId,
      base::BindOnce(&FastPairRepositoryImplTest::VerifyMetadataFailure,
                     base::Unretained(this), run_loop.QuitClosure(),
                     /*expected_retry=*/false));
  run_loop.Run();
  EXPECT_EQ(1, metadata_http_fetcher_->num_gets());
}

TEST_F(FastPairRepositoryImplTest, CheckAccountKeys_NoMatch) {
  AccountKeyFilter filter(kFilterBytes1, {salt});

  auto run_loop = base::RunLoop();
  fast_pair_repository_->CheckAccountKeys(
      filter, base::BindOnce(&FastPairRepositoryImplTest::VerifyAccountKeyCheck,
                             base::Unretained(this), run_loop.QuitClosure(),
                             /*expected_result=*/false));
  run_loop.Run();
}

TEST_F(FastPairRepositoryImplTest, CheckAccountKeys_Match) {
  AccountKeyFilter filter(kFilterBytes1, {salt});
  nearby::fastpair::GetObservedDeviceResponse device;
  DeviceMetadata metadata(device, gfx::Image());

  // FakeFootprintsFetcher APIs are actually synchronous.
  footprints_fetcher_->AddUserFastPairInfo(
      BuildFastPairInfo(kValidModelId, kAccountKey1, kTestClassicAddress1,
                        kDeviceDisplayName, &metadata),
      base::DoNothing());

  auto run_loop = base::RunLoop();
  fast_pair_repository_->CheckAccountKeys(
      filter, base::BindOnce(&FastPairRepositoryImplTest::VerifyAccountKeyCheck,
                             base::Unretained(this), run_loop.QuitClosure(),
                             /*expected_result=*/true));
  run_loop.Run();
}

TEST_F(FastPairRepositoryImplTest, CheckAccountKeys_Match_No_Name) {
  AccountKeyFilter filter(kFilterBytes1, {salt});
  nearby::fastpair::GetObservedDeviceResponse device;
  DeviceMetadata metadata(device, gfx::Image());

  // FakeFootprintsFetcher APIs are actually synchronous.
  // The |device_| display name is not set so this will mimic when we fail to
  // get a |display_name| for the device and have to fall back on using the
  // metadata name when creating the proto.
  footprints_fetcher_->AddUserFastPairInfo(
      BuildFastPairInfo(kValidModelId, kAccountKey1, kTestClassicAddress1,
                        device_->display_name(), &metadata),
      base::DoNothing());

  auto run_loop = base::RunLoop();
  fast_pair_repository_->CheckAccountKeys(
      filter, base::BindOnce(&FastPairRepositoryImplTest::VerifyAccountKeyCheck,
                             base::Unretained(this), run_loop.QuitClosure(),
                             /*expected_result=*/true));
  run_loop.Run();
}

TEST_F(FastPairRepositoryImplTest, CheckAccountKeys_SkipForgetPattern) {
  AccountKeyFilter filter(kFilterBytes1, {salt});
  nearby::fastpair::GetObservedDeviceResponse details;
  DeviceMetadata metadata(details, gfx::Image());

  // In this test, we use a known "marked as forgotten" hash
  // from Android. Note that Android also leaves the account key
  // blank; however, we add a matching account key here to test the logic
  // of detecting the Forget pattern in the hash, which should be cause
  // the device to be skipped in CheckAccountKeys.
  nearby::fastpair::FastPairInfo info =
      BuildFastPairInfo(kValidModelId, kAccountKey1, kTestClassicAddress1,
                        kDeviceDisplayName, &metadata);
  auto* device = info.mutable_device();
  device->set_sha256_account_key_public_address(
      Base64Decode(kBase64ForgetPatternSha256Hash));
  footprints_fetcher_->AddUserFastPairInfo(info, base::DoNothing());

  auto run_loop = std::make_unique<base::RunLoop>();
  fast_pair_repository_->CheckAccountKeys(
      filter, base::BindOnce(&FastPairRepositoryImplTest::VerifyAccountKeyCheck,
                             base::Unretained(this), run_loop->QuitClosure(),
                             /*expected_result=*/false));
  run_loop->Run();
}

TEST_F(FastPairRepositoryImplTest, UpdateStaleUserDeviceCache) {
  AccountKeyFilter filter(kFilterBytes1, {salt});
  nearby::fastpair::GetObservedDeviceResponse response;
  DeviceMetadata metadata(response, gfx::Image());

  auto device = base::MakeRefCounted<Device>(kValidModelId, kTestBLEAddress,
                                             Protocol::kFastPairInitial);
  device->set_classic_address(kTestClassicAddress1);
  device->set_display_name(kDeviceDisplayName);
  fast_pair_repository_->WriteAccountAssociationToFootprints(device,
                                                             kAccountKey1);
  base::RunLoop().RunUntilIdle();
  ASSERT_TRUE(footprints_fetcher_->ContainsKey(kAccountKey1));

  auto run_loop = base::RunLoop();

  // Check for the device, this will also load the device into the cache
  fast_pair_repository_->CheckAccountKeys(
      filter, base::BindOnce(&FastPairRepositoryImplTest::VerifyAccountKeyCheck,
                             base::Unretained(this), run_loop.QuitClosure(),
                             /*expected_result=*/true));
  base::RunLoop().RunUntilIdle();

  // Remove the device directly from footprints. This is equivalent to the
  // device being removed on an Android phone or another Chromebook
  footprints_fetcher_->DeleteUserDevice(base::HexEncode(kAccountKey1),
                                        base::DoNothing());

  // 29 minutes later, device is still in the cache
  task_environment()->FastForwardBy(base::Minutes(29));
  fast_pair_repository_->CheckAccountKeys(
      filter, base::BindOnce(&FastPairRepositoryImplTest::VerifyAccountKeyCheck,
                             base::Unretained(this), run_loop.QuitClosure(),
                             /*expected_result=*/true));
  base::RunLoop().RunUntilIdle();

  // After >30 minutes, cache will have gone stale so device will be removed
  task_environment()->FastForwardBy(base::Seconds(61));
  fast_pair_repository_->CheckAccountKeys(
      filter, base::BindOnce(&FastPairRepositoryImplTest::VerifyAccountKeyCheck,
                             base::Unretained(this), run_loop.QuitClosure(),
                             /*expected_result=*/false));
  run_loop.Run();
}

TEST_F(FastPairRepositoryImplTest, UseStaleCache) {
  AccountKeyFilter filter(kFilterBytes1, {salt});
  nearby::fastpair::GetObservedDeviceResponse response;
  DeviceMetadata metadata(response, gfx::Image());

  auto device = base::MakeRefCounted<Device>(kValidModelId, kTestBLEAddress,
                                             Protocol::kFastPairInitial);
  device->set_classic_address(kTestClassicAddress1);
  device->set_display_name(kDeviceDisplayName);
  fast_pair_repository_->WriteAccountAssociationToFootprints(device,
                                                             kAccountKey1);
  base::RunLoop().RunUntilIdle();
  ASSERT_TRUE(footprints_fetcher_->ContainsKey(kAccountKey1));

  auto run_loop = base::RunLoop();

  // Check for the device, this will also load the device into the cache.
  fast_pair_repository_->CheckAccountKeys(
      filter, base::BindOnce(&FastPairRepositoryImplTest::VerifyAccountKeyCheck,
                             base::Unretained(this), run_loop.QuitClosure(),
                             /*expected_result=*/true));

  // Remove the device directly from footprints. This is equivalent to the
  // device being removed on an Android phone or another Chromebook.
  footprints_fetcher_->DeleteUserDevice(base::HexEncode(kAccountKey1),
                                        base::DoNothing());

  // Set the response to replicate an error getting devices from the server.
  footprints_fetcher_->SetGetUserDevicesResponse(std::nullopt);

  // After >30 minutes, cache is stale but we will fail to get devices from
  // the server so we use the stale cache with the device still present.
  task_environment()->FastForwardBy(base::Minutes(31));
  fast_pair_repository_->CheckAccountKeys(
      filter, base::BindOnce(&FastPairRepositoryImplTest::VerifyAccountKeyCheck,
                             base::Unretained(this), run_loop.QuitClosure(),
                             /*expected_result=*/true));
  run_loop.Run();
}

TEST_F(FastPairRepositoryImplTest, GetDeviceNameFromCache) {
  AccountKeyFilter filter(kFilterBytes1, {salt});
  nearby::fastpair::GetObservedDeviceResponse response;
  DeviceMetadata metadata(response, gfx::Image());

  auto device = base::MakeRefCounted<Device>(kValidModelId, kTestBLEAddress,
                                             Protocol::kFastPairSubsequent);
  device->set_classic_address(kTestClassicAddress1);
  device->set_account_key(kAccountKey1);
  device->set_display_name(kDeviceDisplayName);
  fast_pair_repository_->WriteAccountAssociationToLocalRegistry(device);
  fast_pair_repository_->WriteAccountAssociationToFootprints(device,
                                                             kAccountKey1);
  base::RunLoop().RunUntilIdle();
  ASSERT_TRUE(footprints_fetcher_->ContainsKey(kAccountKey1));

  auto run_loop = base::RunLoop();

  // Check for the device, this will load the device into the cache.
  fast_pair_repository_->CheckAccountKeys(
      filter, base::BindOnce(&FastPairRepositoryImplTest::VerifyAccountKeyCheck,
                             base::Unretained(this), run_loop.QuitClosure(),
                             /*expected_result=*/true));
  run_loop.Run();

  device->set_display_name(fast_pair_repository_->GetDeviceDisplayNameFromCache(
      device->account_key().value()));
  ASSERT_EQ(kDeviceDisplayName, device->display_name());
}

TEST_F(FastPairRepositoryImplTest, ChangeDeviceNameFromCache) {
  AccountKeyFilter filter(kFilterBytes1, {salt});
  nearby::fastpair::GetObservedDeviceResponse response;
  DeviceMetadata metadata(response, gfx::Image());

  auto device = base::MakeRefCounted<Device>(kValidModelId, kTestBLEAddress,
                                             Protocol::kFastPairInitial);
  device->set_classic_address(kTestClassicAddress1);
  device->set_account_key(kAccountKey1);
  device->set_display_name(kDeviceDisplayName);
  fast_pair_repository_->WriteAccountAssociationToLocalRegistry(device);
  fast_pair_repository_->WriteAccountAssociationToFootprints(device,
                                                             kAccountKey1);
  base::RunLoop().RunUntilIdle();
  ASSERT_TRUE(footprints_fetcher_->ContainsKey(kAccountKey1));
  ASSERT_TRUE(
      saved_device_registry_->IsAccountKeySavedToRegistry(kAccountKey1));

  auto run_loop = base::RunLoop();

  // Check for the device, this will load the device into the cache.
  fast_pair_repository_->CheckAccountKeys(
      filter, base::BindOnce(&FastPairRepositoryImplTest::VerifyAccountKeyCheck,
                             base::Unretained(this), run_loop.QuitClosure(),
                             /*expected_result=*/true));

  device->set_display_name(fast_pair_repository_->GetDeviceDisplayNameFromCache(
      device->account_key().value()));
  ASSERT_EQ(device->display_name(), kDeviceDisplayName);

  fast_pair_repository_->UpdateAssociatedDeviceFootprintsName(
      kTestClassicAddress1, kDeviceDisplayName2, /*cache_may_be_stale=*/true);

  // Check for the device, this will load the device into the cache.
  fast_pair_repository_->CheckAccountKeys(
      filter, base::BindOnce(&FastPairRepositoryImplTest::VerifyAccountKeyCheck,
                             base::Unretained(this), run_loop.QuitClosure(),
                             /*expected_result=*/true));

  device->set_display_name(fast_pair_repository_->GetDeviceDisplayNameFromCache(
      device->account_key().value()));
  ASSERT_EQ(device->display_name(), kDeviceDisplayName2);
}

TEST_F(FastPairRepositoryImplTest, ChangeDeviceNameNotInCache) {
  AccountKeyFilter filter(kFilterBytes1, {salt});
  nearby::fastpair::GetObservedDeviceResponse response;
  DeviceMetadata metadata(response, gfx::Image());

  auto device = base::MakeRefCounted<Device>(kValidModelId, kTestBLEAddress,
                                             Protocol::kFastPairInitial);
  device->set_classic_address(kTestClassicAddress1);
  device->set_account_key(kAccountKey1);
  device->set_display_name(kDeviceDisplayName);
  fast_pair_repository_->WriteAccountAssociationToLocalRegistry(device);
  fast_pair_repository_->WriteAccountAssociationToFootprints(device,
                                                             kAccountKey1);
  base::RunLoop().RunUntilIdle();
  ASSERT_TRUE(footprints_fetcher_->ContainsKey(kAccountKey1));
  ASSERT_TRUE(
      saved_device_registry_->IsAccountKeySavedToRegistry(kAccountKey1));

  auto run_loop = base::RunLoop();

  fast_pair_repository_->UpdateAssociatedDeviceFootprintsName(
      kTestClassicAddress1, kDeviceDisplayName2, /*cache_may_be_stale=*/true);
  base::RunLoop().RunUntilIdle();

  // Check for the device, this will load the device into the cache.
  fast_pair_repository_->CheckAccountKeys(
      filter, base::BindOnce(&FastPairRepositoryImplTest::VerifyAccountKeyCheck,
                             base::Unretained(this), run_loop.QuitClosure(),
                             /*expected_result=*/true));
  base::RunLoop().RunUntilIdle();
  device->set_display_name(fast_pair_repository_->GetDeviceDisplayNameFromCache(
      device->account_key().value()));
  ASSERT_EQ(device->display_name(), kDeviceDisplayName2);
}

TEST_F(FastPairRepositoryImplTest,
       UpdateAssociatedDeviceFootprintsName_No_Matching_Account_key) {
  AccountKeyFilter filter(kFilterBytes1, {salt});

  auto device = base::MakeRefCounted<Device>(kValidModelId, kTestBLEAddress,
                                             Protocol::kFastPairInitial);
  device->set_classic_address(kTestClassicAddress1);
  device->set_account_key(kAccountKey1);
  fast_pair_repository_->UpdateAssociatedDeviceFootprintsName(
      kTestClassicAddress1, kDeviceDisplayName2, /*cache_may_be_stale=*/true);
  base::RunLoop().RunUntilIdle();

  auto run_loop = base::RunLoop();

  // Check for the device, this will load the device into the cache.
  fast_pair_repository_->CheckAccountKeys(
      filter, base::BindOnce(&FastPairRepositoryImplTest::VerifyAccountKeyCheck,
                             base::Unretained(this), run_loop.QuitClosure(),
                             /*expected_result=*/false));

  device->set_display_name(fast_pair_repository_->GetDeviceDisplayNameFromCache(
      device->account_key().value()));
}

TEST_F(FastPairRepositoryImplTest, LocalRemoveDeviceUpdatesCache) {
  AccountKeyFilter filter(kFilterBytes1, {salt});
  nearby::fastpair::GetObservedDeviceResponse response;
  DeviceMetadata metadata(response, gfx::Image());

  auto device = base::MakeRefCounted<Device>(kValidModelId, kTestBLEAddress,
                                             Protocol::kFastPairInitial);
  device->set_classic_address(kTestClassicAddress1);
  device->set_account_key(kAccountKey1);
  device->set_display_name(kDeviceDisplayName);
  fast_pair_repository_->WriteAccountAssociationToLocalRegistry(device);
  fast_pair_repository_->WriteAccountAssociationToFootprints(device,
                                                             kAccountKey1);
  base::RunLoop().RunUntilIdle();
  ASSERT_TRUE(footprints_fetcher_->ContainsKey(kAccountKey1));
  ASSERT_TRUE(
      saved_device_registry_->IsAccountKeySavedToRegistry(kAccountKey1));

  auto run_loop = base::RunLoop();

  // Check for the device, this will also load the device into the cache.
  fast_pair_repository_->CheckAccountKeys(
      filter, base::BindOnce(&FastPairRepositoryImplTest::VerifyAccountKeyCheck,
                             base::Unretained(this), run_loop.QuitClosure(),
                             /*expected_result=*/true));

  // Remove the device as if this chromebook was removing it. This should
  // invalidate the cache so the device will be removed there as well.
  fast_pair_repository_->DeleteAssociatedDevice(
      classic_bluetooth_device_.GetAddress(), base::DoNothing());

  // Device should not appear in the cache.
  fast_pair_repository_->CheckAccountKeys(
      filter, base::BindOnce(&FastPairRepositoryImplTest::VerifyAccountKeyCheck,
                             base::Unretained(this), run_loop.QuitClosure(),
                             /*expected_result=*/false));
  run_loop.Run();
}

TEST_F(FastPairRepositoryImplTest,
       WriteAccountAssociationToFootprints_InvalidId) {
  auto device = base::MakeRefCounted<Device>(kInvalidModelId, kTestBLEAddress,
                                             Protocol::kFastPairInitial);
  device->set_classic_address(kTestClassicAddress1);
  device->set_display_name(kDeviceDisplayName);
  fast_pair_repository_->WriteAccountAssociationToFootprints(device,
                                                             kAccountKey1);
  base::RunLoop().RunUntilIdle();

  ASSERT_FALSE(footprints_fetcher_->ContainsKey(kAccountKey1));
}

TEST_F(FastPairRepositoryImplTest,
       WriteAccountAssociationToFootprints_ValidId) {
  auto device = base::MakeRefCounted<Device>(kValidModelId, kTestBLEAddress,
                                             Protocol::kFastPairInitial);
  device->set_classic_address(kTestClassicAddress1);
  device->set_display_name(kDeviceDisplayName);
  fast_pair_repository_->WriteAccountAssociationToFootprints(device,
                                                             kAccountKey1);
  base::RunLoop().RunUntilIdle();

  ASSERT_TRUE(footprints_fetcher_->ContainsKey(kAccountKey1));
  EXPECT_EQ(histogram_tester().GetBucketCount(
                kRetroactiveSuccessFunnelMetric,
                FastPairRetroactiveSuccessFunnelEvent::kSaveComplete),
            0);
}

TEST_F(FastPairRepositoryImplTest,
       WriteAccountAssociationToFootprints_LogRetroactiveSuccessFunnel) {
  auto device = base::MakeRefCounted<Device>(kValidModelId, kTestBLEAddress,
                                             Protocol::kFastPairRetroactive);
  device->set_classic_address(kTestClassicAddress1);
  device->set_display_name(kDeviceDisplayName);
  fast_pair_repository_->WriteAccountAssociationToFootprints(device,
                                                             kAccountKey1);
  base::RunLoop().RunUntilIdle();

  ASSERT_TRUE(footprints_fetcher_->ContainsKey(kAccountKey1));
  EXPECT_EQ(histogram_tester().GetBucketCount(
                kRetroactiveSuccessFunnelMetric,
                FastPairRetroactiveSuccessFunnelEvent::kSaveComplete),
            1);
}

TEST_F(FastPairRepositoryImplTest, AssociateAccountKeyAndCheckName) {
  AccountKeyFilter filter(kFilterBytes1, {salt});
  auto device = base::MakeRefCounted<Device>(kValidModelId, kTestBLEAddress,
                                             Protocol::kFastPairInitial);
  device->set_classic_address(kTestClassicAddress1);
  device->set_display_name(kDeviceDisplayName);
  fast_pair_repository_->WriteAccountAssociationToFootprints(device,
                                                             kAccountKey1);

  ASSERT_TRUE(footprints_fetcher_->ContainsKey(kAccountKey1));

  auto run_loop = base::RunLoop();

  // Check for the device, this will load the device into the cache.
  fast_pair_repository_->CheckAccountKeys(
      filter, base::BindOnce(&FastPairRepositoryImplTest::VerifyAccountKeyCheck,
                             base::Unretained(this), run_loop.QuitClosure(),
                             /*expected_result=*/true));
  run_loop.Run();

  // Device Account Key is set for testing purposes only.
  device->set_account_key(kAccountKey1);
  device->set_display_name(fast_pair_repository_->GetDeviceDisplayNameFromCache(
      device->account_key().value()));
  ASSERT_EQ(device->display_name(), kDeviceDisplayName);
}

TEST_F(FastPairRepositoryImplTest,
       WriteAccountAssociationToLocalRegistry_InvalidNoAccountKey) {
  auto device = base::MakeRefCounted<Device>(kInvalidModelId, kTestBLEAddress,
                                             Protocol::kFastPairInitial);
  device->set_classic_address(kTestClassicAddress1);
  ASSERT_FALSE(
      fast_pair_repository_->WriteAccountAssociationToLocalRegistry(device));
  base::RunLoop().RunUntilIdle();

  ASSERT_FALSE(
      saved_device_registry_->IsAccountKeySavedToRegistry(kAccountKey1));
}

TEST_F(FastPairRepositoryImplTest,
       WriteAccountAssociationToLocalRegistry_ValidAccountKey) {
  auto device = base::MakeRefCounted<Device>(kInvalidModelId, kTestBLEAddress,
                                             Protocol::kFastPairInitial);
  device->set_classic_address(kTestClassicAddress1);
  device->set_account_key(kAccountKey1);
  ASSERT_TRUE(
      fast_pair_repository_->WriteAccountAssociationToLocalRegistry(device));
  base::RunLoop().RunUntilIdle();

  ASSERT_TRUE(
      saved_device_registry_->IsAccountKeySavedToRegistry(kAccountKey1));
}

TEST_F(FastPairRepositoryImplTest, DeleteAssociatedDevice_Valid) {
  AccountKeyFilter filter(kFilterBytes1, {salt});
  nearby::fastpair::GetObservedDeviceResponse response;
  DeviceMetadata metadata(response, gfx::Image());

  auto device = base::MakeRefCounted<Device>(kValidModelId, kTestBLEAddress,
                                             Protocol::kFastPairInitial);
  device->set_classic_address(kTestClassicAddress1);
  device->set_account_key(kAccountKey1);
  device->set_display_name(kDeviceDisplayName);
  fast_pair_repository_->WriteAccountAssociationToLocalRegistry(device);
  fast_pair_repository_->WriteAccountAssociationToFootprints(device,
                                                             kAccountKey1);
  base::RunLoop().RunUntilIdle();
  ASSERT_TRUE(footprints_fetcher_->ContainsKey(kAccountKey1));
  ASSERT_TRUE(
      saved_device_registry_->IsAccountKeySavedToRegistry(kAccountKey1));

  base::MockCallback<base::OnceCallback<void(bool)>> callback;
  EXPECT_CALL(callback, Run(testing::Eq(true))).Times(1);
  fast_pair_repository_->DeleteAssociatedDevice(
      classic_bluetooth_device_.GetAddress(), callback.Get());

  base::RunLoop().RunUntilIdle();

  ASSERT_FALSE(footprints_fetcher_->ContainsKey(kAccountKey1));
  ASSERT_FALSE(
      saved_device_registry_->IsAccountKeySavedToRegistry(kAccountKey1));
  ASSERT_EQ(0u, pending_write_store_->GetPendingDeletes().size());
}

TEST_F(FastPairRepositoryImplTest, DeleteAssociatedDevice_Invalid) {
  base::MockCallback<base::OnceCallback<void(bool)>> callback;
  EXPECT_CALL(callback, Run(testing::Eq(false))).Times(1);
  fast_pair_repository_->DeleteAssociatedDevice(
      classic_bluetooth_device_.GetAddress(), callback.Get());

  ASSERT_EQ(0u, pending_write_store_->GetPendingDeletes().size());
}

TEST_F(FastPairRepositoryImplTest, DeleteAssociatedDeviceByAccountKey_Valid) {
  AccountKeyFilter filter(kFilterBytes1, {salt});
  nearby::fastpair::GetObservedDeviceResponse response;
  DeviceMetadata metadata(response, gfx::Image());

  auto device = base::MakeRefCounted<Device>(kValidModelId, kTestBLEAddress,
                                             Protocol::kFastPairInitial);
  device->set_classic_address(kTestClassicAddress1);
  device->set_account_key(kAccountKey1);
  device->set_display_name(kDeviceDisplayName);
  fast_pair_repository_->WriteAccountAssociationToLocalRegistry(device);
  fast_pair_repository_->WriteAccountAssociationToFootprints(device,
                                                             kAccountKey1);
  base::RunLoop().RunUntilIdle();
  ASSERT_TRUE(footprints_fetcher_->ContainsKey(kAccountKey1));
  ASSERT_TRUE(
      saved_device_registry_->IsAccountKeySavedToRegistry(kAccountKey1));

  base::MockCallback<base::OnceCallback<void(bool)>> callback;
  EXPECT_CALL(callback, Run(testing::Eq(true))).Times(1);
  fast_pair_repository_->DeleteAssociatedDeviceByAccountKey(kAccountKey1,
                                                            callback.Get());
  base::RunLoop().RunUntilIdle();

  ASSERT_FALSE(footprints_fetcher_->ContainsKey(kAccountKey1));
  ASSERT_FALSE(
      saved_device_registry_->IsAccountKeySavedToRegistry(kAccountKey1));
  ASSERT_EQ(0u, pending_write_store_->GetPendingDeletes().size());
}

TEST_F(FastPairRepositoryImplTest, RetriesForgetDevice_AfterNetworkAvailable) {
  auto device = base::MakeRefCounted<Device>(kValidModelId, kTestBLEAddress,
                                             Protocol::kFastPairInitial);
  device->set_classic_address(kTestClassicAddress1);
  device->set_account_key(kAccountKey1);
  device->set_display_name(kDeviceDisplayName);
  fast_pair_repository_->WriteAccountAssociationToLocalRegistry(device);
  fast_pair_repository_->WriteAccountAssociationToFootprints(device,
                                                             kAccountKey1);
  base::RunLoop().RunUntilIdle();
  ASSERT_TRUE(footprints_fetcher_->ContainsKey(kAccountKey1));
  ASSERT_TRUE(
      saved_device_registry_->IsAccountKeySavedToRegistry(kAccountKey1));
  ASSERT_EQ(0u, pending_write_store_->GetPendingDeletes().size());

  base::MockCallback<base::OnceCallback<void(bool)>> callback;
  EXPECT_CALL(callback, Run(testing::Eq(false))).Times(1);

  // Mock an error due to Network failure.
  footprints_fetcher_->SetDeleteUserDeviceResult(false);
  fast_pair_repository_->DeleteAssociatedDevice(
      classic_bluetooth_device_.GetAddress(), callback.Get());

  base::RunLoop().RunUntilIdle();

  // The failed delete should be saved as a pending delete.
  ASSERT_TRUE(footprints_fetcher_->ContainsKey(kAccountKey1));
  ASSERT_TRUE(
      saved_device_registry_->IsAccountKeySavedToRegistry(kAccountKey1));
  ASSERT_EQ(1u, pending_write_store_->GetPendingDeletes().size());

  // Reconnect to the Network, but fail again.
  fast_pair_repository_->DefaultNetworkChanged(
      helper_.network_state_handler()->DefaultNetwork());
  base::RunLoop().RunUntilIdle();

  // The delete, after another failed retry, should still be pending.
  ASSERT_TRUE(footprints_fetcher_->ContainsKey(kAccountKey1));
  ASSERT_TRUE(
      saved_device_registry_->IsAccountKeySavedToRegistry(kAccountKey1));
  ASSERT_EQ(1u, pending_write_store_->GetPendingDeletes().size());

  // Reconnect to the Network, but within the 1 minute timeout.
  footprints_fetcher_->SetDeleteUserDeviceResult(true);
  fast_pair_repository_->DefaultNetworkChanged(
      helper_.network_state_handler()->DefaultNetwork());
  base::RunLoop().RunUntilIdle();

  // Since we don't retry within 1 minute, the delete should still be pending.
  ASSERT_TRUE(footprints_fetcher_->ContainsKey(kAccountKey1));
  ASSERT_TRUE(
      saved_device_registry_->IsAccountKeySavedToRegistry(kAccountKey1));
  ASSERT_EQ(1u, pending_write_store_->GetPendingDeletes().size());

  // Mock waiting out the 1 minute timeout.
  task_environment()->FastForwardBy(base::Minutes(1));
  base::RunLoop().RunUntilIdle();

  // Reconnect to the Network, but after the 1 minute timeout.
  footprints_fetcher_->SetDeleteUserDeviceResult(true);
  fast_pair_repository_->DefaultNetworkChanged(
      helper_.network_state_handler()->DefaultNetwork());
  base::RunLoop().RunUntilIdle();

  // The delete, after a successful retry, should no longer be pending.
  ASSERT_FALSE(footprints_fetcher_->ContainsKey(kAccountKey1));
  ASSERT_FALSE(
      saved_device_registry_->IsAccountKeySavedToRegistry(kAccountKey1));
  ASSERT_EQ(0u, pending_write_store_->GetPendingDeletes().size());
}

// TODO(crbug.com/40264951): Re-enable this test
#if defined(MEMORY_SANITIZER)
#define MAYBE_RetriesForgetDevice_AlreadyDeleted \
  DISABLED_RetriesForgetDevice_AlreadyDeleted
#else
#define MAYBE_RetriesForgetDevice_AlreadyDeleted \
  RetriesForgetDevice_AlreadyDeleted
#endif
TEST_F(FastPairRepositoryImplTest, MAYBE_RetriesForgetDevice_AlreadyDeleted) {
  auto device = base::MakeRefCounted<Device>(kValidModelId, kTestBLEAddress,
                                             Protocol::kFastPairInitial);
  device->set_classic_address(kTestClassicAddress1);
  device->set_account_key(kAccountKey1);
  device->set_display_name(kDeviceDisplayName);
  fast_pair_repository_->WriteAccountAssociationToLocalRegistry(device);
  fast_pair_repository_->WriteAccountAssociationToFootprints(device,
                                                             kAccountKey1);
  base::RunLoop().RunUntilIdle();

  ASSERT_TRUE(footprints_fetcher_->ContainsKey(kAccountKey1));
  ASSERT_TRUE(
      saved_device_registry_->IsAccountKeySavedToRegistry(kAccountKey1));
  ASSERT_EQ(0u, pending_write_store_->GetPendingDeletes().size());

  base::MockCallback<base::OnceCallback<void(bool)>> callback;
  EXPECT_CALL(callback, Run(testing::Eq(false))).Times(1);

  // Mock an error due to Network failure.
  footprints_fetcher_->SetDeleteUserDeviceResult(false);
  fast_pair_repository_->DeleteAssociatedDevice(
      classic_bluetooth_device_.GetAddress(), callback.Get());
  base::RunLoop().RunUntilIdle();

  ASSERT_TRUE(footprints_fetcher_->ContainsKey(kAccountKey1));
  ASSERT_TRUE(
      saved_device_registry_->IsAccountKeySavedToRegistry(kAccountKey1));
  ASSERT_EQ(1u, pending_write_store_->GetPendingDeletes().size());

  // Mock Footprints getting updated by another CB/Android such that the
  // saved device is successfully deleted.
  footprints_fetcher_->SetDeleteUserDeviceResult(true);
  footprints_fetcher_->DeleteUserDevice(
      base::HexEncode(
          std::vector<uint8_t>(kAccountKey1.begin(), kAccountKey1.end())),
      base::DoNothing());
  base::RunLoop().RunUntilIdle();

  ASSERT_FALSE(footprints_fetcher_->ContainsKey(kAccountKey1));
  ASSERT_TRUE(
      saved_device_registry_->IsAccountKeySavedToRegistry(kAccountKey1));
  ASSERT_EQ(1u, pending_write_store_->GetPendingDeletes().size());

  // Reconnect to the Network.
  fast_pair_repository_->DefaultNetworkChanged(
      helper_.network_state_handler()->DefaultNetwork());
  base::RunLoop().RunUntilIdle();

  // The delete, after a successful retry, should no longer be pending.
  ASSERT_FALSE(footprints_fetcher_->ContainsKey(kAccountKey1));
  ASSERT_FALSE(
      saved_device_registry_->IsAccountKeySavedToRegistry(kAccountKey1));
  ASSERT_EQ(0u, pending_write_store_->GetPendingDeletes().size());
}

TEST_F(FastPairRepositoryImplTest, RetriesForgetDevice_MultipleDevices) {
  auto device1 = base::MakeRefCounted<Device>(kValidModelId, kTestBLEAddress,
                                              Protocol::kFastPairInitial);
  device1->set_classic_address(kTestClassicAddress1);
  device1->set_account_key(kAccountKey1);
  device1->set_display_name(kDeviceDisplayName);
  fast_pair_repository_->WriteAccountAssociationToFootprints(device1,
                                                             kAccountKey1);
  fast_pair_repository_->WriteAccountAssociationToLocalRegistry(device1);
  base::RunLoop().RunUntilIdle();

  ASSERT_TRUE(footprints_fetcher_->ContainsKey(kAccountKey1));
  ASSERT_TRUE(
      saved_device_registry_->IsAccountKeySavedToRegistry(kAccountKey1));

  auto device2 = base::MakeRefCounted<Device>(kValidModelId, kTestBLEAddress2,
                                              Protocol::kFastPairInitial);
  device2->set_classic_address(kTestClassicAddress2);
  device2->set_account_key(kAccountKey2);
  device2->set_display_name(kDeviceDisplayName);
  fast_pair_repository_->WriteAccountAssociationToFootprints(device2,
                                                             kAccountKey2);
  fast_pair_repository_->WriteAccountAssociationToLocalRegistry(device2);
  base::RunLoop().RunUntilIdle();

  ASSERT_TRUE(footprints_fetcher_->ContainsKey(kAccountKey2));
  ASSERT_TRUE(
      saved_device_registry_->IsAccountKeySavedToRegistry(kAccountKey2));
  ASSERT_EQ(0u, pending_write_store_->GetPendingDeletes().size());

  base::MockCallback<base::OnceCallback<void(bool)>> callback1;
  EXPECT_CALL(callback1, Run(testing::Eq(false))).Times(1);

  // Mock an error due to Network failure for device1.
  footprints_fetcher_->SetDeleteUserDeviceResult(false);
  fast_pair_repository_->DeleteAssociatedDevice(kTestClassicAddress1,
                                                callback1.Get());
  base::RunLoop().RunUntilIdle();

  // The failed delete should be saved as a pending delete.
  ASSERT_TRUE(footprints_fetcher_->ContainsKey(kAccountKey1));
  ASSERT_TRUE(
      saved_device_registry_->IsAccountKeySavedToRegistry(kAccountKey1));
  ASSERT_EQ(1u, pending_write_store_->GetPendingDeletes().size());

  base::MockCallback<base::OnceCallback<void(bool)>> callback2;
  EXPECT_CALL(callback2, Run(testing::Eq(false))).Times(1);

  // Mock an error due to Network failure for device2.
  footprints_fetcher_->SetDeleteUserDeviceResult(false);
  fast_pair_repository_->DeleteAssociatedDevice(kTestClassicAddress2,
                                                callback2.Get());
  base::RunLoop().RunUntilIdle();

  // The failed deletes should be saved as pending deletes.
  ASSERT_TRUE(footprints_fetcher_->ContainsKey(kAccountKey2));
  ASSERT_TRUE(
      saved_device_registry_->IsAccountKeySavedToRegistry(kAccountKey2));
  ASSERT_EQ(2u, pending_write_store_->GetPendingDeletes().size());

  // Reconnect to the Network.
  footprints_fetcher_->SetDeleteUserDeviceResult(true);
  fast_pair_repository_->DefaultNetworkChanged(
      helper_.network_state_handler()->DefaultNetwork());
  base::RunLoop().RunUntilIdle();

  // Both deletes should be retried and removed from pending write store.
  ASSERT_FALSE(footprints_fetcher_->ContainsKey(kAccountKey1));
  ASSERT_FALSE(footprints_fetcher_->ContainsKey(kAccountKey2));
  ASSERT_FALSE(
      saved_device_registry_->IsAccountKeySavedToRegistry(kAccountKey1));
  ASSERT_FALSE(
      saved_device_registry_->IsAccountKeySavedToRegistry(kAccountKey2));
  ASSERT_EQ(0u, pending_write_store_->GetPendingDeletes().size());
}

TEST_F(FastPairRepositoryImplTest, FetchDeviceImages) {
  ASSERT_FALSE(
      device_address_map_->GetModelIdForMacAddress(kTestClassicAddress1));
  ASSERT_FALSE(device_image_store_->GetImagesForDeviceModel(kValidModelId));

  AccountKeyFilter filter(kFilterBytes1, {salt});
  nearby::fastpair::GetObservedDeviceResponse response;
  DeviceMetadata metadata(response, gfx::Image());

  auto device = base::MakeRefCounted<Device>(kValidModelId, kTestBLEAddress,
                                             Protocol::kFastPairInitial);
  device->set_classic_address(kTestClassicAddress1);
  fast_pair_repository_->FetchDeviceImages(device);

  ASSERT_TRUE(
      device_address_map_->GetModelIdForMacAddress(kTestClassicAddress1));
  ASSERT_TRUE(fast_pair_repository_->GetImagesForDevice(kTestClassicAddress1));
}

TEST_F(FastPairRepositoryImplTest, FetchDeviceImagesNoMacAddress) {
  ASSERT_FALSE(
      device_address_map_->GetModelIdForMacAddress(kTestClassicAddress1));
  ASSERT_FALSE(device_image_store_->GetImagesForDeviceModel(kValidModelId));

  AccountKeyFilter filter(kFilterBytes1, {salt});
  nearby::fastpair::GetObservedDeviceResponse response;
  DeviceMetadata metadata(response, gfx::Image());

  // Don't set the classic address of the device, which should result in the
  // mac address to model ID record failin to save.
  auto device = base::MakeRefCounted<Device>(kValidModelId, kTestBLEAddress,
                                             Protocol::kFastPairInitial);
  fast_pair_repository_->FetchDeviceImages(device);

  // We expect that FetchDeviceImages will first be called while the classic
  // address of the device isn't set. Images should still be downloaded and
  // linked to the corresponding model ID.
  ASSERT_FALSE(
      device_address_map_->GetModelIdForMacAddress(kTestClassicAddress1));
  ASSERT_FALSE(fast_pair_repository_->GetImagesForDevice(kTestClassicAddress1));
  ASSERT_TRUE(device_image_store_->GetImagesForDeviceModel(kValidModelId));

  // Mimic the device now completing pairing, which should set the classic
  // address and attempt to FetchDeviceImages a second time.
  device->set_classic_address(kTestClassicAddress1);
  fast_pair_repository_->FetchDeviceImages(device);

  ASSERT_TRUE(
      device_address_map_->GetModelIdForMacAddress(kTestClassicAddress1));
  ASSERT_TRUE(fast_pair_repository_->GetImagesForDevice(kTestClassicAddress1));
}

TEST_F(FastPairRepositoryImplTest, PersistDeviceImages) {
  ASSERT_FALSE(
      device_address_map_->GetModelIdForMacAddress(kTestClassicAddress1));
  ASSERT_FALSE(device_image_store_->GetImagesForDeviceModel(kValidModelId));

  AccountKeyFilter filter(kFilterBytes1, {salt});
  nearby::fastpair::GetObservedDeviceResponse response;
  DeviceMetadata metadata(response, gfx::Image());

  auto device = base::MakeRefCounted<Device>(kValidModelId, kTestBLEAddress,
                                             Protocol::kFastPairInitial);
  device->set_classic_address(kTestClassicAddress1);
  fast_pair_repository_->FetchDeviceImages(device);
  ASSERT_FALSE(
      device_address_map_->HasPersistedRecordsForModelId(kValidModelId));

  // Persisting should succeed, which allows the images to be found even after
  // the local caches are cleared (i.e. on logout).
  ASSERT_TRUE(fast_pair_repository_->PersistDeviceImages(device));

  device_address_map_->RefreshCacheForTest();
  device_image_store_->RefreshCacheForTest();
  ASSERT_TRUE(
      device_address_map_->GetModelIdForMacAddress(kTestClassicAddress1));
  ASSERT_TRUE(fast_pair_repository_->GetImagesForDevice(kTestClassicAddress1));
  ASSERT_TRUE(
      device_address_map_->HasPersistedRecordsForModelId(kValidModelId));
}

TEST_F(FastPairRepositoryImplTest, PersistDeviceImagesNoMacAddress) {
  ASSERT_FALSE(
      device_address_map_->GetModelIdForMacAddress(kTestClassicAddress1));
  ASSERT_FALSE(device_image_store_->GetImagesForDeviceModel(kValidModelId));

  AccountKeyFilter filter(kFilterBytes1, {salt});
  nearby::fastpair::GetObservedDeviceResponse response;
  DeviceMetadata metadata(response, gfx::Image());

  // Don't set the classic address of the device, which should result in the
  // mac address to model ID record failin to save.
  auto device = base::MakeRefCounted<Device>(kValidModelId, kTestBLEAddress,
                                             Protocol::kFastPairInitial);
  fast_pair_repository_->FetchDeviceImages(device);

  // We expect that FetchDeviceImages will first be called while the classic
  // address of the device isn't set. Images should still be downloaded and
  // linked to the corresponding model ID.
  ASSERT_FALSE(
      device_address_map_->GetModelIdForMacAddress(kTestClassicAddress1));
  ASSERT_FALSE(fast_pair_repository_->GetImagesForDevice(kTestClassicAddress1));
  ASSERT_TRUE(device_image_store_->GetImagesForDeviceModel(kValidModelId));

  // Even though there are images saved that we could persist, we should not
  // persist them since we lack the corresponding mac address to model ID
  // mapping.
  ASSERT_FALSE(fast_pair_repository_->PersistDeviceImages(device));

  device_address_map_->RefreshCacheForTest();
  device_image_store_->RefreshCacheForTest();
  ASSERT_FALSE(
      device_address_map_->HasPersistedRecordsForModelId(kValidModelId));
  ASSERT_FALSE(device_image_store_->GetImagesForDeviceModel(kValidModelId));
}

TEST_F(FastPairRepositoryImplTest, EvictDeviceImages) {
  AccountKeyFilter filter(kFilterBytes1, {salt});
  nearby::fastpair::GetObservedDeviceResponse response;
  DeviceMetadata metadata(response, gfx::Image());

  auto device = base::MakeRefCounted<Device>(kValidModelId, kTestBLEAddress,
                                             Protocol::kFastPairInitial);
  device->set_classic_address(kTestClassicAddress1);
  fast_pair_repository_->FetchDeviceImages(device);
  fast_pair_repository_->PersistDeviceImages(device);
  base::RunLoop().RunUntilIdle();

  ASSERT_TRUE(
      device_address_map_->GetModelIdForMacAddress(kTestClassicAddress1));
  ASSERT_TRUE(device_image_store_->GetImagesForDeviceModel(kValidModelId));
  ASSERT_TRUE(
      device_address_map_->HasPersistedRecordsForModelId(kValidModelId));

  fast_pair_repository_->EvictDeviceImages(kTestClassicAddress1);
  ASSERT_FALSE(
      device_address_map_->HasPersistedRecordsForModelId(kValidModelId));

  device_address_map_->RefreshCacheForTest();
  device_image_store_->RefreshCacheForTest();
  ASSERT_FALSE(
      device_address_map_->GetModelIdForMacAddress(kTestClassicAddress1));
  ASSERT_FALSE(device_image_store_->GetImagesForDeviceModel(kValidModelId));
}

// TODO(crbug.com/40264951): Re-enable this test
#if defined(MEMORY_SANITIZER)
#define MAYBE_UpdateOptInStatus_OptedIn DISABLED_UpdateOptInStatus_OptedIn
#else
#define MAYBE_UpdateOptInStatus_OptedIn UpdateOptInStatus_OptedIn
#endif
TEST_F(FastPairRepositoryImplTest, MAYBE_UpdateOptInStatus_OptedIn) {
  base::MockCallback<base::OnceCallback<void(bool)>> callback1;
  EXPECT_CALL(callback1, Run(true)).Times(1);
  fast_pair_repository_->UpdateOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_IN, callback1.Get());
  base::RunLoop().RunUntilIdle();

  base::MockCallback<base::OnceCallback<void(nearby::fastpair::OptInStatus)>>
      callback2;
  EXPECT_CALL(callback2,
              Run(testing::Eq(nearby::fastpair::OptInStatus::STATUS_OPTED_IN)))
      .Times(1);
  fast_pair_repository_->CheckOptInStatus(callback2.Get());
}

TEST_F(FastPairRepositoryImplTest, UpdateOptInStatus_OptedOut) {
  base::MockCallback<base::OnceCallback<void(bool)>> callback1;
  EXPECT_CALL(callback1, Run(true)).Times(1);
  fast_pair_repository_->UpdateOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT, callback1.Get());
  base::RunLoop().RunUntilIdle();

  base::MockCallback<base::OnceCallback<void(nearby::fastpair::OptInStatus)>>
      callback2;
  EXPECT_CALL(callback2,
              Run(testing::Eq(nearby::fastpair::OptInStatus::STATUS_OPTED_OUT)))
      .Times(1);
  fast_pair_repository_->CheckOptInStatus(callback2.Get());
}

// TODO(crbug.com/40264951): Re-enable this test
#if defined(MEMORY_SANITIZER)
#define MAYBE_UpdateOptInStatus_StatusUnknown \
  DISABLED_UpdateOptInStatus_StatusUnknown
#else
#define MAYBE_UpdateOptInStatus_StatusUnknown UpdateOptInStatus_StatusUnknown
#endif
TEST_F(FastPairRepositoryImplTest, MAYBE_UpdateOptInStatus_StatusUnknown) {
  base::MockCallback<base::OnceCallback<void(bool)>> callback1;
  EXPECT_CALL(callback1, Run(true)).Times(1);
  fast_pair_repository_->UpdateOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_UNKNOWN, callback1.Get());
  base::RunLoop().RunUntilIdle();

  base::MockCallback<base::OnceCallback<void(nearby::fastpair::OptInStatus)>>
      callback2;
  EXPECT_CALL(callback2,
              Run(testing::Eq(nearby::fastpair::OptInStatus::STATUS_UNKNOWN)))
      .Times(1);
  fast_pair_repository_->CheckOptInStatus(callback2.Get());
}

TEST_F(FastPairRepositoryImplTest, UpdateOptInStatus_NoFootprintsResponse) {
  footprints_fetcher_->SetGetUserDevicesResponse(std::nullopt);
  base::MockCallback<base::OnceCallback<void(nearby::fastpair::OptInStatus)>>
      callback;
  EXPECT_CALL(callback,
              Run(testing::Eq(nearby::fastpair::OptInStatus::STATUS_UNKNOWN)))
      .Times(1);
  fast_pair_repository_->CheckOptInStatus(callback.Get());
}

// TODO(crbug.com/40264951): Re-enable this test
#if defined(MEMORY_SANITIZER)
#define MAYBE_UpdateOptInStatus_OptedInUpdateFailed \
  DISABLED_UpdateOptInStatus_OptedInUpdateFailed
#else
#define MAYBE_UpdateOptInStatus_OptedInUpdateFailed \
  UpdateOptInStatus_OptedInUpdateFailed
#endif
TEST_F(FastPairRepositoryImplTest,
       MAYBE_UpdateOptInStatus_OptedInUpdateFailed) {
  footprints_fetcher_->SetAddUserFastPairInfoResult(/*add_user_result=*/false);
  base::MockCallback<base::OnceCallback<void(bool)>> callback1;
  EXPECT_CALL(callback1, Run(false)).Times(1);
  fast_pair_repository_->UpdateOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_IN, callback1.Get());
  base::RunLoop().RunUntilIdle();

  base::MockCallback<base::OnceCallback<void(nearby::fastpair::OptInStatus)>>
      callback2;
  EXPECT_CALL(callback2,
              Run(testing::Eq(nearby::fastpair::OptInStatus::STATUS_UNKNOWN)))
      .Times(1);
  fast_pair_repository_->CheckOptInStatus(callback2.Get());
}

TEST_F(FastPairRepositoryImplTest, GetSavedDevices_OptedIn) {
  histogram_tester().ExpectBucketCount(kSavedDeviceGetDevicesResultMetricName,
                                       /*success=*/true, 0);
  histogram_tester().ExpectBucketCount(kSavedDeviceGetDevicesResultMetricName,
                                       /*success=*/false, 0);
  fast_pair_repository_->UpdateOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_IN, base::DoNothing());
  base::RunLoop().RunUntilIdle();
  AccountKeyFilter filter(kFilterBytes1, {salt});
  nearby::fastpair::GetObservedDeviceResponse response;
  DeviceMetadata metadata(response, gfx::Image());

  auto device = base::MakeRefCounted<Device>(kValidModelId, kTestBLEAddress,
                                             Protocol::kFastPairInitial);
  device->set_classic_address(kTestClassicAddress1);
  device->set_display_name(kDeviceDisplayName);
  fast_pair_repository_->WriteAccountAssociationToFootprints(device,
                                                             kAccountKey1);
  base::RunLoop().RunUntilIdle();
  ASSERT_TRUE(footprints_fetcher_->ContainsKey(kAccountKey1));
  base::RunLoop().RunUntilIdle();

  fast_pair_repository_->GetSavedDevices(
      base::BindOnce(&FastPairRepositoryImplTest::GetSavedDevicesCallback,
                     weak_ptr_factory_.GetWeakPtr()));
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(nearby::fastpair::OptInStatus::STATUS_OPTED_IN, status_);
  EXPECT_EQ(1u, devices_.size());
  histogram_tester().ExpectBucketCount(kSavedDeviceGetDevicesResultMetricName,
                                       /*success=*/true, 1);
  histogram_tester().ExpectBucketCount(kSavedDeviceGetDevicesResultMetricName,
                                       /*success=*/false, 0);
}

TEST_F(FastPairRepositoryImplTest, GetSavedDevices_OptedOut) {
  histogram_tester().ExpectBucketCount(kSavedDeviceGetDevicesResultMetricName,
                                       /*success=*/true, 0);
  histogram_tester().ExpectBucketCount(kSavedDeviceGetDevicesResultMetricName,
                                       /*success=*/false, 0);
  fast_pair_repository_->UpdateOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT, base::DoNothing());
  base::RunLoop().RunUntilIdle();
  fast_pair_repository_->GetSavedDevices(
      base::BindOnce(&FastPairRepositoryImplTest::GetSavedDevicesCallback,
                     weak_ptr_factory_.GetWeakPtr()));
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(nearby::fastpair::OptInStatus::STATUS_OPTED_OUT, status_);
  EXPECT_EQ(0u, devices_.size());
  histogram_tester().ExpectBucketCount(kSavedDeviceGetDevicesResultMetricName,
                                       /*success=*/true, 1);
  histogram_tester().ExpectBucketCount(kSavedDeviceGetDevicesResultMetricName,
                                       /*success=*/false, 0);
}

TEST_F(FastPairRepositoryImplTest, GetSavedDevices_OptStatusUnknown) {
  histogram_tester().ExpectBucketCount(kSavedDeviceGetDevicesResultMetricName,
                                       /*success=*/true, 0);
  histogram_tester().ExpectBucketCount(kSavedDeviceGetDevicesResultMetricName,
                                       /*success=*/false, 0);
  fast_pair_repository_->UpdateOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_UNKNOWN, base::DoNothing());
  base::RunLoop().RunUntilIdle();
  fast_pair_repository_->GetSavedDevices(
      base::BindOnce(&FastPairRepositoryImplTest::GetSavedDevicesCallback,
                     weak_ptr_factory_.GetWeakPtr()));
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(nearby::fastpair::OptInStatus::STATUS_UNKNOWN, status_);
  EXPECT_EQ(0u, devices_.size());
  histogram_tester().ExpectBucketCount(kSavedDeviceGetDevicesResultMetricName,
                                       /*success=*/true, 1);
  histogram_tester().ExpectBucketCount(kSavedDeviceGetDevicesResultMetricName,
                                       /*success=*/false, 0);
}

TEST_F(FastPairRepositoryImplTest, GetSavedDevices_MissingResponse) {
  histogram_tester().ExpectBucketCount(kSavedDeviceGetDevicesResultMetricName,
                                       /*success=*/true, 0);
  histogram_tester().ExpectBucketCount(kSavedDeviceGetDevicesResultMetricName,
                                       /*success=*/false, 0);
  footprints_fetcher_->SetGetUserDevicesResponse(std::nullopt);
  fast_pair_repository_->GetSavedDevices(
      base::BindOnce(&FastPairRepositoryImplTest::GetSavedDevicesCallback,
                     weak_ptr_factory_.GetWeakPtr()));
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(nearby::fastpair::OptInStatus::
                STATUS_ERROR_RETRIEVING_FROM_FOOTPRINTS_SERVER,
            status_);
  EXPECT_EQ(0u, devices_.size());
  histogram_tester().ExpectBucketCount(kSavedDeviceGetDevicesResultMetricName,
                                       /*success=*/true, 0);
  histogram_tester().ExpectBucketCount(kSavedDeviceGetDevicesResultMetricName,
                                       /*success=*/false, 1);
}

TEST_F(FastPairRepositoryImplTest,
       IsAccountKeyPairedLocally_SavedLocallyNotPaired) {
  // Simulate a device already saved to the registry. A Fast Pair device can
  // be saved in the registry even if it is not paired locally because
  // the SavedDeviceRegistry  tracks devices that have been Fast paired in the
  // past.
  bool success = saved_device_registry_->SaveAccountAssociation(
      kTestClassicAddress1, kAccountKey1);
  EXPECT_TRUE(success);
  EXPECT_TRUE(
      saved_device_registry_->IsAccountKeySavedToRegistry(kAccountKey1));

  EXPECT_TRUE(fast_pair_repository_->IsAccountKeyPairedLocally(kAccountKey1));
  EXPECT_FALSE(fast_pair_repository_->IsAccountKeyPairedLocally(kAccountKey2));
}

// TODO(crbug.com/40264951): Re-enable this test
#if defined(MEMORY_SANITIZER)
#define MAYBE_IsAccountKeyPairedLocally_PairedNotSavedLocally \
  DISABLED_IsAccountKeyPairedLocally_PairedNotSavedLocally
#else
#define MAYBE_IsAccountKeyPairedLocally_PairedNotSavedLocally \
  IsAccountKeyPairedLocally_PairedNotSavedLocally
#endif
TEST_F(FastPairRepositoryImplTest,
       MAYBE_IsAccountKeyPairedLocally_PairedNotSavedLocally) {
  // Simulate a device saved to a user's account that matches a device paired
  // with devices |adapter_| is loaded with. In the init of the |adapter_|, we
  // mocked `GetDevices` with |device_list_|.
  nearby::fastpair::FastPairInfo info;
  auto* device = info.mutable_device();
  device->set_account_key(
      std::string(kAccountKey1.begin(), kAccountKey1.end()));
  device->set_sha256_account_key_public_address(
      GenerateSha256AccountKeyMacAddress(
          std::string(kAccountKey1.begin(), kAccountKey1.end()),
          kTestClassicAddress1));
  nearby::fastpair::UserReadDevicesResponse response;
  *response.add_fast_pair_info() = info;
  footprints_fetcher_->SetGetUserDevicesResponse(response);

  // We want to simulate the cache being updated when it is parsing a
  // NotDiscoverableAdv, which happens when it is checking an account key.
  AccountKeyFilter filter(kFilterBytes1, {salt});
  auto run_loop = base::RunLoop();
  fast_pair_repository_->CheckAccountKeys(
      filter, base::BindOnce(&FastPairRepositoryImplTest::VerifyAccountKeyCheck,
                             base::Unretained(this), run_loop.QuitClosure(),
                             /*expected_result=*/false));
  run_loop.Run();

  // At this point the cache will be updated with any devices Saved to
  // Footprints. We can continue now checking if it matches any paired devices.
  EXPECT_TRUE(fast_pair_repository_->IsAccountKeyPairedLocally(kAccountKey1));
  EXPECT_FALSE(fast_pair_repository_->IsAccountKeyPairedLocally(kAccountKey2));
}

TEST_F(FastPairRepositoryImplTest, IsDeviceSavedToAccount_Match) {
  nearby::fastpair::FastPairInfo info;
  auto* device = info.mutable_device();
  device->set_account_key(
      std::string(kAccountKey1.begin(), kAccountKey1.end()));
  device->set_sha256_account_key_public_address(
      GenerateSha256AccountKeyMacAddress(
          std::string(kAccountKey1.begin(), kAccountKey1.end()),
          kTestClassicAddress1));
  nearby::fastpair::UserReadDevicesResponse response;
  *response.add_fast_pair_info() = info;
  footprints_fetcher_->SetGetUserDevicesResponse(response);

  base::MockCallback<base::OnceCallback<void(bool)>> callback;
  EXPECT_CALL(callback, Run(testing::Eq(true))).Times(1);
  fast_pair_repository_->IsDeviceSavedToAccount(kTestClassicAddress1,
                                                callback.Get());

  base::RunLoop().RunUntilIdle();
}

TEST_F(FastPairRepositoryImplTest,
       IsDeviceSavedToAccount_MatchKnownAndroidValues) {
  // In this test, we use a known triple from Android to ensure this works
  // cross-platform.
  nearby::fastpair::FastPairInfo info;
  auto* device = info.mutable_device();
  device->set_account_key(Base64Decode(kBase64AccountKey));
  device->set_sha256_account_key_public_address(
      Base64Decode(kBase64ExpectedSha256Hash));
  nearby::fastpair::UserReadDevicesResponse response;
  *response.add_fast_pair_info() = info;
  footprints_fetcher_->SetGetUserDevicesResponse(response);

  base::MockCallback<base::OnceCallback<void(bool)>> callback;
  EXPECT_CALL(callback, Run(testing::Eq(true))).Times(1);
  fast_pair_repository_->IsDeviceSavedToAccount(kTestClassicAddress3,
                                                callback.Get());

  base::RunLoop().RunUntilIdle();
}

// TODO(crbug.com/40264951): Re-enable this test
#if defined(MEMORY_SANITIZER)
#define MAYBE_IsDeviceSavedToAccount_IgnoreForgetPattern \
  DISABLED_IsDeviceSavedToAccount_IgnoreForgetPattern
#else
#define MAYBE_IsDeviceSavedToAccount_IgnoreForgetPattern \
  IsDeviceSavedToAccount_IgnoreForgetPattern
#endif
TEST_F(FastPairRepositoryImplTest,
       MAYBE_IsDeviceSavedToAccount_IgnoreForgetPattern) {
  // In this test, we use a known "marked as forgotten" hash
  // from Android. Note that Android also leaves the account key
  // blank; however, we add an account key here to test the logic
  // of detecting the Forget pattern in the hash.
  nearby::fastpair::FastPairInfo info;
  auto* device = info.mutable_device();
  device->set_account_key(Base64Decode(kBase64AccountKey));
  device->set_sha256_account_key_public_address(
      Base64Decode(kBase64ForgetPatternSha256Hash));
  nearby::fastpair::UserReadDevicesResponse response;
  *response.add_fast_pair_info() = info;
  footprints_fetcher_->SetGetUserDevicesResponse(response);

  base::MockCallback<base::OnceCallback<void(bool)>> callback;
  EXPECT_CALL(callback, Run(testing::Eq(false))).Times(1);
  fast_pair_repository_->IsDeviceSavedToAccount(kTestClassicAddress3,
                                                callback.Get());

  base::RunLoop().RunUntilIdle();
}

TEST_F(FastPairRepositoryImplTest, IsDeviceSavedToAccount_NoMatch) {
  nearby::fastpair::FastPairInfo info;
  auto* device = info.mutable_device();
  device->set_account_key(
      std::string(kAccountKey1.begin(), kAccountKey1.end()));
  device->set_sha256_account_key_public_address(
      GenerateSha256AccountKeyMacAddress(
          std::string(kAccountKey1.begin(), kAccountKey1.end()),
          kTestClassicAddress1));
  nearby::fastpair::UserReadDevicesResponse response;
  *response.add_fast_pair_info() = info;
  footprints_fetcher_->SetGetUserDevicesResponse(response);

  base::MockCallback<base::OnceCallback<void(bool)>> callback;
  EXPECT_CALL(callback, Run(testing::Eq(false))).Times(1);
  fast_pair_repository_->IsDeviceSavedToAccount(kTestClassicAddress2,
                                                callback.Get());

  base::RunLoop().RunUntilIdle();
}

TEST_F(FastPairRepositoryImplTest, IsDeviceSavedToAccount_MissingResponse) {
  footprints_fetcher_->SetGetUserDevicesResponse(std::nullopt);

  base::MockCallback<base::OnceCallback<void(bool)>> callback;
  EXPECT_CALL(callback, Run(testing::Eq(false))).Times(1);
  fast_pair_repository_->IsDeviceSavedToAccount(kTestClassicAddress1,
                                                callback.Get());

  base::RunLoop().RunUntilIdle();
}

TEST_F(FastPairRepositoryImplTest, IsDeviceSavedToAccount_MissingAccountKey) {
  nearby::fastpair::FastPairInfo info;
  auto* device = info.mutable_device();
  device->set_sha256_account_key_public_address(
      GenerateSha256AccountKeyMacAddress(
          std::string(kAccountKey1.begin(), kAccountKey1.end()),
          kTestClassicAddress1));
  nearby::fastpair::UserReadDevicesResponse response;
  *response.add_fast_pair_info() = info;
  footprints_fetcher_->SetGetUserDevicesResponse(response);

  base::MockCallback<base::OnceCallback<void(bool)>> callback;
  EXPECT_CALL(callback, Run(testing::Eq(false))).Times(1);
  fast_pair_repository_->IsDeviceSavedToAccount(kTestClassicAddress1,
                                                callback.Get());

  base::RunLoop().RunUntilIdle();
}

// TODO(crbug.com/40264951): Re-enable this test
#if defined(MEMORY_SANITIZER)
#define MAYBE_IsDeviceSavedToAccount_MissingSha \
  DISABLED_IsDeviceSavedToAccount_MissingSha
#else
#define MAYBE_IsDeviceSavedToAccount_MissingSha \
  IsDeviceSavedToAccount_MissingSha
#endif
TEST_F(FastPairRepositoryImplTest, MAYBE_IsDeviceSavedToAccount_MissingSha) {
  nearby::fastpair::FastPairInfo info;
  auto* device = info.mutable_device();
  device->set_account_key(
      std::string(kAccountKey1.begin(), kAccountKey1.end()));
  nearby::fastpair::UserReadDevicesResponse response;
  *response.add_fast_pair_info() = info;
  footprints_fetcher_->SetGetUserDevicesResponse(response);

  base::MockCallback<base::OnceCallback<void(bool)>> callback;
  EXPECT_CALL(callback, Run(testing::Eq(false))).Times(1);
  fast_pair_repository_->IsDeviceSavedToAccount(kTestClassicAddress1,
                                                callback.Get());

  base::RunLoop().RunUntilIdle();
}

TEST_F(FastPairRepositoryImplTest,
       IsDeviceSavedToAccount_MissingShaAccountKey) {
  nearby::fastpair::FastPairInfo info;
  nearby::fastpair::UserReadDevicesResponse response;
  *response.add_fast_pair_info() = info;
  footprints_fetcher_->SetGetUserDevicesResponse(response);

  base::MockCallback<base::OnceCallback<void(bool)>> callback;
  EXPECT_CALL(callback, Run(testing::Eq(false))).Times(1);
  fast_pair_repository_->IsDeviceSavedToAccount(kTestClassicAddress1,
                                                callback.Get());

  base::RunLoop().RunUntilIdle();
}

TEST_F(FastPairRepositoryImplTest, IsHashCorrect) {
  nearby::fastpair::GetObservedDeviceResponse device;
  DeviceMetadata metadata(device, gfx::Image());
  std::string account_key = Base64Decode(kBase64AccountKey);
  std::vector<uint8_t> account_key_bytes(account_key.begin(),
                                         account_key.end());

  // FakeFootprintsFetcher APIs are actually synchronous.
  footprints_fetcher_->AddUserFastPairInfo(
      BuildFastPairInfo(kValidModelId, account_key_bytes, kTestClassicAddress3,
                        kDeviceDisplayName, &metadata),
      base::DoNothing());

  base::MockCallback<base::OnceCallback<void(bool)>> callback;
  EXPECT_CALL(callback, Run(testing::Eq(true))).Times(1);
  fast_pair_repository_->IsDeviceSavedToAccount(kTestClassicAddress3,
                                                callback.Get());

  base::RunLoop().RunUntilIdle();
}

TEST_F(FastPairRepositoryImplTest,
       WriteAccountAssociationToFootprints_RemoveDeviceFromPendingWriteStore) {
  auto device = base::MakeRefCounted<Device>(kValidModelId, kTestBLEAddress,
                                             Protocol::kFastPairInitial);
  device->set_classic_address(kTestClassicAddress1);
  device->set_display_name(kDeviceDisplayName);
  fast_pair_repository_->WriteAccountAssociationToFootprints(device,
                                                             kAccountKey1);
  base::RunLoop().RunUntilIdle();
  ASSERT_TRUE(footprints_fetcher_->ContainsKey(kAccountKey1));

  // After a successful Footprints write, pending writes list should be empty.
  ASSERT_EQ(0u, pending_write_store_->GetPendingWrites().size());
}

// TODO(crbug.com/40264951): Re-enable this test
#if defined(MEMORY_SANITIZER)
#define MAYBE_RetriesWriteDevice_AfterNetworkAvailable \
  DISABLED_RetriesWriteDevice_AfterNetworkAvailable
#else
#define MAYBE_RetriesWriteDevice_AfterNetworkAvailable \
  RetriesWriteDevice_AfterNetworkAvailable
#endif
TEST_F(FastPairRepositoryImplTest,
       MAYBE_RetriesWriteDevice_AfterNetworkAvailable) {
  auto device = base::MakeRefCounted<Device>(kValidModelId, kTestBLEAddress,
                                             Protocol::kFastPairInitial);
  device->set_classic_address(kTestClassicAddress1);

  // Mock an error due to Network failure.
  footprints_fetcher_->SetAddUserFastPairInfoResult(false);
  device->set_display_name(kDeviceDisplayName);
  fast_pair_repository_->WriteAccountAssociationToFootprints(device,
                                                             kAccountKey1);

  base::RunLoop().RunUntilIdle();

  // The failed write should save as pending write.
  std::vector<PendingWriteStore::PendingWrite> pending_writes =
      pending_write_store_->GetPendingWrites();
  ASSERT_EQ(1u, pending_writes.size());
  ASSERT_FALSE(footprints_fetcher_->ContainsKey(kAccountKey1));

  // Parse device account key from device fast pair info.
  const std::string& account_key_str =
      pending_writes[0].fast_pair_info.device().account_key();
  std::vector<uint8_t> account_key =
      std::vector<uint8_t>{account_key_str.begin(), account_key_str.end()};

  // Check that account key retrieved from PendingWrite is the same as passed
  // into PendingWrite.
  ASSERT_EQ(kAccountKey1, account_key);

  // Reconnect to the Network, but fail again because |footprints_fetcher_| is
  // still stubbed to fail.
  fast_pair_repository_->DefaultNetworkChanged(
      helper_.network_state_handler()->DefaultNetwork());
  base::RunLoop().RunUntilIdle();

  // The write should still be pending after a failed retry.
  ASSERT_EQ(1u, pending_write_store_->GetPendingWrites().size());
  ASSERT_FALSE(footprints_fetcher_->ContainsKey(kAccountKey1));

  // Reconnect to the Network, but within the 1 minute timeout.
  footprints_fetcher_->SetAddUserFastPairInfoResult(true);
  fast_pair_repository_->DefaultNetworkChanged(
      helper_.network_state_handler()->DefaultNetwork());
  base::RunLoop().RunUntilIdle();

  // Since we don't try within 1 minute, the write should still be pending.
  ASSERT_EQ(1u, pending_write_store_->GetPendingWrites().size());
  ASSERT_FALSE(footprints_fetcher_->ContainsKey(kAccountKey1));

  // Mock waiting out the 1 minute timeout.
  task_environment()->FastForwardBy(base::Minutes(1));
  base::RunLoop().RunUntilIdle();

  // Reconnect to the Network, but after the 1 minute timeout.
  footprints_fetcher_->SetAddUserFastPairInfoResult(true);
  fast_pair_repository_->DefaultNetworkChanged(
      helper_.network_state_handler()->DefaultNetwork());
  base::RunLoop().RunUntilIdle();

  // The write, after a successful retry, should no longer be pending.
  ASSERT_EQ(0u, pending_write_store_->GetPendingWrites().size());
  ASSERT_TRUE(footprints_fetcher_->ContainsKey(kAccountKey1));
}

TEST_F(FastPairRepositoryImplTest,
       RetryWriteRetroactivePair_DoesntRecordMetric) {
  auto device = base::MakeRefCounted<Device>(kValidModelId, kTestBLEAddress,
                                             Protocol::kFastPairRetroactive);
  device->set_classic_address(kTestClassicAddress1);
  device->set_display_name(kDeviceDisplayName);

  // Mock an error due to Network failure.
  footprints_fetcher_->SetAddUserFastPairInfoResult(false);
  fast_pair_repository_->WriteAccountAssociationToFootprints(device,
                                                             kAccountKey1);

  base::RunLoop().RunUntilIdle();

  // The failed write should save as pending write.
  std::vector<PendingWriteStore::PendingWrite> pending_writes =
      pending_write_store_->GetPendingWrites();
  ASSERT_EQ(1u, pending_writes.size());
  ASSERT_FALSE(footprints_fetcher_->ContainsKey(kAccountKey1));

  // Mock waiting out the 1 minute timeout.
  task_environment()->FastForwardBy(base::Minutes(1));
  base::RunLoop().RunUntilIdle();

  // Reconnect to the Network, but after the 1 minute timeout.
  footprints_fetcher_->SetAddUserFastPairInfoResult(true);
  fast_pair_repository_->DefaultNetworkChanged(
      helper_.network_state_handler()->DefaultNetwork());
  base::RunLoop().RunUntilIdle();

  // The write, after a successful retry, should no longer be pending.
  ASSERT_EQ(0u, pending_write_store_->GetPendingWrites().size());
  ASSERT_TRUE(footprints_fetcher_->ContainsKey(kAccountKey1));

  // A pending write for retroactive pairing does not log a success in the
  // metrics.
  EXPECT_EQ(histogram_tester().GetBucketCount(
                kRetroactiveSuccessFunnelMetric,
                FastPairRetroactiveSuccessFunnelEvent::kSaveComplete),
            0);
}

// `WriteAccountAssociationToFootprints()` previously wrote the association
// locally as well. This unit test ensures it does not anymore.
TEST_F(FastPairRepositoryImplTest,
       AccountAssociationWriteToFootprints_NoLocalWrite) {
  auto device = base::MakeRefCounted<Device>(kValidModelId, kTestBLEAddress,
                                             Protocol::kFastPairRetroactive);
  device->set_classic_address(kTestClassicAddress1);
  device->set_display_name(kDeviceDisplayName);
  fast_pair_repository_->WriteAccountAssociationToFootprints(device,
                                                             kAccountKey1);
  ASSERT_TRUE(footprints_fetcher_->ContainsKey(kAccountKey1));
  ASSERT_FALSE(
      saved_device_registry_->IsAccountKeySavedToRegistry(kAccountKey1));
}

}  // namespace quick_pair
}  // namespace ash
