// 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/services/multidevice_setup/host_device_timestamp_manager_impl.h"

#include "base/memory/ptr_util.h"
#include "base/no_destructor.h"
#include "base/time/clock.h"
#include "chromeos/components/proximity_auth/logging/logging.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"

namespace chromeos {

namespace multidevice_setup {

namespace {
const int64_t kTimestampNotSet = 0;
}  // namespace

// static
HostDeviceTimestampManagerImpl::Factory*
    HostDeviceTimestampManagerImpl::Factory::test_factory_ = nullptr;

// static
HostDeviceTimestampManagerImpl::Factory*
HostDeviceTimestampManagerImpl::Factory::Get() {
  if (test_factory_)
    return test_factory_;

  static base::NoDestructor<HostDeviceTimestampManagerImpl::Factory> factory;
  return factory.get();
}

// static
void HostDeviceTimestampManagerImpl::Factory::SetFactoryForTesting(
    Factory* test_factory) {
  test_factory_ = test_factory;
}

HostDeviceTimestampManagerImpl::Factory::~Factory() = default;

std::unique_ptr<HostDeviceTimestampManager>
HostDeviceTimestampManagerImpl::Factory::BuildInstance(
    HostStatusProvider* host_status_provider,
    PrefService* pref_service,
    base::Clock* clock) {
  return base::WrapUnique(new HostDeviceTimestampManagerImpl(
      host_status_provider, pref_service, clock));
}

// static
const char
    HostDeviceTimestampManagerImpl::kWasHostSetFromThisChromebookPrefName[] =
        "multidevice_setup.was_host_set_from_this_chromebook";

// static
const char HostDeviceTimestampManagerImpl::kSetupFlowCompletedPrefName[] =
    "multidevice_setup.setup_flow_completed";

// static
const char
    HostDeviceTimestampManagerImpl::kHostVerifiedUpdateReceivedPrefName[] =
        "multidevice_setup.host_verified_update_received";

// static
void HostDeviceTimestampManagerImpl::RegisterPrefs(
    PrefRegistrySimple* registry) {
  registry->RegisterBooleanPref(kWasHostSetFromThisChromebookPrefName, false);
  registry->RegisterInt64Pref(kSetupFlowCompletedPrefName, kTimestampNotSet);
  registry->RegisterInt64Pref(kHostVerifiedUpdateReceivedPrefName,
                              kTimestampNotSet);
}

HostDeviceTimestampManagerImpl::~HostDeviceTimestampManagerImpl() {
  host_status_provider_->RemoveObserver(this);
}

HostDeviceTimestampManagerImpl::HostDeviceTimestampManagerImpl(
    HostStatusProvider* host_status_provider,
    PrefService* pref_service,
    base::Clock* clock)
    : host_status_provider_(host_status_provider),
      pref_service_(pref_service),
      clock_(clock) {
  host_status_provider_->AddObserver(this);
}

bool HostDeviceTimestampManagerImpl::WasHostSetFromThisChromebook() {
  return pref_service_->GetBoolean(kWasHostSetFromThisChromebookPrefName);
}

base::Optional<base::Time>
HostDeviceTimestampManagerImpl::GetLatestSetupFlowCompletionTimestamp() {
  if (pref_service_->GetInt64(kSetupFlowCompletedPrefName) == kTimestampNotSet)
    return base::nullopt;
  return base::Time::FromJavaTime(
      pref_service_->GetInt64(kSetupFlowCompletedPrefName));
}

base::Optional<base::Time>
HostDeviceTimestampManagerImpl::GetLatestVerificationTimestamp() {
  if (pref_service_->GetInt64(kHostVerifiedUpdateReceivedPrefName) ==
      kTimestampNotSet)
    return base::nullopt;
  return base::Time::FromJavaTime(
      pref_service_->GetInt64(kHostVerifiedUpdateReceivedPrefName));
}

void HostDeviceTimestampManagerImpl::OnHostStatusChange(
    const HostStatusProvider::HostStatusWithDevice& host_status_with_device) {
  // Check if setup flow was completed on this Chromebook. Note that it suffices
  // to use a host status update with the status
  // kHostSetLocallyButWaitingForBackendConfirmation as a proxy for completing
  // setup flow because the Chromebook sets a host locally (i.e. enters this
  // state) exactly when it successfully completes the flow. Note that this is
  // equivalent to a host being set on the Chromebook.
  if (host_status_with_device.host_status() ==
      mojom::HostStatus::kHostSetLocallyButWaitingForBackendConfirmation) {
    pref_service_->SetInt64(kSetupFlowCompletedPrefName,
                            clock_->Now().ToJavaTime());
    pref_service_->SetBoolean(kWasHostSetFromThisChromebookPrefName, true);
    PA_LOG(INFO) << "HostDeviceTimestampManagerImpl::OnHostStatusChange(): "
                 << "Setup flow successfully completed. Recording timestamp "
                 << pref_service_->GetInt64(kSetupFlowCompletedPrefName) << ".";
  }

  // Check if a host has been verified.
  if (host_status_with_device.host_status() ==
      mojom::HostStatus::kHostVerified) {
    pref_service_->SetInt64(kHostVerifiedUpdateReceivedPrefName,
                            clock_->Now().ToJavaTime());
    PA_LOG(INFO) << "HostDeviceTimestampManagerImpl::OnHostStatusChange(): "
                 << "New host verified. Recording timestamp "
                 << pref_service_->GetInt64(kHostVerifiedUpdateReceivedPrefName)
                 << ".";
  }

  // If there is no host set, set the "was host set form this Chromebook" bit to
  // false.
  if (host_status_with_device.host_status() ==
          mojom::HostStatus::kNoEligibleHosts ||
      host_status_with_device.host_status() ==
          mojom::HostStatus::kEligibleHostExistsButNoHostSet) {
    pref_service_->SetBoolean(kWasHostSetFromThisChromebookPrefName, false);
  }
}

}  // namespace multidevice_setup

}  // namespace chromeos
