// Copyright 2018 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/ash/login/demo_mode/demo_setup_controller.h"

#include <algorithm>
#include <utility>

#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/constants/ash_switches.h"
#include "ash/strings/grit/ash_strings.h"
#include "base/barrier_closure.h"
#include "base/check_deref.h"
#include "base/command_line.h"
#include "base/containers/flat_map.h"
#include "base/containers/span.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/time/time.h"
#include "chrome/browser/ash/login/demo_mode/demo_components.h"
#include "chrome/browser/ash/login/demo_mode/demo_session.h"
#include "chrome/browser/ash/login/startup_utils.h"
#include "chrome/browser/ash/login/wizard_controller.h"
#include "chrome/browser/ash/policy/enrollment/enrollment_config.h"
#include "chrome/browser/ash/policy/enrollment/enrollment_requisition_manager.h"
#include "chrome/browser/ash/policy/enrollment/enrollment_status.h"
#include "chromeos/ash/components/dbus/dbus_thread_manager.h"
#include "chromeos/ash/components/demo_mode/utils/demo_session_utils.h"
#include "chromeos/ash/components/demo_mode/utils/dimensions_utils.h"
#include "chromeos/ash/components/growth/campaigns_manager.h"
#include "chromeos/ash/components/install_attributes/install_attributes.h"
#include "chromeos/ash/experiences/arc/arc_util.h"
#include "chromeos/constants/chromeos_features.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "google_apis/gaia/google_service_auth_error.h"
#include "ui/base/l10n/l10n_util.h"

