// Copyright 2019 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_cryptohome_remover.h"

#include <algorithm>
#include <optional>
#include <string>
#include <utility>

#include "base/barrier_closure.h"
#include "base/check_deref.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/logging.h"
#include "chrome/browser/ash/app_mode/pref_names.h"
#include "chromeos/ash/components/cryptohome/error_util.h"
#include "chromeos/ash/components/cryptohome/userdataauth_util.h"
#include "chromeos/ash/components/dbus/userdataauth/userdataauth_client.h"
#include "components/account_id/account_id.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/session_manager/core/session_manager.h"
#include "components/user_manager/user.h"
#include "components/user_manager/user_manager.h"
#include "third_party/cros_system_api/dbus/cryptohome/dbus-constants.h"

namespace ash {

KioskCryptohomeRemover::KioskCryptohomeRemover(PrefService* local_state)
    : local_state_(CHECK_DEREF(local_state)) {}

KioskCryptohomeRemover::~KioskCryptohomeRemover() = default;

void KioskCryptohomeRemover::RegisterPrefs(PrefRegistrySimple* registry) {
  registry->RegisterDictionaryPref(prefs::kAllKioskUsersToRemove);
}

void KioskCryptohomeRemover::RemoveObsoleteCryptohomes() {
  auto* client = UserDataAuthClient::Get();
  client->WaitForServiceToBeAvailable(
      base::BindOnce(&KioskCryptohomeRemover::PerformDelayedCryptohomeRemovals,
                     weak_ptr_factory_.GetWeakPtr()));
}

void KioskCryptohomeRemover::CancelDelayedCryptohomeRemoval(
    const AccountId& account_id) {
  UnscheduleDelayedCryptohomeRemoval(cryptohome::Identification(account_id));
}

void KioskCryptohomeRemover::RemoveCryptohomesAndExitIfNeeded(
    const std::vector<AccountId>& account_ids) {
  base::RepeatingClosure cryptohomes_barrier_closure;
  const user_manager::User* active_user =
      user_manager::UserManager::Get()->GetActiveUser();
  AccountId active_account_id;
  if (active_user) {
    active_account_id = active_user->GetAccountId();
  }
  if (std::ranges::contains(account_ids, active_account_id)) {
    cryptohomes_barrier_closure = BarrierClosure(
        account_ids.size() - 1, base::BindOnce([]() {
          session_manager::SessionManager::Get()->RequestSignOut();
        }));
  }

  // First schedule cryptohome removal in case there is a power failure during
  // cryptohome calls.
  for (const auto& account_id : account_ids) {
    ScheduleDelayedCryptohomeRemoval(account_id);
  }

  for (auto& account_id : account_ids) {
    if (account_id != active_account_id) {
      user_data_auth::RemoveRequest request;
      const cryptohome::Identification cryptohome_id(account_id);
      request.mutable_identifier()->set_account_id(cryptohome_id.id());
      UserDataAuthClient::Get()->Remove(
          request,
          base::BindOnce(&KioskCryptohomeRemover::OnRemoveAppCryptohomeComplete,
                         weak_ptr_factory_.GetWeakPtr(), cryptohome_id,
                         cryptohomes_barrier_closure));
    }
  }
}

void KioskCryptohomeRemover::PerformDelayedCryptohomeRemovals(
    bool service_is_available) {
  if (!service_is_available) {
    LOG(ERROR) << "Cryptohome client is not avaiable.";
    return;
  }

  const base::DictValue& dict =
      local_state_->GetDict(prefs::kAllKioskUsersToRemove);
  for (const auto it : dict) {
    std::string app_id;
    if (it.second.is_string()) {
      app_id = it.second.GetString();
    }
    VLOG(1) << "Removing obsolete cryptohome for " << app_id;

    const cryptohome::Identification cryptohome_id(
        cryptohome::Identification::FromString(it.first));
    cryptohome::AccountIdentifier account_id_proto;
    account_id_proto.set_account_id(cryptohome_id.id());

    user_data_auth::RemoveRequest request;
    *request.mutable_identifier() = account_id_proto;
    UserDataAuthClient::Get()->Remove(
        request,
        base::BindOnce(&KioskCryptohomeRemover::OnRemoveAppCryptohomeComplete,
                       weak_ptr_factory_.GetWeakPtr(), cryptohome_id,
                       base::OnceClosure()));
  }
}

void KioskCryptohomeRemover::OnRemoveAppCryptohomeComplete(
    const cryptohome::Identification& id,
    base::OnceClosure callback,
    std::optional<user_data_auth::RemoveReply> reply) {
  cryptohome::ErrorWrapper error = ReplyToCryptohomeError(reply);
  if (!cryptohome::HasError(error) ||
      cryptohome::ErrorMatches(
          error, user_data_auth::CRYPTOHOME_ERROR_ACCOUNT_NOT_FOUND)) {
    UnscheduleDelayedCryptohomeRemoval(id);
  }
  if (callback) {
    std::move(callback).Run();
  }
}

void KioskCryptohomeRemover::ScheduleDelayedCryptohomeRemoval(
    const AccountId& account_id) {
  {
    ScopedDictPrefUpdate dict_update(&local_state_.get(),
                                     prefs::kAllKioskUsersToRemove);
    dict_update->Set(cryptohome::Identification(account_id).id(),
                     account_id.GetUserEmail());
  }
  local_state_->CommitPendingWrite();
}

void KioskCryptohomeRemover::UnscheduleDelayedCryptohomeRemoval(
    const cryptohome::Identification& id) {
  {
    ScopedDictPrefUpdate dict_update(&local_state_.get(),
                                     prefs::kAllKioskUsersToRemove);
    dict_update->Remove(id.id());
  }
  local_state_->CommitPendingWrite();
}

}  // namespace ash
