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

#include "chrome/browser/user_education/user_education_service_factory.h"

#include <memory>
#include <optional>

#include "base/command_line.h"
#include "base/synchronization/lock.h"
#include "build/branding_buildflags.h"
#include "build/build_config.h"
#include "chrome/browser/feature_engagement/tracker_factory.h"
#include "chrome/browser/headless/headless_mode_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profiles_state.h"
#include "chrome/browser/user_education/browser_user_education_storage_service.h"
#include "chrome/browser/user_education/user_education_service.h"
#include "chrome/common/chrome_switches.h"
#include "components/feature_engagement/public/tracker.h"
#include "components/keyed_service/content/browser_context_dependency_manager.h"
#include "components/user_education/common/session/user_education_idle_observer.h"
#include "components/user_education/common/session/user_education_idle_policy.h"
#include "components/user_education/common/session/user_education_session_manager.h"
#include "content/public/browser/browser_context.h"

#if BUILDFLAG(IS_CHROMEOS)
#include "chromeos/components/mgs/managed_guest_session_utils.h"
#endif

#if BUILDFLAG(CHROME_FOR_TESTING)
#include "chrome/browser/chrome_for_testing/config.h"
#endif

namespace {

// Idle observer that doesn't do anything.
class StubIdleObserver : public user_education::UserEducationIdleObserver {
 public:
  StubIdleObserver() = default;
  ~StubIdleObserver() override = default;

  // UserEducationIdleObserver:
  std::optional<base::Time> MaybeGetNewLastActiveTime() const override {
    return std::nullopt;
  }
};

}  // namespace

// These are found in chrome/browser/ui/user_education, so extern the factory
// methods to create the necessary objects.
extern std::unique_ptr<user_education::UserEducationIdleObserver>
CreatePollingIdleObserver();
extern std::unique_ptr<RecentSessionObserver> CreateRecentSessionObserver(
    Profile& profile);

UserEducationServiceFactory* UserEducationServiceFactory::GetInstance() {
  static base::NoDestructor<UserEducationServiceFactory> instance;
  return instance.get();
}

// static
UserEducationService* UserEducationServiceFactory::GetForBrowserContext(
    content::BrowserContext* profile) {
  return static_cast<UserEducationService*>(
      GetInstance()->GetServiceForBrowserContext(profile, true));
}

UserEducationServiceFactory::UserEducationServiceFactory()
    : ProfileKeyedServiceFactory(
          "UserEducationService",
          ProfileSelections::Builder()
              .WithRegular(ProfileSelection::kOriginalOnly)
              // TODO(crbug.com/40257657): Check if this service is needed in
              // Guest mode.
              .WithGuest(ProfileSelection::kOriginalOnly)
              // The service is needed by the System Profile OTR (that manages
              // the Profile Picker) to control the IPHs displayed in the
              // Profile Picker.
              .WithSystem(ProfileSelection::kOffTheRecordOnly)
              // TODO(crbug.com/41488885): Check if this service is needed for
              // Ash Internals.
              .WithAshInternals(ProfileSelection::kOriginalOnly)
              .Build()) {}

UserEducationServiceFactory::~UserEducationServiceFactory() = default;

// static
std::unique_ptr<UserEducationService>
UserEducationServiceFactory::BuildServiceInstanceForBrowserContextImpl(
    content::BrowserContext* context,
    bool disable_idle_polling) {
  Profile* const profile = Profile::FromBrowserContext(context);

  // Create the user education service.
  auto result = std::make_unique<UserEducationService>(
      profile, ProfileAllowsUserEducation(profile));

  // Set up the session manager.
  result->user_education_session_manager().Init(
      &result->user_education_storage_service(),
      disable_idle_polling ? std::make_unique<StubIdleObserver>()
                           : CreatePollingIdleObserver(),
      std::make_unique<user_education::UserEducationIdlePolicy>());

  // Possibly install a session observer. This isn't public, since it's
  // self-contained and mostly for tracking state.
  if (result->recent_session_tracker()) {
    result->recent_session_observer_ = CreateRecentSessionObserver(*profile);
    result->recent_session_observer_->Init(*result->recent_session_tracker());
  }

  return result;
}

// static
bool UserEducationServiceFactory::ProfileAllowsUserEducation(Profile* profile) {
#if BUILDFLAG(CHROME_FOR_TESTING)
  if (!chrome_for_testing::IsEnableUserEducationUI()) {
    return false;
  }
#endif

  // In order to do user education, the browser must have a UI and not be an
  // "off-the-record" or in a demo or guest mode.

  if (profile->IsIncognitoProfile() || profile->IsGuestSession() ||
      profiles::IsDemoSession() || profiles::IsChromeAppKioskSession()) {
    return false;
  }

#if BUILDFLAG(IS_CHROMEOS)
  if (chromeos::IsManagedGuestSession()) {
    return false;
  }
#endif

  if (headless::IsHeadlessMode()) {
    return false;
  }

  // If `--no-first-run` is enabled and we have not put IPH into test mode, User
  // Education is not eligible. (No FRE implies no startup bubbles, which
  // precludes any IPH that might show, as it may be used for performance
  // testing.)
  //
  // If IPH is in test mode (all features blocked, or all but a specific feature
  // is blocked) then zero or more IPH are under test and all other IPH are
  // already blocked. Since tests are no-FRE, not making this additional check
  // would break all IPH tests.
  const bool no_first_run =
      base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kNoFirstRun);
  const bool in_feature_test_mode =
      feature_engagement::TrackerFactory::GetForBrowserContext(profile)
          ->IsInFeatureTestMode();
  if (no_first_run && !in_feature_test_mode) {
    return false;
  }

  return true;
}

std::unique_ptr<KeyedService>
UserEducationServiceFactory::BuildServiceInstanceForBrowserContext(
    content::BrowserContext* context) const {
  return BuildServiceInstanceForBrowserContextImpl(context,
                                                   disable_idle_polling_);
}
