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

#include "chromeos/components/tether/ble_service_data_helper_impl.h"

#include <memory>

#include "base/macros.h"
#include "base/test/scoped_feature_list.h"
#include "chromeos/chromeos_features.h"
#include "chromeos/components/tether/fake_tether_host_fetcher.h"
#include "chromeos/services/device_sync/public/cpp/fake_device_sync_client.h"
#include "chromeos/services/secure_channel/device_id_pair.h"
#include "components/cryptauth/ble/ble_advertisement_generator.h"
#include "components/cryptauth/ble/fake_ble_advertisement_generator.h"
#include "components/cryptauth/fake_background_eid_generator.h"
#include "components/cryptauth/mock_foreground_eid_generator.h"
#include "components/cryptauth/mock_local_device_data_provider.h"
#include "components/cryptauth/remote_device_cache.h"
#include "components/cryptauth/remote_device_test_util.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace chromeos {

namespace tether {

namespace {

const size_t kNumTestDevices = 3;

const size_t kNumBytesInBackgroundAdvertisementServiceData = 2;
const size_t kMinNumBytesInForegroundAdvertisementServiceData = 4;

const char current_eid_data[] = "currentEidData";
const int64_t current_eid_start_ms = 1000L;
const int64_t current_eid_end_ms = 2000L;

const char adjacent_eid_data[] = "adjacentEidData";
const int64_t adjacent_eid_start_ms = 2000L;
const int64_t adjacent_eid_end_ms = 3000L;

const char fake_beacon_seed1_data[] = "fakeBeaconSeed1Data";
const int64_t fake_beacon_seed1_start_ms = current_eid_start_ms;
const int64_t fake_beacon_seed1_end_ms = current_eid_end_ms;

const char fake_beacon_seed2_data[] = "fakeBeaconSeed2Data";
const int64_t fake_beacon_seed2_start_ms = adjacent_eid_start_ms;
const int64_t fake_beacon_seed2_end_ms = adjacent_eid_end_ms;

std::unique_ptr<cryptauth::ForegroundEidGenerator::EidData>
CreateFakeBackgroundScanFilter() {
  cryptauth::DataWithTimestamp current(current_eid_data, current_eid_start_ms,
                                       current_eid_end_ms);

  std::unique_ptr<cryptauth::DataWithTimestamp> adjacent =
      std::make_unique<cryptauth::DataWithTimestamp>(
          adjacent_eid_data, adjacent_eid_start_ms, adjacent_eid_end_ms);

  return std::make_unique<cryptauth::ForegroundEidGenerator::EidData>(
      current, std::move(adjacent));
}

std::vector<cryptauth::BeaconSeed> CreateFakeBeaconSeeds() {
  cryptauth::BeaconSeed seed1;
  seed1.set_data(fake_beacon_seed1_data);
  seed1.set_start_time_millis(fake_beacon_seed1_start_ms);
  seed1.set_start_time_millis(fake_beacon_seed1_end_ms);

  cryptauth::BeaconSeed seed2;
  seed2.set_data(fake_beacon_seed2_data);
  seed2.set_start_time_millis(fake_beacon_seed2_start_ms);
  seed2.set_start_time_millis(fake_beacon_seed2_end_ms);

  std::vector<cryptauth::BeaconSeed> seeds = {seed1, seed2};
  return seeds;
}

cryptauth::RemoteDeviceRef CreateLocalDevice() {
  return cryptauth::RemoteDeviceRefBuilder()
      .SetPublicKey("local public key")
      .SetBeaconSeeds(CreateFakeBeaconSeeds())
      .Build();
}

}  // namespace

class BleServiceDataHelperImplTest : public testing::Test {
 protected:
  BleServiceDataHelperImplTest()
      : test_local_device_(CreateLocalDevice()),
        test_remote_devices_(
            cryptauth::CreateRemoteDeviceRefListForTest(kNumTestDevices)),
        fake_advertisement_(
            cryptauth::DataWithTimestamp("advertisement1", 1000L, 2000L)) {
    std::transform(test_remote_devices_.begin(), test_remote_devices_.end(),
                   std::back_inserter(test_remote_device_ids_),
                   [this](const auto& device) { return device.GetDeviceId(); });
  };
  ~BleServiceDataHelperImplTest() override = default;

