// 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 "components/segmentation_platform/internal/ukm_data_manager_impl.h"

#include "base/check_is_test.h"
#include "base/check_op.h"
#include "base/functional/callback_helpers.h"
#include "base/task/sequenced_task_runner.h"
#include "components/segmentation_platform/internal/database/ukm_database_impl.h"
#include "components/segmentation_platform/internal/signals/ukm_config.h"
#include "components/segmentation_platform/internal/signals/ukm_observer.h"
#include "components/segmentation_platform/internal/signals/url_signal_handler.h"

namespace segmentation_platform {

namespace {

// Delay for running clean up task from startup.
const base::TimeDelta kDatabaseCleanupDelayStartup = base::Minutes(2);

// Periodic interval between two cleanup tasks.
const base::TimeDelta kDatabaseCleanupDelayNormal = base::Days(1);

// Number of days to keep UKM metrics in database.
constexpr base::TimeDelta kUkmEntriesTTL = base::Days(30);

// Number of days to keep UMA metrics in database.
constexpr base::TimeDelta kUmaEntriesTTL = base::Days(365);

}  // namespace

UkmDataManagerImpl::UkmDataManagerImpl() = default;

UkmDataManagerImpl::~UkmDataManagerImpl() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_check_);
  DCHECK_EQ(ref_count_, 0);

  if (ukm_observer_) {
    ukm_observer_->set_ukm_data_manager(nullptr);
  }
  url_signal_handler_.reset();
  ukm_database_.reset();
}

void UkmDataManagerImpl::InitializeForTesting(
    std::unique_ptr<UkmDatabase> ukm_database,
    UkmObserver* ukm_observer) {
  InitiailizeImpl(std::move(ukm_database));
  StartObservation(ukm_observer);
}

void UkmDataManagerImpl::Initialize(const base::FilePath& database_path,
                                    bool in_memory) {
  InitiailizeImpl(std::make_unique<UkmDatabaseImpl>(database_path, in_memory));
}

void UkmDataManagerImpl::BeginShutdown() {
  shutting_down_ = true;
  if (ref_count_ == 0) {
    url_signal_handler_.reset();
    ukm_database_.reset();
  }
}

void UkmDataManagerImpl::StartObservation(UkmObserver* ukm_observer) {
  ukm_observer_ = ukm_observer;
  ukm_observer_->set_ukm_data_manager(this);
}

void UkmDataManagerImpl::InitiailizeImpl(
    std::unique_ptr<UkmDatabase> ukm_database) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_check_);
  DCHECK(!ukm_database_);
  DCHECK(!ukm_observer_);

  ukm_database_ = std::move(ukm_database);
  // TODO(ssid): Move this call  to constructor to make it clear any transaction
  // is posted after initialization.
  ukm_database_->InitDatabase(base::DoNothing());

  GetOrCreateUrlHandler();

  base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
      FROM_HERE,
      base::BindOnce(&UkmDataManagerImpl::RunCleanupTask,
                     weak_factory_.GetWeakPtr()),
      kDatabaseCleanupDelayStartup);
}

bool UkmDataManagerImpl::IsUkmEngineEnabled() {
  // DummyUkmDataManager is created when UKM engine is disabled.
  return true;
}

UrlSignalHandler* UkmDataManagerImpl::GetOrCreateUrlHandler() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_check_);
  DCHECK(ukm_database_);
  if (!url_signal_handler_) {
    url_signal_handler_ =
        std::make_unique<UrlSignalHandler>(ukm_database_.get());
  }
  return url_signal_handler_.get();
}

void UkmDataManagerImpl::StartObservingUkm(const UkmConfig& ukm_config) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_check_);
  // TODO(b/290821132): Remove this check.
  if (!ukm_observer_) {
    CHECK_IS_TEST();
    return;
  }
  ukm_observer_->StartObserving(ukm_config);
}

void UkmDataManagerImpl::PauseOrResumeObservation(bool pause) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_check_);
  // TODO(b/290821132): Remove this check.
  if (!ukm_observer_) {
    // On iOS the eg tests do not set this flag.
#if !BUILDFLAG(IS_IOS)
    CHECK_IS_TEST();
#endif
    return;
  }
  ukm_observer_->PauseOrResumeObservation(pause);
}

UkmDatabase* UkmDataManagerImpl::GetUkmDatabase() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_check_);
  DCHECK(ukm_database_);
  return ukm_database_.get();
}

bool UkmDataManagerImpl::HasUkmDatabase() {
  return ukm_database_ ? true : false;
}

void UkmDataManagerImpl::OnEntryAdded(ukm::mojom::UkmEntryPtr entry) {
  ukm_database_->StoreUkmEntry(std::move(entry));
}

void UkmDataManagerImpl::OnUkmSourceUpdated(ukm::SourceId source_id,
                                            const std::vector<GURL>& urls) {
  if (url_signal_handler_)
    url_signal_handler_->OnUkmSourceUpdated(source_id, urls);
}

void UkmDataManagerImpl::AddRef() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_check_);
  ref_count_++;
}

void UkmDataManagerImpl::RemoveRef() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_check_);
  DCHECK_GT(ref_count_, 0);
  if (--ref_count_ == 0 && shutting_down_) {
    url_signal_handler_.reset();
    ukm_database_.reset();
  }
}

void UkmDataManagerImpl::RunCleanupTask() {
  DCHECK(ukm_database_);
  base::Time now = base::Time::Now();
  ukm_database_->CleanupOldEntries(now - kUkmEntriesTTL, now - kUmaEntriesTTL);

  // Consider waiting for the above task to finish successfully before posting
  // the next one.
  base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
      FROM_HERE,
      base::BindOnce(&UkmDataManagerImpl::RunCleanupTask,
                     weak_factory_.GetWeakPtr()),
      kDatabaseCleanupDelayNormal);
}

}  // namespace segmentation_platform
