// Copyright 2017 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/feature_engagement/internal/tracker_impl.h"

#include <memory>
#include <optional>
#include <utility>

#include "base/check_is_test.h"
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/metrics/field_trial_params.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/user_metrics.h"
#include "base/no_destructor.h"
#include "base/synchronization/lock.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/clock.h"
#include "build/build_config.h"
#include "components/feature_engagement/internal/availability_model_impl.h"
#include "components/feature_engagement/internal/blocked_iph_features.h"
#include "components/feature_engagement/internal/chrome_variations_configuration.h"
#include "components/feature_engagement/internal/display_lock_controller_impl.h"
#include "components/feature_engagement/internal/editable_configuration.h"
#include "components/feature_engagement/internal/event_model.h"
#include "components/feature_engagement/internal/event_model_impl.h"
#include "components/feature_engagement/internal/feature_config_condition_validator.h"
#include "components/feature_engagement/internal/feature_config_event_storage_validator.h"
#include "components/feature_engagement/internal/in_memory_event_store.h"
#include "components/feature_engagement/internal/init_aware_event_model.h"
#include "components/feature_engagement/internal/multiple_event_model_provider.h"
#include "components/feature_engagement/internal/multiple_event_model_writer.h"
#include "components/feature_engagement/internal/never_availability_model.h"
#include "components/feature_engagement/internal/never_event_storage_validator.h"
#include "components/feature_engagement/internal/noop_display_lock_controller.h"
#include "components/feature_engagement/internal/once_condition_validator.h"
#include "components/feature_engagement/internal/persistent_event_store.h"
#include "components/feature_engagement/internal/proto/availability.pb.h"
#include "components/feature_engagement/internal/single_event_model_provider.h"
#include "components/feature_engagement/internal/stats.h"
#include "components/feature_engagement/internal/system_time_provider.h"
#include "components/feature_engagement/internal/testing_clock_time_provider.h"
#include "components/feature_engagement/public/configuration.h"
#include "components/feature_engagement/public/feature_activation.h"
#include "components/feature_engagement/public/feature_constants.h"
#include "components/feature_engagement/public/feature_list.h"
#include "components/feature_engagement/public/group_constants.h"
#include "components/feature_engagement/public/group_list.h"
#include "components/feature_engagement/public/pref_names.h"
#include "components/leveldb_proto/public/proto_database_provider.h"
#include "components/prefs/pref_service.h"