namespace ash {

namespace {

using ErrorCode = DemoSetupController::DemoSetupError::ErrorCode;
using RecoveryMethod = DemoSetupController::DemoSetupError::RecoveryMethod;

constexpr char kDemoSetupDownloadDurationHistogram[] =
    "DemoMode.Setup.DownloadDuration";
constexpr char kDemoSetupEnrollDurationHistogram[] =
    "DemoMode.Setup.EnrollDuration";
constexpr char kDemoSetupLoadingDurationHistogram[] =
    "DemoMode.Setup.LoadingDuration";
constexpr char kDemoSetupNumRetriesHistogram[] = "DemoMode.Setup.NumRetries";
constexpr char kDemoSetupComponentInitialLoadingResultHistogram[] =
    "DemoMode.Setup.ComponentInitialLoadingResult";
constexpr char kDemoSetupComponentLoadingRetryResultHistogram[] =
    "DemoMode.Setup.ComponentLoadingRetryResult";
constexpr char kDemoSetupErrorHistogram[] = "DemoMode.Setup.Error";

struct DemoSetupStepInfo {
  DemoSetupController::DemoSetupStep step;
  const int step_index;
};

base::span<const DemoSetupStepInfo> GetDemoSetupStepsInfo() {
  static const DemoSetupStepInfo kDemoModeSetupStepsInfo[] = {
      {DemoSetupController::DemoSetupStep::kDownloadResources, 0},
      {DemoSetupController::DemoSetupStep::kEnrollment, 1},
      {DemoSetupController::DemoSetupStep::kComplete, 2}};
  return kDemoModeSetupStepsInfo;
}

DemoSetupController::DemoSetupError CreateFromClientStatus(
    policy::DeviceManagementStatus status,
    const std::string& debug_message) {
  switch (status) {
    case policy::DM_STATUS_SUCCESS:
      return DemoSetupController::DemoSetupError(
          ErrorCode::kUnexpectedError, RecoveryMethod::kUnknown, debug_message);
    case policy::DM_STATUS_REQUEST_INVALID:
      return DemoSetupController::DemoSetupError(
          ErrorCode::kInvalidRequest, RecoveryMethod::kUnknown, debug_message);
    case policy::DM_STATUS_REQUEST_FAILED:
      return DemoSetupController::DemoSetupError(
          ErrorCode::kRequestNetworkError, RecoveryMethod::kCheckNetwork,
          debug_message);
    case policy::DM_STATUS_TEMPORARY_UNAVAILABLE:
      return DemoSetupController::DemoSetupError(
          ErrorCode::kTemporaryUnavailable, RecoveryMethod::kRetry,
          debug_message);
    case policy::DM_STATUS_HTTP_STATUS_ERROR:
    case policy::DM_STATUS_REQUEST_TOO_LARGE:
      return DemoSetupController::DemoSetupError(
          ErrorCode::kResponseError, RecoveryMethod::kUnknown, debug_message);
    case policy::DM_STATUS_RESPONSE_DECODING_ERROR:
      return DemoSetupController::DemoSetupError(
          ErrorCode::kResponseDecodingError, RecoveryMethod::kUnknown,
          debug_message);
    case policy::DM_STATUS_SERVICE_MANAGEMENT_NOT_SUPPORTED:
    case policy::DM_STATUS_SERVICE_CONSUMER_ACCOUNT_WITH_PACKAGED_LICENSE:
    case policy::DM_STATUS_SERVICE_ACTIVATION_PENDING:
    case policy::DM_STATUS_SERVICE_ENTERPRISE_ACCOUNT_IS_NOT_ELIGIBLE_TO_ENROLL:
    case policy::DM_STATUS_SERVICE_ENTERPRISE_TOS_HAS_NOT_BEEN_ACCEPTED:
    case policy::DM_STATUS_SERVICE_ILLEGAL_ACCOUNT_FOR_PACKAGED_EDU_LICENSE:
    case policy::DM_STATUS_SERVICE_INVALID_PACKAGED_DEVICE_FOR_KIOSK:
    case policy::DM_STATUS_SERVICE_ORG_UNIT_ENROLLMENT_LIMIT_EXCEEEDED:
      return DemoSetupController::DemoSetupError(ErrorCode::kDemoAccountError,
                                                 RecoveryMethod::kUnknown,
                                                 debug_message);
    case policy::DM_STATUS_SERVICE_DEVICE_NOT_FOUND:
    case policy::DM_STATUS_SERVICE_DEVICE_NEEDS_RESET:
      return DemoSetupController::DemoSetupError(
          ErrorCode::kDeviceNotFound, RecoveryMethod::kUnknown, debug_message);
    case policy::DM_STATUS_SERVICE_MANAGEMENT_TOKEN_INVALID:
      return DemoSetupController::DemoSetupError(
          ErrorCode::kInvalidDMToken, RecoveryMethod::kUnknown, debug_message);
    case policy::DM_STATUS_SERVICE_INVALID_SERIAL_NUMBER:
      return DemoSetupController::DemoSetupError(
          ErrorCode::kInvalidSerialNumber, RecoveryMethod::kUnknown,
          debug_message);
    case policy::DM_STATUS_SERVICE_DEVICE_ID_CONFLICT:
      return DemoSetupController::DemoSetupError(
          ErrorCode::kDeviceIdError, RecoveryMethod::kUnknown, debug_message);
    case policy::DM_STATUS_SERVICE_TOO_MANY_REQUESTS:
      return DemoSetupController::DemoSetupError(
          ErrorCode::kTooManyRequestsError, RecoveryMethod::kRetry,
          debug_message);
    case policy::DM_STATUS_SERVICE_MISSING_LICENSES:
      return DemoSetupController::DemoSetupError(
          ErrorCode::kLicenseError, RecoveryMethod::kUnknown, debug_message);
    case policy::DM_STATUS_SERVICE_DEPROVISIONED:
      return DemoSetupController::DemoSetupError(
          ErrorCode::kDeviceDeprovisioned, RecoveryMethod::kUnknown,
          debug_message);
    case policy::DM_STATUS_SERVICE_DOMAIN_MISMATCH:
      return DemoSetupController::DemoSetupError(
          ErrorCode::kDomainMismatch, RecoveryMethod::kUnknown, debug_message);
    case policy::DM_STATUS_CANNOT_SIGN_REQUEST:
      return DemoSetupController::DemoSetupError(
          ErrorCode::kSigningError, RecoveryMethod::kPowerwash, debug_message);
    case policy::DM_STATUS_SERVICE_POLICY_NOT_FOUND:
      return DemoSetupController::DemoSetupError(
          ErrorCode::kPolicyNotFound, RecoveryMethod::kUnknown, debug_message);
    case policy::DM_STATUS_SERVICE_ARC_DISABLED:
      return DemoSetupController::DemoSetupError(
          ErrorCode::kArcError, RecoveryMethod::kUnknown, debug_message);
  }
  NOTREACHED() << "Demo mode setup received unsupported client status";
}

DemoSetupController::DemoSetupError CreateFromLockStatus(
    InstallAttributes::LockResult status,
    const std::string& debug_message) {
  switch (status) {
    case InstallAttributes::LOCK_SUCCESS:
    case InstallAttributes::LOCK_NOT_READY:
      return DemoSetupController::DemoSetupError(
          ErrorCode::kUnexpectedError, RecoveryMethod::kUnknown, debug_message);
    case InstallAttributes::LOCK_TIMEOUT:
      return DemoSetupController::DemoSetupError(
          ErrorCode::kLockTimeout, RecoveryMethod::kReboot, debug_message);
    case InstallAttributes::LOCK_BACKEND_INVALID:
    case InstallAttributes::LOCK_SET_ERROR:
    case InstallAttributes::LOCK_FINALIZE_ERROR:
    case InstallAttributes::LOCK_READBACK_ERROR:
      return DemoSetupController::DemoSetupError(
          ErrorCode::kLockError, RecoveryMethod::kPowerwash, debug_message);
    case InstallAttributes::LOCK_ALREADY_LOCKED:
    case InstallAttributes::LOCK_WRONG_DOMAIN:
    case InstallAttributes::LOCK_WRONG_MODE:
      return DemoSetupController::DemoSetupError(
          ErrorCode::kAlreadyLocked, RecoveryMethod::kPowerwash, debug_message);
  }
  NOTREACHED() << "Demo mode setup received unsupported lock status";
}

}  //  namespace

// static
DemoSetupController::DemoSetupError
DemoSetupController::DemoSetupError::CreateFromEnrollmentStatus(
    const policy::EnrollmentStatus& status) {
  const std::string debug_message = base::StringPrintf(
      "EnrollmentError: (code: %d, client_status: %d, store_status: %d, "
      "validation_status: %d, lock_status: %d)",
      static_cast<int>(status.enrollment_code()), status.client_status(),
      status.store_status(), status.validation_status(), status.lock_status());

  switch (status.enrollment_code()) {
    case policy::EnrollmentStatus::Code::kSuccess:
      return DemoSetupError(ErrorCode::kUnexpectedError,
                            RecoveryMethod::kUnknown, debug_message);
    case policy::EnrollmentStatus::Code::kNoStateKeys:
      return DemoSetupError(ErrorCode::kNoStateKeys, RecoveryMethod::kReboot,
                            debug_message);
    case policy::EnrollmentStatus::Code::kRegistrationFailed:
      return CreateFromClientStatus(status.client_status(), debug_message);
    case policy::EnrollmentStatus::Code::kRobotAuthFetchFailed:
    case policy::EnrollmentStatus::Code::kRobotRefreshFetchFailed:
      return DemoSetupError(ErrorCode::kRobotFetchError,
                            RecoveryMethod::kCheckNetwork, debug_message);
    case policy::EnrollmentStatus::Code::kRobotRefreshStoreFailed:
      return DemoSetupError(ErrorCode::kRobotStoreError,
                            RecoveryMethod::kReboot, debug_message);
    case policy::EnrollmentStatus::Code::kRegistrationBadMode:
      return DemoSetupError(ErrorCode::kBadMode, RecoveryMethod::kRetry,
                            debug_message);
    case policy::EnrollmentStatus::Code::kRegistrationCertFetchFailed:
      return DemoSetupError(ErrorCode::kCertFetchError, RecoveryMethod::kRetry,
                            debug_message);
    case policy::EnrollmentStatus::Code::kPolicyFetchFailed:
      return DemoSetupError(ErrorCode::kPolicyFetchError,
                            RecoveryMethod::kRetry, debug_message);
    case policy::EnrollmentStatus::Code::kValidationFailed:
      return DemoSetupError(ErrorCode::kPolicyValidationError,
                            RecoveryMethod::kRetry, debug_message);
    case policy::EnrollmentStatus::Code::kLockError:
      return CreateFromLockStatus(status.lock_status(), debug_message);
    case policy::EnrollmentStatus::Code::kStoreError:
      return DemoSetupError(ErrorCode::kOnlineStoreError,
                            RecoveryMethod::kRetry, debug_message);
    case policy::EnrollmentStatus::Code::kAttributeUpdateFailed:
      return DemoSetupError(ErrorCode::kUnexpectedError,
                            RecoveryMethod::kUnknown, debug_message);
    case policy::EnrollmentStatus::Code::kNoMachineIdentification:
      return DemoSetupError(ErrorCode::kMachineIdentificationError,
                            RecoveryMethod::kUnknown, debug_message);
    case policy::EnrollmentStatus::Code::kDmTokenStoreFailed:
      return DemoSetupError(ErrorCode::kDMTokenStoreError,
                            RecoveryMethod::kUnknown, debug_message);
    case policy::EnrollmentStatus::Code::kMayNotBlockDevMode:
      return DemoSetupError(ErrorCode::kUnexpectedError,
                            RecoveryMethod::kUnknown, debug_message);
  }
  NOTREACHED() << "Demo mode setup received unsupported enrollment status";
}

// static
DemoSetupController::DemoSetupError
DemoSetupController::DemoSetupError::CreateFromOtherEnrollmentError(
    EnrollmentLauncher::OtherError error) {
  const std::string debug_message =
      base::StringPrintf("Other error: %d", error);
  switch (error) {
    case EnrollmentLauncher::OTHER_ERROR_DOMAIN_MISMATCH:
      return DemoSetupError(ErrorCode::kAlreadyLocked,
                            RecoveryMethod::kPowerwash, debug_message);
    case EnrollmentLauncher::OTHER_ERROR_FATAL:
      return DemoSetupError(ErrorCode::kUnexpectedError,
                            RecoveryMethod::kUnknown, debug_message);
  }
  NOTREACHED() << "Demo mode setup received unsupported enrollment error";
}

// static
DemoSetupController::DemoSetupError
DemoSetupController::DemoSetupError::CreateFromComponentError(
    component_updater::ComponentManagerAsh::Error error,
    std::string component_name) {
  const std::string debug_message =
      base::StringPrintf("Failed to load '%s' CrOS component with error: %d",
                         component_name.c_str(), static_cast<int>(error));
  return DemoSetupError(ErrorCode::kOnlineComponentError,
                        RecoveryMethod::kCheckNetwork, debug_message);
}

DemoSetupController::DemoSetupError::DemoSetupError(
    DemoSetupError::ErrorCode error_code,
    DemoSetupError::RecoveryMethod recovery_method)
    : error_code_(error_code), recovery_method_(recovery_method) {}

DemoSetupController::DemoSetupError::DemoSetupError(
    DemoSetupError::ErrorCode error_code,
    DemoSetupError::RecoveryMethod recovery_method,
    const std::string& debug_message)
    : error_code_(error_code),
      recovery_method_(recovery_method),
      debug_message_(debug_message) {}

DemoSetupController::DemoSetupError::~DemoSetupError() = default;

std::u16string DemoSetupController::DemoSetupError::GetLocalizedErrorMessage()
    const {
  switch (error_code_) {
    case ErrorCode::kOnlineFRECheckRequired:
      return l10n_util::GetStringUTF16(
          IDS_DEMO_SETUP_OFFLINE_UNAVAILABLE_ERROR);
    case ErrorCode::kOnlineComponentError:
      return l10n_util::GetStringUTF16(IDS_DEMO_SETUP_COMPONENT_ERROR);
    case ErrorCode::kNoStateKeys:
      return l10n_util::GetStringUTF16(IDS_DEMO_SETUP_NO_STATE_KEYS_ERROR);
    case ErrorCode::kInvalidRequest:
      return l10n_util::GetStringUTF16(IDS_DEMO_SETUP_INVALID_REQUEST_ERROR);
    case ErrorCode::kRequestNetworkError:
      return l10n_util::GetStringUTF16(IDS_DEMO_SETUP_NETWORK_ERROR);
    case ErrorCode::kTemporaryUnavailable:
      return l10n_util::GetStringUTF16(IDS_DEMO_SETUP_TEMPORARY_ERROR);
    case ErrorCode::kResponseError:
      return l10n_util::GetStringUTF16(IDS_DEMO_SETUP_RESPONSE_ERROR);
    case ErrorCode::kResponseDecodingError:
      return l10n_util::GetStringUTF16(IDS_DEMO_SETUP_RESPONSE_DECODING_ERROR);
    case ErrorCode::kDemoAccountError:
      return l10n_util::GetStringUTF16(IDS_DEMO_SETUP_ACCOUNT_ERROR);
    case ErrorCode::kDeviceNotFound:
      return l10n_util::GetStringUTF16(IDS_DEMO_SETUP_DEVICE_NOT_FOUND_ERROR);
    case ErrorCode::kInvalidDMToken:
      return l10n_util::GetStringUTF16(IDS_DEMO_SETUP_INVALID_DM_TOKEN_ERROR);
    case ErrorCode::kInvalidSerialNumber:
      return l10n_util::GetStringUTF16(
          IDS_DEMO_SETUP_INVALID_SERIAL_NUMBER_ERROR);
    case ErrorCode::kDeviceIdError:
      return l10n_util::GetStringUTF16(IDS_DEMO_SETUP_DEVICE_ID_ERROR);
    case ErrorCode::kTooManyRequestsError:
      return l10n_util::GetStringUTF16(IDS_DEMO_SETUP_TOO_MANY_REQUESTS_ERROR);
    case ErrorCode::kLicenseError:
      return l10n_util::GetStringUTF16(IDS_DEMO_SETUP_LICENSE_ERROR);
    case ErrorCode::kDeviceDeprovisioned:
      return l10n_util::GetStringUTF16(IDS_DEMO_SETUP_DEPROVISIONED_ERROR);
    case ErrorCode::kDomainMismatch:
      return l10n_util::GetStringUTF16(IDS_DEMO_SETUP_DOMAIN_MISMATCH_ERROR);
    case ErrorCode::kSigningError:
      return l10n_util::GetStringUTF16(IDS_DEMO_SETUP_SIGNING_ERROR);
    case ErrorCode::kPolicyNotFound:
      return l10n_util::GetStringUTF16(IDS_DEMO_SETUP_POLICY_NOT_FOUND_ERROR);
    case ErrorCode::kArcError:
      return l10n_util::GetStringUTF16(IDS_DEMO_SETUP_ARC_ERROR);
    case ErrorCode::kRobotFetchError:
      return l10n_util::GetStringUTF16(IDS_DEMO_SETUP_ROBOT_ERROR);
    case ErrorCode::kRobotStoreError:
      return l10n_util::GetStringUTF16(IDS_DEMO_SETUP_ROBOT_STORE_ERROR);
    case ErrorCode::kBadMode:
      return l10n_util::GetStringUTF16(IDS_DEMO_SETUP_BAD_MODE_ERROR);
    case ErrorCode::kCertFetchError:
      return l10n_util::GetStringUTF16(IDS_DEMO_SETUP_CERT_FETCH_ERROR);
    case ErrorCode::kPolicyFetchError:
      return l10n_util::GetStringUTF16(IDS_DEMO_SETUP_POLICY_FETCH_ERROR);
    case ErrorCode::kPolicyValidationError:
      return l10n_util::GetStringUTF16(IDS_DEMO_SETUP_POLICY_VALIDATION_ERROR);
    case ErrorCode::kLockTimeout:
      return l10n_util::GetStringUTF16(IDS_DEMO_SETUP_LOCK_TIMEOUT_ERROR);
    case ErrorCode::kLockError:
      return l10n_util::GetStringUTF16(IDS_DEMO_SETUP_LOCK_ERROR);
    case ErrorCode::kAlreadyLocked:
      return l10n_util::GetStringUTF16(IDS_DEMO_SETUP_ALREADY_LOCKED_ERROR);
    case ErrorCode::kOnlineStoreError:
      return l10n_util::GetStringUTF16(IDS_DEMO_SETUP_ONLINE_STORE_ERROR);
    case ErrorCode::kMachineIdentificationError:
      return l10n_util::GetStringUTF16(
          IDS_DEMO_SETUP_NO_MACHINE_IDENTIFICATION_ERROR);
    case ErrorCode::kDMTokenStoreError:
      return l10n_util::GetStringUTF16(IDS_DEMO_SETUP_DM_TOKEN_STORE_ERROR);
    case ErrorCode::kUnexpectedError:
      return l10n_util::GetStringUTF16(IDS_DEMO_SETUP_UNEXPECTED_ERROR);
    case ErrorCode::kSuccess:
      // We don't display the success message. It's for recording the UMA
      // metrics only.
      return std::u16string();
  }
  NOTREACHED() << "No localized error message available for demo setup error.";
}

std::u16string
DemoSetupController::DemoSetupError::GetLocalizedRecoveryMessage() const {
  switch (recovery_method_) {
    case RecoveryMethod::kRetry:
      return l10n_util::GetStringUTF16(IDS_DEMO_SETUP_RECOVERY_RETRY);
    case RecoveryMethod::kReboot:
      return l10n_util::GetStringUTF16(IDS_DEMO_SETUP_RECOVERY_REBOOT);
    case RecoveryMethod::kPowerwash:
      return l10n_util::GetStringUTF16(IDS_DEMO_SETUP_RECOVERY_POWERWASH);
    case RecoveryMethod::kCheckNetwork:
      return l10n_util::GetStringUTF16(IDS_DEMO_SETUP_RECOVERY_CHECK_NETWORK);
    case RecoveryMethod::kOnlineOnly:
      return l10n_util::GetStringUTF16(IDS_DEMO_SETUP_RECOVERY_OFFLINE_FATAL);
    case RecoveryMethod::kUnknown:
      return l10n_util::GetStringUTF16(IDS_DEMO_SETUP_RECOVERY_FATAL);
  }
  NOTREACHED()
      << "No localized error message available for demo setup recovery method.";
}

std::string DemoSetupController::DemoSetupError::GetDebugDescription() const {
  return base::StringPrintf("DemoSetupError (code: %d, recovery: %d) : %s",
                            static_cast<int>(error_code_),
                            static_cast<int>(recovery_method_),
                            debug_message_.c_str());
}

// static
void DemoSetupController::ClearDemoRequisition(PrefService& local_state) {
  if (policy::EnrollmentRequisitionManager::GetDeviceRequisition(local_state) ==
      policy::EnrollmentRequisitionManager::kDemoRequisition) {
    policy::EnrollmentRequisitionManager::SetDeviceRequisition(local_state,
                                                               std::string());
    // If device requisition is `kDemoRequisition`, it means the sub
    // organization was also set by the demo setup controller, so remove it as
    // well.
    policy::EnrollmentRequisitionManager::SetSubOrganization(local_state,
                                                             std::string());
  }
}

// static
bool DemoSetupController::IsDemoModeAllowed() {
  // Demo mode is only allowed on devices that support ARC++.
  return arc::IsArcAvailable();
}

// static
bool DemoSetupController::IsOobeDemoSetupFlowInProgress() {
  const WizardController* const wizard_controller =
      WizardController::default_controller();
  return wizard_controller &&
         wizard_controller->demo_setup_controller() != nullptr;
}

// static
std::string DemoSetupController::GetSubOrganizationEmail(
    const PrefService& local_state) {
  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
  DCHECK(command_line);

  if (command_line->HasSwitch(switches::kDemoModeEnrollingUsername)) {
    std::string customUser =
        command_line->GetSwitchValueASCII(switches::kDemoModeEnrollingUsername);
    if (!customUser.empty()) {
      std::string email = customUser + "@" + policy::kDemoModeDomain;
      VLOG(1) << "Enrolling into Demo Mode with user: " << email;
      return email;
    }
  }

  const std::string country = local_state.GetString(prefs::kDemoModeCountry);

  std::string country_uppercase = base::ToUpperASCII(country);
  std::string country_lowercase = base::ToLowerASCII(country);

  // Exclude US as it is the default country.
  if (std::ranges::contains(demo_mode::kSupportedCountries,
                            country_uppercase)) {
    if (chromeos::features::IsCloudGamingDeviceEnabled()) {
      return base::StringPrintf("admin-%s-blazey@%s", country_lowercase.c_str(),
                                policy::kDemoModeDomain);
    }

    return "admin-" + country_lowercase + "@" + policy::kDemoModeDomain;
  }
  return std::string();
}

// static
base::DictValue DemoSetupController::GetDemoSetupSteps() {
  base::DictValue setup_steps_dict;
  for (auto entry : GetDemoSetupStepsInfo()) {
    setup_steps_dict.Set(GetDemoSetupStepString(entry.step), entry.step_index);
  }

  return setup_steps_dict;
}

// static
std::string DemoSetupController::GetDemoSetupStepString(
    const DemoSetupStep step_enum) {
  switch (step_enum) {
    case DemoSetupStep::kDownloadResources:
      return "downloadResources";
    case DemoSetupStep::kEnrollment:
      return "enrollment";
    case DemoSetupStep::kComplete:
      return "complete";
  }

  NOTREACHED();
}

DemoSetupController::DemoSetupController(
    PrefService* local_state,
    scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory,
    policy::BrowserPolicyConnectorAsh* browser_policy_connector_ash,
    scoped_refptr<component_updater::ComponentManagerAsh> component_manager_ash)
    : local_state_(CHECK_DEREF(local_state)),
      shared_url_loader_factory_(std::move(shared_url_loader_factory)),
      browser_policy_connector_ash_(CHECK_DEREF(browser_policy_connector_ash)),
      component_manager_ash_(std::move(component_manager_ash)) {
  CHECK(shared_url_loader_factory_);
  CHECK(component_manager_ash_);
}

DemoSetupController::~DemoSetupController() = default;

void DemoSetupController::SetAndCanonicalizeRetailerName(
    const std::string& retailer_name) {
  retailer_name_ = ash::demo_mode::CanonicalizeDimension(retailer_name);
}

void DemoSetupController::Enroll(
    OnSetupSuccess on_setup_success,
    OnSetupError on_setup_error,
    const OnSetCurrentSetupStep& set_current_setup_step) {
  DCHECK_NE(demo_config_, DemoSession::DemoModeConfig::kNone)
      << "Demo config needs to be explicitly set before calling Enroll()";
  DCHECK(!enrollment_launcher_);

  set_current_setup_step_ = set_current_setup_step;
  on_setup_success_ = std::move(on_setup_success);
  on_setup_error_ = std::move(on_setup_error);

  VLOG(1) << "Starting demo setup "
          << DemoSession::DemoConfigToString(demo_config_);

  SetCurrentSetupStep(DemoSetupStep::kDownloadResources);

  local_state_->SetString(prefs::kDemoModeRetailerId, retailer_name_);
  local_state_->SetString(prefs::kDemoModeStoreId, store_number_);

  switch (demo_config_) {
    case DemoSession::DemoModeConfig::kOnline:
      LoadDemoComponents();
      return;
    case DemoSession::DemoModeConfig::kNone:
    case DemoSession::DemoModeConfig::kOfflineDeprecated:
      NOTREACHED() << "No valid demo mode config specified";
  }
}

void DemoSetupController::LoadDemoComponents() {
  VLOG(1) << "Loading demo resources and demo app components";

  download_start_time_ = base::TimeTicks::Now();

  if (!demo_components_)
    demo_components_ = std::make_unique<DemoComponents>(
        &local_state_.get(), component_manager_ash_, demo_config_);

  // Simulate loading demo components completed for unit tests.
  if (DBusThreadManager::Get()->IsUsingFakes() &&
      !load_real_components_for_test_) {
    demo_components_->SetCrOSComponentLoadedForTesting(
        base::FilePath(), component_error_for_tests_);

    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(&DemoSetupController::OnDemoComponentsLoaded,
                                  weak_ptr_factory_.GetWeakPtr()));
    return;
  }