  // testing::Test:
  void SetUp() override {
    fake_ble_advertisement_generator_ =
        std::make_unique<cryptauth::FakeBleAdvertisementGenerator>();
    cryptauth::BleAdvertisementGenerator::SetInstanceForTesting(
        fake_ble_advertisement_generator_.get());
    fake_ble_advertisement_generator_->set_advertisement(
        std::make_unique<cryptauth::DataWithTimestamp>(fake_advertisement_));

    fake_tether_host_fetcher_ = std::make_unique<FakeTetherHostFetcher>();
    fake_tether_host_fetcher_->set_tether_hosts({test_remote_devices_[0],
                                                 test_remote_devices_[1],
                                                 test_remote_devices_[2]});

    mock_local_device_data_provider_ =
        std::make_unique<cryptauth::MockLocalDeviceDataProvider>();
    mock_local_device_data_provider_->SetBeaconSeeds(
        std::make_unique<std::vector<cryptauth::BeaconSeed>>(
            test_local_device_.beacon_seeds()));

    fake_device_sync_client_ =
        std::make_unique<device_sync::FakeDeviceSyncClient>();

    mock_foreground_eid_generator_ =
        new cryptauth::MockForegroundEidGenerator();
    mock_foreground_eid_generator_->set_background_scan_filter(
        CreateFakeBackgroundScanFilter());
    fake_background_eid_generator_ =
        new cryptauth::FakeBackgroundEidGenerator();
  }

  void TearDown() override {
    cryptauth::BleAdvertisementGenerator::SetInstanceForTesting(nullptr);
  }

  void InitializeTest() {
    helper_ = base::WrapUnique(new BleServiceDataHelperImpl(
        fake_tether_host_fetcher_.get(),
        base::FeatureList::IsEnabled(chromeos::features::kMultiDeviceApi)
            ? nullptr
            : mock_local_device_data_provider_.get(),
        base::FeatureList::IsEnabled(chromeos::features::kMultiDeviceApi)
            ? fake_device_sync_client_.get()
            : nullptr));

    helper_->SetTestDoubles(base::WrapUnique(fake_background_eid_generator_),
                            base::WrapUnique(mock_foreground_eid_generator_));

    SetLocalDeviceMetadata(test_local_device_);
  }

  void SetMultiDeviceApiEnabled() {
    scoped_feature_list_.InitAndEnableFeature(features::kMultiDeviceApi);
  }

  void SetLocalDeviceMetadata(
      base::Optional<cryptauth::RemoteDeviceRef> local_device) {
    if (base::FeatureList::IsEnabled(chromeos::features::kMultiDeviceApi)) {
      fake_device_sync_client_->set_local_device_metadata(local_device);
    } else {
      mock_local_device_data_provider_->SetPublicKey(
          local_device
              ? std::make_unique<std::string>(local_device->public_key())
              : nullptr);
    }
  }

  void TestIdentifyRemoteDevice_InvalidAdvertisementLength() {
    InitializeTest();

    std::string invalid_service_data = "a";
    mock_foreground_eid_generator_->set_identified_device_id(
        test_remote_devices_[0].GetDeviceId());

    auto device_with_background_bool = helper_->IdentifyRemoteDevice(
        invalid_service_data, test_remote_device_ids_);

    EXPECT_EQ(0, mock_foreground_eid_generator_->num_identify_calls());
    EXPECT_EQ(0, fake_background_eid_generator_->num_identify_calls());
    EXPECT_FALSE(device_with_background_bool);
  }

  void TestIdentifyRemoteDevice_ForegroundAdvertisement() {
    InitializeTest();

    std::string valid_service_data_for_registered_device = "abcde";
    ASSERT_TRUE(valid_service_data_for_registered_device.size() >=
                kMinNumBytesInForegroundAdvertisementServiceData);

    mock_foreground_eid_generator_->set_identified_device_id(
        test_remote_devices_[0].GetDeviceId());

    auto device_with_background_bool = helper_->IdentifyRemoteDevice(
        valid_service_data_for_registered_device, test_remote_device_ids_);

    EXPECT_EQ(1, mock_foreground_eid_generator_->num_identify_calls());
    EXPECT_EQ(0, fake_background_eid_generator_->num_identify_calls());
    EXPECT_EQ(test_remote_devices_[0].GetDeviceId(),
              device_with_background_bool->first.GetDeviceId());
    EXPECT_FALSE(device_with_background_bool->second);
  }

  void TestIdentifyRemoteDevice_ForegroundAdvertisement_NoRegisteredDevice() {
    InitializeTest();

    std::string valid_service_data = "abcde";
    ASSERT_TRUE(valid_service_data.size() >=
                kMinNumBytesInForegroundAdvertisementServiceData);

    auto device_with_background_bool = helper_->IdentifyRemoteDevice(
        valid_service_data, test_remote_device_ids_);

    EXPECT_EQ(1, mock_foreground_eid_generator_->num_identify_calls());
    EXPECT_EQ(0, fake_background_eid_generator_->num_identify_calls());
    EXPECT_FALSE(device_with_background_bool);
  }