namespace feature_engagement {

namespace {

const char kEventDBName[] = "EventDB";
const char kAvailabilityDBName[] = "AvailabilityDB";

#if !BUILDFLAG(IS_ANDROID)

// Reads event data from `config` and - if valid - places it into `result` along
// with the event count in the appropriate window.
void MaybeGetEventData(Tracker::EventList& result,
                       const EventConfig& config,
                       const EventModelReader& event_model_reader,
                       uint32_t current_day) {
  if (config.name.empty()) {
    return;
  }
  result.emplace_back(
      std::make_pair(config, event_model_reader.GetEventCount(
                                 config.name, current_day, config.window)));
}

#endif  // !BUILDFLAG(IS_ANDROID)

}  // namespace

std::unique_ptr<Tracker> CreateDemoModeTracker(
    feature_engagement::FeatureActivation feature_activation) {
  feature_engagement::FeatureActivation::State state =
      feature_activation.get_state();

  switch (state) {
    case feature_engagement::FeatureActivation::State::kAllEnabled:
      DVLOG(2) << "Enabling demo mode. All features enabled ";
      break;
    case feature_engagement::FeatureActivation::State::kAllDisabled:
      DVLOG(2) << "Enabling demo mode. All features disabled ";
      break;
    case feature_engagement::FeatureActivation::State::kSingleFeatureEnabled:
      DVLOG(2) << "Enabling demo mode. Chosen feature: "
               << feature_activation.get_unique_feature_name();
      break;
  }

  std::unique_ptr<EditableConfiguration> configuration =
      std::make_unique<EditableConfiguration>();

  // Create valid configurations for all features to ensure that the
  // OnceConditionValidator acknowledges that thet meet conditions once.
  std::vector<const base::Feature*> features = GetAllFeatures();
  for (auto* feature : features) {
    FeatureConfig feature_config;
    // If a particular feature has been chosen to use with demo mode, only
    // mark that feature with a valid configuration.
    switch (state) {
      case feature_engagement::FeatureActivation::State::kAllEnabled:
        feature_config.valid = true;
        break;
      case feature_engagement::FeatureActivation::State::kAllDisabled:
        feature_config.valid = false;
        break;
      case feature_engagement::FeatureActivation::State::kSingleFeatureEnabled:
        feature_config.valid =
            feature_activation.get_unique_feature_name() == feature->name;
    }
    feature_config.trigger.name = feature->name + std::string("_trigger");
    configuration->SetConfiguration(feature, feature_config);
  }

  auto raw_event_model = std::make_unique<EventModelImpl>(
      std::make_unique<InMemoryEventStore>(),
      std::make_unique<NeverEventStorageValidator>());

  auto event_model =
      std::make_unique<InitAwareEventModel>(std::move(raw_event_model));

  return std::make_unique<TrackerImpl>(
      std::make_unique<SingleEventModelProvider>(std::move(event_model)),
      std::make_unique<NeverAvailabilityModel>(), std::move(configuration),
      std::make_unique<NoopDisplayLockController>(),
      std::make_unique<OnceConditionValidator>(),
      std::make_unique<SystemTimeProvider>(), nullptr, nullptr, nullptr,
      nullptr);
}

// This method is declared in
// //components/feature_engagement/public/tracker.h
// and should be linked in to any binary using Tracker::Create.
// static
std::unique_ptr<Tracker> Tracker::Create(
    const base::FilePath& storage_dir,
    const base::FilePath& device_storage_dir,
    PrefService* pref_service,
    const scoped_refptr<base::SequencedTaskRunner>& background_task_runner,
    leveldb_proto::ProtoDatabaseProvider* db_provider,
    std::unique_ptr<TrackerEventExporter> event_exporter,
    const ConfigurationProviderList& configuration_providers,
    std::unique_ptr<SessionController> session_controller) {
  DVLOG(2) << "Creating Tracker";
  if (base::FeatureList::IsEnabled(kIPHDemoMode)) {
    // GetFieldTrialParamValueByFeature returns an empty string if the param is
    // not set. In this case, we want to enable all features.
    std::string chosen_feature_name = base::GetFieldTrialParamValueByFeature(
        kIPHDemoMode, kIPHDemoModeFeatureChoiceParam);
    feature_engagement::FeatureActivation feature_action =
        chosen_feature_name.empty()
            ? feature_engagement::FeatureActivation::AllEnabled()
            : feature_engagement::FeatureActivation(chosen_feature_name);
    return CreateDemoModeTracker(feature_action);
  }

  base::FilePath event_storage_dir = storage_dir.AppendASCII(kEventDBName);
  auto event_db = db_provider->GetDB<Event>(
      leveldb_proto::ProtoDbType::FEATURE_ENGAGEMENT_EVENT, event_storage_dir,
      background_task_runner);

  auto profile_event_db = event_db.get();

  auto event_store =
      std::make_unique<PersistentEventStore>(std::move(event_db));

  auto configuration = std::make_unique<ChromeVariationsConfiguration>();
  configuration->LoadConfigs(configuration_providers, GetAllFeatures(),
                             GetAllGroups());

  auto event_storage_validator =
      std::make_unique<FeatureConfigEventStorageValidator>();
  event_storage_validator->InitializeFeatures(GetAllFeatures(), GetAllGroups(),
                                              *configuration);

  auto raw_event_model = std::make_unique<EventModelImpl>(
      std::move(event_store), std::move(event_storage_validator));

  auto event_model =
      std::make_unique<InitAwareEventModel>(std::move(raw_event_model));

  auto condition_validator =
      std::make_unique<FeatureConfigConditionValidator>();
  auto time_provider = std::make_unique<SystemTimeProvider>();

  base::FilePath availability_storage_dir =
      storage_dir.AppendASCII(kAvailabilityDBName);
  auto availability_db = db_provider->GetDB<Availability>(
      leveldb_proto::ProtoDbType::FEATURE_ENGAGEMENT_AVAILABILITY,
      availability_storage_dir, background_task_runner);
  auto availability_store_loader = base::BindOnce(
      &PersistentAvailabilityStore::LoadAndUpdateStore,
      availability_storage_dir, std::move(availability_db), GetAllFeatures());

  auto availability_model = std::make_unique<AvailabilityModelImpl>(
      std::move(availability_store_loader));

  std::unique_ptr<EventModelProvider> event_model_provider;
  std::unique_ptr<EventStorageMigration> event_storage_migration;
  if (IsOnDeviceStorageEnabled()) {
    base::FilePath device_event_storage_dir =
        device_storage_dir.AppendASCII(kEventDBName);
    auto device_event_db = db_provider->GetUniqueDB<Event>(
        leveldb_proto::ProtoDbType::FEATURE_ENGAGEMENT_EVENT,
        device_event_storage_dir, background_task_runner);

    auto* raw_device_event_db = device_event_db.get();

    auto device_event_store =
        std::make_unique<PersistentEventStore>(std::move(device_event_db));

    auto device_event_storage_validator =
        std::make_unique<FeatureConfigEventStorageValidator>();
    device_event_storage_validator->InitializeFeatures(
        GetAllFeatures(), GetAllGroups(), *configuration);

    auto device_raw_event_model = std::make_unique<EventModelImpl>(
        std::move(device_event_store),
        std::move(device_event_storage_validator));

    // If the migration is completed, we don't need to migrate the data.
    // TODO(crbug.com/426624087): Remove this and all the code related to it
    // once the migration is completed.
    event_storage_migration = std::make_unique<EventStorageMigration>(
        profile_event_db, raw_device_event_db, device_raw_event_model.get());

    auto device_event_model = std::make_unique<InitAwareEventModel>(
        std::move(device_raw_event_model));
    event_model_provider = std::make_unique<MultipleEventModelProvider>(
        std::move(event_model), std::move(device_event_model));
  } else {
    event_model_provider =
        std::make_unique<SingleEventModelProvider>(std::move(event_model));
  }

  return std::make_unique<TrackerImpl>(
      std::move(event_model_provider), std::move(availability_model),
      std::move(configuration), std::make_unique<DisplayLockControllerImpl>(),
      std::move(condition_validator), std::move(time_provider),
      std::move(event_exporter), std::move(session_controller),
      std::move(event_storage_migration), pref_service);
}

TrackerImpl::TrackerImpl(
    std::unique_ptr<EventModelProvider> event_model_provider,
    std::unique_ptr<AvailabilityModel> availability_model,
    std::unique_ptr<Configuration> configuration,
    std::unique_ptr<DisplayLockController> display_lock_controller,
    std::unique_ptr<ConditionValidator> condition_validator,
    std::unique_ptr<TimeProvider> time_provider,
    std::unique_ptr<TrackerEventExporter> event_exporter,
    std::unique_ptr<SessionController> session_controller,
    std::unique_ptr<EventStorageMigration> event_storage_migration,
    PrefService* pref_service)
    : event_model_provider_(std::move(event_model_provider)),
      availability_model_(std::move(availability_model)),
      configuration_(std::move(configuration)),
      display_lock_controller_(std::move(display_lock_controller)),
      condition_validator_(std::move(condition_validator)),
      time_provider_(std::move(time_provider)),
      event_exporter_(std::move(event_exporter)),
      session_controller_(std::move(session_controller)),
      event_storage_migration_(std::move(event_storage_migration)),
      pref_service_(pref_service) {
  availability_model_->Initialize(
      base::BindOnce(&TrackerImpl::OnAvailabilityModelInitializationFinished,
                     weak_ptr_factory_.GetWeakPtr()),
      time_provider_->GetCurrentDay());

  event_model_provider_->Initialize(
      base::BindOnce(&TrackerImpl::OnEventModelInitializationFinished,
                     weak_ptr_factory_.GetWeakPtr()),
      time_provider_->GetCurrentDay());
}

TrackerImpl::~TrackerImpl() = default;

void TrackerImpl::NotifyEvent(const std::string& event) {
  GetEventModelWriter()->IncrementEvent(event, time_provider_->GetCurrentDay());
  stats::RecordNotifyEvent(event, configuration_.get(),
                           event_model_provider_->IsReady());
}

#if !BUILDFLAG(IS_ANDROID)
void TrackerImpl::NotifyUsedEvent(const base::Feature& feature) {
  const auto& feature_config = configuration_->GetFeatureConfig(feature);
  if (!feature_config.used.name.empty()) {
    NotifyEvent(feature_config.used.name);
  }
}

void TrackerImpl::ClearEventData(const base::Feature& feature) {
  const auto& feature_config = configuration_->GetFeatureConfig(feature);
  if (!feature_config.trigger.name.empty()) {
    GetEventModelWriter()->ClearEvent(feature_config.trigger.name);
  }
  if (!feature_config.used.name.empty()) {
    GetEventModelWriter()->ClearEvent(feature_config.used.name);
  }
  for (const auto& event_config : feature_config.event_configs) {
    GetEventModelWriter()->ClearEvent(event_config.name);
  }
}

Tracker::EventList TrackerImpl::ListEvents(const base::Feature& feature) const {
  EventList result;
  if (!IsInitialized()) {
    return result;
  }
  const auto& feature_config = configuration_->GetFeatureConfig(feature);
  const auto current_day = time_provider_->GetCurrentDay();
  auto* event_model_reader = GetEventModelReaderForFeature(feature_config);
  MaybeGetEventData(result, feature_config.trigger, *event_model_reader,
                    current_day);
  MaybeGetEventData(result, feature_config.used, *event_model_reader,
                    current_day);
  for (const auto& event_config : feature_config.event_configs) {
    MaybeGetEventData(result, event_config, *event_model_reader, current_day);
  }
  return result;
}

#endif  // !BUILDFLAG(IS_ANDROID)

bool TrackerImpl::ShouldTriggerHelpUI(const base::Feature& feature) {
  return ShouldTriggerHelpUIWithSnooze(feature).ShouldShowIph();
}

TrackerImpl::TriggerDetails TrackerImpl::ShouldTriggerHelpUIWithSnooze(
    const base::Feature& feature) {
  if (session_controller_ && session_controller_->ShouldResetSession()) {
    condition_validator_->ResetSession();
  }
  if (IsFeatureBlockedByTest(feature)) {
    return TriggerDetails(false, false);
  }

  FeatureConfig feature_config = configuration_->GetFeatureConfig(feature);
  std::vector<GroupConfig> group_configs;
  for (auto group : feature_config.groups) {
    group_configs.push_back(configuration_->GetGroupConfigByName(group));
  }
  auto* event_model_reader = GetEventModelReaderForFeature(feature_config);
  ConditionValidator::Result result = condition_validator_->MeetsConditions(
      feature, feature_config, group_configs, *event_model_reader,
      *availability_model_, *display_lock_controller_, configuration_.get(),
      *time_provider_);
  if (result.NoErrors()) {
    condition_validator_->NotifyIsShowing(
        feature, feature_config, configuration_->GetRegisteredFeatures());

    DCHECK_NE("", feature_config.trigger.name);
    GetEventModelWriter()->IncrementEvent(feature_config.trigger.name,
                                          time_provider_->GetCurrentDay());

    for (auto group : feature_config.groups) {
      GroupConfig group_config = configuration_->GetGroupConfigByName(group);
      GetEventModelWriter()->IncrementEvent(group_config.trigger.name,
                                            time_provider_->GetCurrentDay());
    }
  }

  stats::RecordShouldTriggerHelpUI(feature, feature_config, result);
  DVLOG(2) << "Trigger result for " << feature.name
           << ": trigger=" << result.NoErrors()
           << " tracking_only=" << feature_config.tracking_only << " "
           << result;

  bool should_show_iph = false;

  if (feature_config.tracking_only) {
    if (result.NoErrors()) {
      // Because tracking only features always return false to the client,
      // clients do not have the context to correctly dismiss. The dismiss is
      // needed because our condition validator thinks the feature is currently
      // showing. See https://crbug.com/1188679 for more details.
      Dismissed(feature);
    }
    should_show_iph = false;
  } else {
    should_show_iph = result.NoErrors();
  }

  if (should_show_iph) {
    DCHECK(start_times_.find(feature.name) == start_times_.end());
    start_times_[feature.name] = time_provider_->Now();
  }

  return TriggerDetails(should_show_iph, result.should_show_snooze);
}

bool TrackerImpl::WouldTriggerHelpUI(const base::Feature& feature) const {
  if (session_controller_ && session_controller_->ShouldResetSession()) {
    condition_validator_->ResetSession();
  }

  if (IsFeatureBlockedByTest(feature)) {
    return false;
  }

  FeatureConfig feature_config = configuration_->GetFeatureConfig(feature);
  std::vector<GroupConfig> group_configs;
  for (auto group : feature_config.groups) {
    group_configs.push_back(configuration_->GetGroupConfigByName(group));
  }
  auto* event_model_reader = GetEventModelReaderForFeature(feature_config);
  ConditionValidator::Result result = condition_validator_->MeetsConditions(
      feature, feature_config, group_configs, *event_model_reader,
      *availability_model_, *display_lock_controller_, configuration_.get(),
      *time_provider_);
  DVLOG(2) << "Would trigger result for " << feature.name
           << ": trigger=" << result.NoErrors()
           << " tracking_only=" << feature_config.tracking_only << " "
           << result;
  return result.NoErrors() && !feature_config.tracking_only;
}

bool TrackerImpl::HasEverTriggered(const base::Feature& feature,
                                   bool from_window) const {
  const FeatureConfig feature_config =
      configuration_->GetFeatureConfig(feature);
  const EventConfig trigger_config = feature_config.trigger;

  uint32_t window_size =
      from_window ? trigger_config.window : trigger_config.storage;

  uint32_t event_count =
      GetEventModelReaderForFeature(feature_config)
          ->GetEventCount(trigger_config.name, time_provider_->GetCurrentDay(),
                          window_size);

  return event_count > 0;
}

Tracker::TriggerState TrackerImpl::GetTriggerState(
    const base::Feature& feature) const {
  if (!IsInitialized()) {
    DVLOG(2) << "TriggerState for " << feature.name << ": "
             << static_cast<int>(Tracker::TriggerState::NOT_READY);
    return Tracker::TriggerState::NOT_READY;
  }

  FeatureConfig feature_config = configuration_->GetFeatureConfig(feature);
  std::vector<GroupConfig> group_configs;
  for (auto group : feature_config.groups) {
    group_configs.push_back(configuration_->GetGroupConfigByName(group));
  }
  auto* event_model_reader = GetEventModelReaderForFeature(feature_config);
  ConditionValidator::Result result = condition_validator_->MeetsConditions(
      feature, configuration_->GetFeatureConfig(feature), group_configs,
      *event_model_reader, *availability_model_, *display_lock_controller_,
      configuration_.get(), *time_provider_);

  if (result.trigger_ok) {
    DVLOG(2) << "TriggerState for " << feature.name << ": "
             << static_cast<int>(Tracker::TriggerState::HAS_NOT_BEEN_DISPLAYED);
    return Tracker::TriggerState::HAS_NOT_BEEN_DISPLAYED;
  }

  DVLOG(2) << "TriggerState for " << feature.name << ": "
           << static_cast<int>(Tracker::TriggerState::HAS_BEEN_DISPLAYED);
  return Tracker::TriggerState::HAS_BEEN_DISPLAYED;
}

void TrackerImpl::Dismissed(const base::Feature& feature) {
  DVLOG(2) << "Dismissing " << feature.name;
  DCHECK(!IsFeatureBlockedByTest(feature));

  condition_validator_->NotifyDismissed(feature);
  stats::RecordUserDismiss();
  RecordShownTime(feature);
}

void TrackerImpl::DismissedWithSnooze(
    const base::Feature& feature,
    std::optional<SnoozeAction> snooze_action) {
  DCHECK(!IsFeatureBlockedByTest(feature));

  FeatureConfig feature_config = configuration_->GetFeatureConfig(feature);
  if (snooze_action == SnoozeAction::SNOOZED) {
    GetEventModelWriter()->IncrementSnooze(feature_config.trigger.name,
                                           time_provider_->GetCurrentDay(),
                                           time_provider_->Now());
  } else if (snooze_action == SnoozeAction::DISMISSED) {
    GetEventModelWriter()->DismissSnooze(feature_config.trigger.name);
  }
  Dismissed(feature);
  if (snooze_action.has_value())
    stats::RecordUserSnoozeAction(snooze_action.value());
}

std::unique_ptr<DisplayLockHandle> TrackerImpl::AcquireDisplayLock() {
  return display_lock_controller_->AcquireDisplayLock();
}

void TrackerImpl::SetPriorityNotification(const base::Feature& feature) {
  // If the handler hasn't been registered.
  auto iter = priority_notification_handlers_.find(feature.name);
  if (iter == priority_notification_handlers_.end()) {
    condition_validator_->SetPriorityNotification(feature.name);
    return;
  }

  // We already have a handler. Serve the request and remove the handler.
  condition_validator_->SetPriorityNotification(std::nullopt);
  std::move(iter->second).Run();
  priority_notification_handlers_.erase(feature.name);
}

std::optional<std::string> TrackerImpl::GetPendingPriorityNotification() {
  return condition_validator_->GetPendingPriorityNotification();
}

void TrackerImpl::RegisterPriorityNotificationHandler(
    const base::Feature& feature,
    base::OnceClosure callback) {
  // If we already have a pending notification, handle it right away.
  auto pending_priority_notification =
      condition_validator_->GetPendingPriorityNotification();
  if (pending_priority_notification.has_value() &&
      pending_priority_notification.value() == feature.name) {
    std::move(callback).Run();
    condition_validator_->SetPriorityNotification(std::nullopt);
    return;
  }

  // We don't have the notification yet. Cache the handler.
  priority_notification_handlers_.emplace(feature.name, std::move(callback));
}

void TrackerImpl::UnregisterPriorityNotificationHandler(
    const base::Feature& feature) {
  priority_notification_handlers_.erase(feature.name);
}

#if BUILDFLAG(IS_CHROMEOS)
void TrackerImpl::UpdateConfig(const base::Feature& feature,
                               const ConfigurationProvider* provider) {
  CHECK(IsInitialized());
  configuration_->UpdateConfig(feature, provider);
}
#endif

const Configuration* TrackerImpl::GetConfiguration() const {
  return configuration_.get();
}

void TrackerImpl::SetClockForTesting(const base::Clock& clock,
                                     base::Time initial_now) {
  CHECK_IS_TEST();
  time_provider_ =
      std::make_unique<TestingClockTimeProvider>(clock, initial_now);
}

bool TrackerImpl::IsInFeatureTestMode() const {
  auto* const blocked = BlockedIphFeatures::GetInstance();
  base::AutoLock lock(blocked->GetLock());
  return blocked->AreAnyFeaturesBlocked();
}

bool TrackerImpl::IsInitialized() const {
  return event_model_provider_->IsReady() && availability_model_->IsReady();
}

void TrackerImpl::AddOnInitializedCallback(OnInitializedCallback callback) {
  if (IsInitializationFinished()) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), IsInitialized()));
    return;
  }

  on_initialized_callbacks_.push_back(std::move(callback));
}