  auto is_growth_campaigns_enabled_in_demo_mode =
      features::IsGrowthCampaignsInDemoModeEnabled();
  base::OnceClosure load_callback =
      base::BindOnce(&DemoSetupController::OnDemoComponentsLoaded,
                     weak_ptr_factory_.GetWeakPtr());
  base::RepeatingClosure barrier_closure =
      base::BarrierClosure(is_growth_campaigns_enabled_in_demo_mode ? 3 : 2,
                           std::move(load_callback));
  demo_components_->LoadResourcesComponent(barrier_closure);
  demo_components_->LoadAppComponent(barrier_closure);
  if (is_growth_campaigns_enabled_in_demo_mode) {
    // Growth campaign is enabled in demo mode, also load growth campaigns
    // component.
    growth::CampaignsManager::Get()->LoadCampaigns(barrier_closure,
                                                   /*in_oobe=*/true);
  }
}

void DemoSetupController::OnDemoComponentsLoaded() {
  DCHECK_EQ(demo_config_, DemoSession::DemoModeConfig::kOnline);

  base::TimeDelta download_duration =
      base::TimeTicks::Now() - download_start_time_;
  base::UmaHistogramLongTimes100(kDemoSetupDownloadDurationHistogram,
                                 download_duration);
  SetCurrentSetupStep(DemoSetupStep::kEnrollment);

  auto resources_component_error =
      demo_components_->resources_component_error().value_or(
          component_updater::ComponentManagerAsh::Error::NOT_FOUND);
  auto app_component_error = demo_components_->app_component_error().value_or(
      component_updater::ComponentManagerAsh::Error::NOT_FOUND);
  // We determine it's an initial loading or retry based on the
  // `num_setup_retries_` count.
  const std::string kDemoSetupComponentlLoadingResultHistogram =
      num_setup_retries_ == 0 ? kDemoSetupComponentInitialLoadingResultHistogram
                              : kDemoSetupComponentLoadingRetryResultHistogram;

  if (resources_component_error !=
      component_updater::ComponentManagerAsh::Error::NONE) {
    // Reporting the corresponding enum based on the app component error.
    base::UmaHistogramEnumeration(
        kDemoSetupComponentlLoadingResultHistogram,
        app_component_error ==
                component_updater::ComponentManagerAsh::Error::NONE
            ? DemoSetupComponentLoadingResult::kAppSuccessResourcesFailure
            : DemoSetupComponentLoadingResult::kAppFailureResourcesFailure);

    SetupFailed(DemoSetupError::CreateFromComponentError(
        resources_component_error,
        DemoComponents::kDemoModeResourcesComponentName));
    return;
  }

  if (app_component_error !=
      component_updater::ComponentManagerAsh::Error::NONE) {
    // There should be no error on the resources component loading if we've got
    // to this point. It should've been handled in the previous "if block".
    DCHECK(resources_component_error ==
           component_updater::ComponentManagerAsh::Error::NONE);
    base::UmaHistogramEnumeration(
        kDemoSetupComponentlLoadingResultHistogram,
        DemoSetupComponentLoadingResult::kAppFailureResourcesSuccess);

    SetupFailed(DemoSetupError::CreateFromComponentError(
        app_component_error, DemoComponents::kDemoModeAppComponentName));
    return;
  }

  // There should be no error on both the app and the resources components
  // loading if we've got to this point. It should've been handled in the
  // previous two "if blocks".
  base::UmaHistogramEnumeration(
      kDemoSetupComponentlLoadingResultHistogram,
      DemoSetupComponentLoadingResult::kAppSuccessResourcesSuccess);

  VLOG(1) << "Starting online enrollment";

  enroll_start_time_ = base::TimeTicks::Now();

  DCHECK(policy::EnrollmentRequisitionManager::GetDeviceRequisition(
             local_state_.get())
             .empty());
  policy::EnrollmentRequisitionManager::SetDeviceRequisition(
      local_state_.get(),
      policy::EnrollmentRequisitionManager::kDemoRequisition);
  policy::EnrollmentRequisitionManager::SetSubOrganization(
      local_state_.get(), GetSubOrganizationEmail(local_state_.get()));
  policy::EnrollmentConfig config =
      policy::EnrollmentConfig::GetDemoModeEnrollmentConfig();

  enrollment_launcher_ = EnrollmentLauncher::Create(
      &local_state_.get(), shared_url_loader_factory_,
      &browser_policy_connector_ash_.get(), this, config,
      policy::kDemoModeDomain);
  enrollment_launcher_->EnrollUsingAttestation();
}