  void TestIdentifyRemoteDevice_BackgroundAdvertisement() {
    InitializeTest();

    std::string valid_service_data_for_registered_device = "ab";
    ASSERT_TRUE(valid_service_data_for_registered_device.size() >=
                kNumBytesInBackgroundAdvertisementServiceData);

    fake_background_eid_generator_->set_identified_device_id(
        test_remote_devices_[0].GetDeviceId());

    auto device_with_background_bool = helper_->IdentifyRemoteDevice(
        valid_service_data_for_registered_device, test_remote_device_ids_);

    EXPECT_EQ(0, mock_foreground_eid_generator_->num_identify_calls());
    EXPECT_EQ(1, fake_background_eid_generator_->num_identify_calls());
    EXPECT_EQ(test_remote_devices_[0].GetDeviceId(),
              device_with_background_bool->first.GetDeviceId());
    EXPECT_TRUE(device_with_background_bool->second);
  }

  void TestIdentifyRemoteDevice_BackgroundAdvertisement_NoRegisteredDevice() {
    InitializeTest();

    std::string valid_service_data_for_registered_device = "ab";
    ASSERT_TRUE(valid_service_data_for_registered_device.size() >=
                kNumBytesInBackgroundAdvertisementServiceData);

    auto device_with_background_bool = helper_->IdentifyRemoteDevice(
        valid_service_data_for_registered_device, test_remote_device_ids_);

    EXPECT_EQ(0, mock_foreground_eid_generator_->num_identify_calls());
    EXPECT_EQ(1, fake_background_eid_generator_->num_identify_calls());
    EXPECT_FALSE(device_with_background_bool);
  }

  std::unique_ptr<cryptauth::DataWithTimestamp>
  CallGenerateForegroundAdvertisement(const std::string& remote_device_id) {
    return helper_->GenerateForegroundAdvertisement(
        secure_channel::DeviceIdPair(remote_device_id,
                                     std::string() /* local_device_id */));
  }

  std::unique_ptr<cryptauth::FakeBleAdvertisementGenerator>
      fake_ble_advertisement_generator_;
  std::unique_ptr<FakeTetherHostFetcher> fake_tether_host_fetcher_;
  std::unique_ptr<cryptauth::MockLocalDeviceDataProvider>
      mock_local_device_data_provider_;
  std::unique_ptr<device_sync::FakeDeviceSyncClient> fake_device_sync_client_;

  cryptauth::MockForegroundEidGenerator* mock_foreground_eid_generator_;
  cryptauth::FakeBackgroundEidGenerator* fake_background_eid_generator_;

  std::unique_ptr<BleServiceDataHelperImpl> helper_;

  cryptauth::RemoteDeviceRef test_local_device_;
  cryptauth::RemoteDeviceRefList test_remote_devices_;
  std::vector<std::string> test_remote_device_ids_;
  cryptauth::DataWithTimestamp fake_advertisement_;

 private:
  base::test::ScopedFeatureList scoped_feature_list_;