void TrackerImpl::OnEventModelInitializationFinished(bool success) {
  DCHECK_EQ(success, event_model_provider_->IsReady());

  // If the migration is not completed, we need to migrate the data from the
  // profile db to the device db.
  if (success && event_storage_migration_ &&
      !pref_service_->GetBoolean(
          kFeatureEngagementProfileToDeviceMigrationCompleted)) {
    event_storage_migration_->Migrate(
        base::BindOnce(&TrackerImpl::OnEventStorageMigrationFinished,
                       weak_ptr_factory_.GetWeakPtr()),
        time_provider_->GetCurrentDay());
    return;
  }

  if (success && IsOnDeviceStorageEnabled()) {
    // If the migration was completed, log the status.
    EventStorageMigration::RecordMigrationStatus(
        EventStorageMigration::EventStorageMigrationStatus::kNotRequired);
  }

  event_model_provider_initialization_finished_ = true;

  DVLOG(2) << "Event model initialization result = " << success;

  if (event_exporter_) {
    event_exporter_->ExportEvents(base::BindOnce(
        &TrackerImpl::OnReceiveExportedEvents, weak_ptr_factory_.GetWeakPtr()));
  } else {
    MaybePostInitializedCallbacks();
  }
}

void TrackerImpl::OnEventStorageMigrationFinished(bool success) {
  DVLOG(2) << "Event storage migration result = " << success;

  if (success) {
    pref_service_->SetBoolean(
        kFeatureEngagementProfileToDeviceMigrationCompleted, true);
  }

  event_storage_migration_ = nullptr;

  event_model_provider_initialization_finished_ = true;

  DVLOG(2) << "Event model initialization result = " << success;

  if (event_exporter_) {
    event_exporter_->ExportEvents(base::BindOnce(
        &TrackerImpl::OnReceiveExportedEvents, weak_ptr_factory_.GetWeakPtr()));
  } else {
    MaybePostInitializedCallbacks();
  }
}