void DemoSetupController::OnAuthError(const GoogleServiceAuthError& error) {
  NOTREACHED();
}

void DemoSetupController::OnEnrollmentError(policy::EnrollmentStatus status) {
  SetupFailed(DemoSetupError::CreateFromEnrollmentStatus(status));
}

void DemoSetupController::OnOtherError(EnrollmentLauncher::OtherError error) {
  SetupFailed(DemoSetupError::CreateFromOtherEnrollmentError(error));
}

void DemoSetupController::OnDeviceEnrolled() {
  DCHECK_NE(demo_config_, DemoSession::DemoModeConfig::kNone);

  // `enroll_start_time_` is only set for online enrollment.
  if (!enroll_start_time_.is_null()) {
    base::TimeDelta enroll_duration =
        base::TimeTicks::Now() - enroll_start_time_;
    base::UmaHistogramLongTimes100(kDemoSetupEnrollDurationHistogram,
                                   enroll_duration);
  }
  UMA_HISTOGRAM_ENUMERATION(kDemoSetupErrorHistogram, ErrorCode::kSuccess);
  VLOG(1) << "Marking device registered";
  StartupUtils::MarkDeviceRegistered(
      local_state_.get(),
      base::BindOnce(&DemoSetupController::OnDeviceRegistered,
                     weak_ptr_factory_.GetWeakPtr()));
}