  DISALLOW_COPY_AND_ASSIGN(BleServiceDataHelperImplTest);
};

TEST_F(BleServiceDataHelperImplTest,
       TestGenerateForegroundAdvertisement_CannotGenerateAdvertisement) {
  InitializeTest();
  fake_ble_advertisement_generator_->set_advertisement(nullptr);
  EXPECT_FALSE(CallGenerateForegroundAdvertisement(
      test_remote_devices_[1].GetDeviceId()));
}

TEST_F(
    BleServiceDataHelperImplTest,
    TestGenerateForegroundAdvertisement_CannotGenerateAdvertisement_MultideviceApiEnabled) {
  SetMultiDeviceApiEnabled();

  InitializeTest();
  fake_ble_advertisement_generator_->set_advertisement(nullptr);
  EXPECT_FALSE(CallGenerateForegroundAdvertisement(
      test_remote_devices_[1].GetDeviceId()));
}

TEST_F(BleServiceDataHelperImplTest, TestGenerateForegroundAdvertisement) {
  InitializeTest();
  auto data_with_timestamp = CallGenerateForegroundAdvertisement(
      test_remote_devices_[1].GetDeviceId());
  EXPECT_EQ(fake_advertisement_, *data_with_timestamp);
}

TEST_F(BleServiceDataHelperImplTest,
       TestGenerateForegroundAdvertisement_MultideviceApiEnabled) {
  SetMultiDeviceApiEnabled();

  InitializeTest();
  auto data_with_timestamp = CallGenerateForegroundAdvertisement(
      test_remote_devices_[1].GetDeviceId());
  EXPECT_EQ(fake_advertisement_, *data_with_timestamp);
}

TEST_F(BleServiceDataHelperImplTest,
       TestGenerateForegroundAdvertisement_NoLocalDeviceMetadata) {
  InitializeTest();
  SetLocalDeviceMetadata(base::nullopt);
  EXPECT_FALSE(CallGenerateForegroundAdvertisement(
      test_remote_devices_[0].GetDeviceId()));
}

TEST_F(
    BleServiceDataHelperImplTest,
    TestGenerateForegroundAdvertisement_NoLocalDeviceMetadata_MultideviceApiEnabled) {
  SetMultiDeviceApiEnabled();

  InitializeTest();
  SetLocalDeviceMetadata(base::nullopt);
  EXPECT_FALSE(CallGenerateForegroundAdvertisement(
      test_remote_devices_[0].GetDeviceId()));
}

TEST_F(BleServiceDataHelperImplTest,
       TestGenerateForegroundAdvertisement_EmptyLocalPublicKey) {
  InitializeTest();

  SetLocalDeviceMetadata(
      cryptauth::RemoteDeviceRefBuilder().SetPublicKey(std::string()).Build());
  EXPECT_FALSE(CallGenerateForegroundAdvertisement(
      test_remote_devices_[0].GetDeviceId()));
}

TEST_F(
    BleServiceDataHelperImplTest,
    TestGenerateForegroundAdvertisement_EmptyLocalPublicKey_MultideviceApiEnabled) {
  SetMultiDeviceApiEnabled();

  InitializeTest();
  SetLocalDeviceMetadata(
      cryptauth::RemoteDeviceRefBuilder().SetPublicKey(std::string()).Build());
  EXPECT_FALSE(CallGenerateForegroundAdvertisement(
      test_remote_devices_[0].GetDeviceId()));
}

TEST_F(BleServiceDataHelperImplTest,
       TestGenerateForegroundAdvertisement_RemoteDeviceNotInTetherHostList) {
  InitializeTest();
  EXPECT_FALSE(CallGenerateForegroundAdvertisement("invalid device id"));
}

TEST_F(
    BleServiceDataHelperImplTest,
    TestGenerateForegroundAdvertisement_RemoteDeviceNotInTetherHostList_MultideviceApiEnabled) {
  SetMultiDeviceApiEnabled();

  InitializeTest();
  EXPECT_FALSE(CallGenerateForegroundAdvertisement("invalid device id"));
}

TEST_F(BleServiceDataHelperImplTest,
       TestIdentifyRemoteDevice_InvalidAdvertisementLength) {
  TestIdentifyRemoteDevice_InvalidAdvertisementLength();
}

TEST_F(
    BleServiceDataHelperImplTest,
    TestIdentifyRemoteDevice_InvalidAdvertisementLength_MultideviceApiEnabled) {
  SetMultiDeviceApiEnabled();
  TestIdentifyRemoteDevice_InvalidAdvertisementLength();
}

TEST_F(BleServiceDataHelperImplTest,
       TestIdentifyRemoteDevice_ForegroundAdvertisement) {
  TestIdentifyRemoteDevice_ForegroundAdvertisement();
}

TEST_F(BleServiceDataHelperImplTest,
       TestIdentifyRemoteDevice_ForegroundAdvertisement_MultideviceApiEnabled) {
  SetMultiDeviceApiEnabled();
  TestIdentifyRemoteDevice_ForegroundAdvertisement();
}

TEST_F(BleServiceDataHelperImplTest,
       TestIdentifyRemoteDevice_ForegroundAdvertisement_NoRegisteredDevice) {
  TestIdentifyRemoteDevice_ForegroundAdvertisement_NoRegisteredDevice();
}

TEST_F(
    BleServiceDataHelperImplTest,
    TestIdentifyRemoteDevice_ForegroundAdvertisement_NoRegisteredDevice_MultideviceApiEnabled) {
  SetMultiDeviceApiEnabled();
  TestIdentifyRemoteDevice_ForegroundAdvertisement_NoRegisteredDevice();
}

TEST_F(BleServiceDataHelperImplTest,
       TestIdentifyRemoteDevice_BackgroundAdvertisement) {
  TestIdentifyRemoteDevice_BackgroundAdvertisement();
}

TEST_F(BleServiceDataHelperImplTest,
       TestIdentifyRemoteDevice_BackgroundAdvertisement_MultideviceApiEnabled) {
  SetMultiDeviceApiEnabled();
  TestIdentifyRemoteDevice_BackgroundAdvertisement();
}

TEST_F(BleServiceDataHelperImplTest,
       TestIdentifyRemoteDevice_BackgroundAdvertisement_NoRegisteredDevice) {
  TestIdentifyRemoteDevice_BackgroundAdvertisement_NoRegisteredDevice();
}

TEST_F(
    BleServiceDataHelperImplTest,
    TestIdentifyRemoteDevice_BackgroundAdvertisement_NoRegisteredDevice_MultideviceApiEnabled) {
  SetMultiDeviceApiEnabled();
  TestIdentifyRemoteDevice_BackgroundAdvertisement_NoRegisteredDevice();
}

}  // namespace tether

}  // namespace chromeos
