// Copyright 2013 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/app_mode/kiosk_app_launch_error.h"

#include <optional>
#include <string>
#include <string_view>

#include "ash/strings/grit/ash_strings.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/notreached.h"
#include "base/values.h"
#include "chrome/browser/ash/app_mode/kiosk_chrome_app_manager.h"
#include "chromeos/ash/components/login/auth/public/auth_failure.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "ui/base/l10n/l10n_util.h"

namespace ash {

namespace {

// Key under "kiosk" dictionary to store the last launch error.
constexpr std::string_view kKeyLaunchError = "launch_error";

// Key under "kiosk" dictionary to store the last cryptohome error.
constexpr std::string_view kKeyCryptohomeFailure = "cryptohome_failure";

// Key under "kiosk" dictionary to store whether the user cancelled the last
// launch.
constexpr std::string_view kUserCancelledLaunchKey = "user_cancelled_launch";

// Error from the last kiosk launch.
std::optional<KioskAppLaunchError::Error> s_last_error;

}  // namespace

// static
std::string KioskAppLaunchError::GetErrorMessage(Error error) {
  switch (error) {
    case Error::kNone:
      return std::string();

    case Error::kHasPendingLaunch:
    case Error::kNotKioskEnabled:
    case Error::kUnableToRetrieveHash:
    case Error::kPolicyLoadFailed:
    case Error::kUserNotAllowlisted:
      return l10n_util::GetStringUTF8(IDS_KIOSK_APP_FAILED_TO_LAUNCH);

    case Error::kCryptohomedNotRunning:
    case Error::kAlreadyMounted:
    case Error::kUnableToMount:
    case Error::kUnableToRemove:
      return l10n_util::GetStringUTF8(IDS_KIOSK_APP_ERROR_UNABLE_TO_MOUNT);

    case Error::kUnableToInstall:
      return l10n_util::GetStringUTF8(IDS_KIOSK_APP_ERROR_UNABLE_TO_INSTALL);

    case Error::kUserCancel:
      return l10n_util::GetStringUTF8(IDS_KIOSK_APP_ERROR_USER_CANCEL);

    case Error::kUnableToDownload:
      return l10n_util::GetStringUTF8(IDS_KIOSK_APP_ERROR_UNABLE_TO_DOWNLOAD);

    case Error::kUnableToLaunch:
      return l10n_util::GetStringUTF8(IDS_KIOSK_APP_ERROR_UNABLE_TO_LAUNCH);

    case Error::kExtensionsLoadTimeout:
      return l10n_util::GetStringUTF8(
          IDS_KIOSK_APP_ERROR_EXTENSIONS_LOAD_TIMEOUT);

    case Error::kExtensionsPolicyInvalid:
      return l10n_util::GetStringUTF8(
          IDS_KIOSK_APP_ERROR_EXTENSIONS_POLICY_INVALID);
    case Error::kChromeAppDeprecated:
      return l10n_util::GetStringUTF8(
          IDS_KIOSK_APP_ERROR_UNABLE_TO_LAUNCH_CHROME_APP);
    case Error::kIsolatedAppNotAllowed:
      return l10n_util::GetStringUTF8(IDS_KIOSK_APP_ERROR_IWA_UNSUPPORTED);
  }

  NOTREACHED() << "Unknown kiosk app launch error, error="
               << static_cast<int>(error);
}

// static
void KioskAppLaunchError::Save(PrefService& local_state,
                               KioskAppLaunchError::Error error) {
  s_last_error = error;

  {
    ScopedDictPrefUpdate dict_update(
        &local_state, KioskChromeAppManager::kKioskDictionaryName);
    dict_update->Set(kKeyLaunchError, static_cast<int>(error));
  }

  // Make sure that the kiosk launch error gets written to disk before the
  // browser is killed.
  local_state.CommitPendingWrite();
}

// static
void KioskAppLaunchError::SaveCryptohomeFailure(
    PrefService& local_state,
    const AuthFailure& auth_failure) {
  ScopedDictPrefUpdate dict_update(&local_state,
                                   KioskChromeAppManager::kKioskDictionaryName);
  dict_update->SetByDottedPath(kKeyCryptohomeFailure, auth_failure.reason());
}

// static
void KioskAppLaunchError::SaveUserCancelledLaunch(PrefService& local_state) {
  ScopedDictPrefUpdate dict_update(&local_state,
                                   KioskChromeAppManager::kKioskDictionaryName);
  dict_update->SetByDottedPath(kUserCancelledLaunchKey, true);
}

// static
KioskAppLaunchError::Error KioskAppLaunchError::Get(
    const PrefService& local_state) {
  if (s_last_error) {
    return *s_last_error;
  }
  s_last_error = Error::kNone;
  const base::DictValue& dict =
      local_state.GetDict(KioskChromeAppManager::kKioskDictionaryName);

  std::optional<int> error = dict.FindInt(kKeyLaunchError);
  if (error.has_value()) {
    s_last_error = static_cast<KioskAppLaunchError::Error>(error.value());
    return *s_last_error;
  }

  return Error::kNone;
}

// static
bool KioskAppLaunchError::DidUserCancelLaunch(const PrefService& local_state) {
  const base::DictValue& dict =
      local_state.GetDict(KioskChromeAppManager::kKioskDictionaryName);
  return dict.FindBool(kUserCancelledLaunchKey).value_or(false);
}

// static
void KioskAppLaunchError::RecordMetricAndClear(PrefService& local_state) {
  ScopedDictPrefUpdate dict_update(&local_state,
                                   KioskChromeAppManager::kKioskDictionaryName);

  std::optional<int> error = dict_update->FindInt(kKeyLaunchError);
  if (error) {
    base::UmaHistogramEnumeration(kKioskLaunchErrorHistogram,
                                  static_cast<Error>(*error));
  }

  dict_update->Remove(kKeyLaunchError);
  s_last_error = std::nullopt;

  std::optional<int> cryptohome_failure =
      dict_update->FindInt(kKeyCryptohomeFailure);
  if (cryptohome_failure) {
    UMA_HISTOGRAM_ENUMERATION(
        "Kiosk.Launch.CryptohomeFailure",
        static_cast<AuthFailure::FailureReason>(*cryptohome_failure),
        AuthFailure::NUM_FAILURE_REASONS);
  }
  dict_update->Remove(kKeyCryptohomeFailure);

  dict_update->Remove(kUserCancelledLaunchKey);
}

}  // namespace ash