void DemoSetupController::OnDeviceAttributeUploadCompleted(bool success) {
  NOTREACHED();
}

void DemoSetupController::OnDeviceAttributeUpdatePermission(bool granted) {
  NOTREACHED();
}

void DemoSetupController::SetCrOSComponentLoadErrorForTest(
    component_updater::ComponentManagerAsh::Error error) {
  component_error_for_tests_ = error;
}

void DemoSetupController::EnableLoadRealComponentsForTest() {
  load_real_components_for_test_ = true;
}

void DemoSetupController::OnDeviceRegistered() {
  VLOG(1) << "Demo mode setup finished successfully.";

  if (demo_config_ == DemoSession::DemoModeConfig::kOnline) {
    base::TimeDelta loading_duration =
        base::TimeTicks::Now() - download_start_time_;
    // A similar metric can be found at OOBE.StepCompletionTime.Demo-setup,
    // however this is only useful for durations up to three minutes. Demo mode
    // setup typically takes longer than this, so we use a LONG_TIMES metric
    // here to capture metrics of up to one hour.
    base::UmaHistogramLongTimes100(kDemoSetupLoadingDurationHistogram,
                                   loading_duration);
  }

  base::UmaHistogramCounts100(kDemoSetupNumRetriesHistogram,
                              num_setup_retries_);

  SetCurrentSetupStep(DemoSetupStep::kComplete);

  local_state_->SetInteger(prefs::kDemoModeConfig,
                           static_cast<int>(demo_config_));
  local_state_->CommitPendingWrite();
  Reset();
  if (!on_setup_success_.is_null())
    std::move(on_setup_success_).Run();
}

void DemoSetupController::SetCurrentSetupStep(DemoSetupStep current_step) {
  if (!set_current_setup_step_.is_null())
    set_current_setup_step_.Run(current_step);
}

void DemoSetupController::SetupFailed(const DemoSetupError& error) {
  num_setup_retries_++;
  Reset();
  LOG(ERROR) << error.GetDebugDescription();
  if (!on_setup_error_.is_null())
    std::move(on_setup_error_).Run(error);
  UMA_HISTOGRAM_ENUMERATION(kDemoSetupErrorHistogram, error.error_code());
}

void DemoSetupController::Reset() {
  DCHECK_NE(demo_config_, DemoSession::DemoModeConfig::kNone);

  // `demo_config_` is not reset here, because it is needed for retrying setup.
  enrollment_launcher_.reset();
  ClearDemoRequisition(local_state_.get());
}

}  //  namespace ash