void TrackerImpl::OnAvailabilityModelInitializationFinished(bool success) {
  DCHECK_EQ(success, availability_model_->IsReady());
  availability_model_initialization_finished_ = true;

  DVLOG(2) << "Availability model initialization result = " << success;

  MaybePostInitializedCallbacks();
}

bool TrackerImpl::IsInitializationFinished() const {
  bool event_migration_finished =
      event_exporter_ == nullptr || event_migration_finished_;
  return event_model_provider_initialization_finished_ &&
         availability_model_initialization_finished_ &&
         event_migration_finished;
}

void TrackerImpl::MaybePostInitializedCallbacks() {
  if (!IsInitializationFinished())
    return;

  if (!initialization_time_recorded_) {
    // TODO(crbug.com/500891039): Re-evaluate the range of this histogram after
    // gathering initial data.
    base::UmaHistogramTimes("InProductHelp.InitializationTime.Total",
                            init_timer_.Elapsed());
    initialization_time_recorded_ = true;
  }

  DVLOG(2) << "Initialization finished.";

  for (auto& callback : on_initialized_callbacks_) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), IsInitialized()));
  }

  on_initialized_callbacks_.clear();
}

void TrackerImpl::RecordShownTime(const base::Feature& feature) {
  std::string feature_name = feature.name;
  auto iter = start_times_.find(feature_name);
  if (iter == start_times_.end())
    return;

  UmaHistogramTimes("InProductHelp.ShownTime." + feature_name,
                    time_provider_->Now() - iter->second);
  UmaHistogramMediumTimes("InProductHelp.ShownTime2." + feature_name,
                          time_provider_->Now() - iter->second);
  start_times_.erase(feature_name);
}

void TrackerImpl::OnReceiveExportedEvents(
    std::vector<TrackerEventExporter::EventData> events) {
  for (auto& event : events) {
    GetEventModelWriter()->IncrementEvent(event.event_name, event.day);
  }

  event_migration_finished_ = true;
  MaybePostInitializedCallbacks();
}

EventModelWriter* TrackerImpl::GetEventModelWriter() {
  return event_model_provider_->GetEventModelWriter();
}

const EventModelReader* TrackerImpl::GetEventModelReaderForFeature(
    const FeatureConfig& feature_config) const {
  return event_model_provider_->GetEventModelReaderForFeature(feature_config);
}

// static
void Tracker::PropagateTestStateToChildProcess(
    base::CommandLine& command_line) {
  auto* const blocked = BlockedIphFeatures::GetInstance();
  base::AutoLock lock(blocked->GetLock());
  blocked->MaybeWriteToCommandLine(command_line);
}

// static
bool TrackerImpl::IsFeatureBlockedByTest(const base::Feature& feature) {
  auto* const blocked = BlockedIphFeatures::GetInstance();
  base::AutoLock lock(blocked->GetLock());
  return blocked->IsFeatureBlocked(feature.name);
}

}  // namespace feature_engagement
