// Copyright 2025 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/user_manager/test_helper.h"

#include <cstddef>
#include <optional>

#include "base/check_deref.h"
#include "base/notreached.h"
#include "chromeos/ash/components/cryptohome/cryptohome_parameters.h"
#include "chromeos/ash/components/dbus/userdataauth/userdataauth_client.h"
#include "chromeos/ash/components/policy/device_local_account/device_local_account_type.h"
#include "components/account_id/account_id.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/user_manager/known_user.h"
#include "components/user_manager/user_manager.h"
#include "components/user_manager/user_manager_impl.h"
#include "components/user_manager/user_manager_pref_names.h"
#include "components/user_manager/user_names.h"

namespace user_manager {
namespace {

void RegisterPersistedUserInternal(PrefService& local_state,
                                   const AccountId& account_id,
                                   UserType user_type) {
  {
    ScopedListPrefUpdate update(&local_state, prefs::kRegularUsersPref);
    update->Append(account_id.GetUserEmail());
  }
  {
    ScopedDictPrefUpdate update(&local_state, prefs::kUserType);
    update->Set(account_id.GetAccountIdKey(), static_cast<int>(user_type));
  }
  {
    KnownUser known_user(&local_state);
    known_user.UpdateId(account_id);
  }
}

bool IsUserIdMatchingKioskType(std::string_view user_id,
                               const UserType& expected_kiosk_type) {
  auto type_by_user_id = policy::GetDeviceLocalAccountType(user_id);
  if (!type_by_user_id.has_value()) {
    return false;
  }

  switch (expected_kiosk_type) {
    case UserType::kRegular:
    case UserType::kChild:
    case UserType::kGuest:
    case UserType::kPublicAccount:
      NOTREACHED() << "Provided UserType is not kiosk: " << expected_kiosk_type;
    case UserType::kKioskChromeApp:
      return policy::DeviceLocalAccountType::kKioskApp == type_by_user_id;
    case UserType::kKioskWebApp:
      return policy::DeviceLocalAccountType::kWebKioskApp == type_by_user_id;
    case UserType::kKioskIWA:
      return policy::DeviceLocalAccountType::kKioskIsolatedWebApp ==
             type_by_user_id;
    case UserType::kKioskArcvmApp:
      return policy::DeviceLocalAccountType::kArcvmKioskApp == type_by_user_id;
  }
}

}  // namespace

// static
void TestHelper::RegisterPersistedUser(PrefService& local_state,
                                       const AccountId& account_id) {
  RegisterPersistedUserInternal(local_state, account_id, UserType::kRegular);
}

// static
void TestHelper::RegisterPersistedChildUser(PrefService& local_state,
                                            const AccountId& account_id) {
  RegisterPersistedUserInternal(local_state, account_id, UserType::kChild);
}

// static
void TestHelper::RegisterKioskAppUser(PrefService& local_state,
                                      std::string_view user_id) {
  auto type = policy::GetDeviceLocalAccountType(user_id);
  CHECK_EQ(type, policy::DeviceLocalAccountType::kKioskApp)
      << user_id << " did not satisfy to be used for a kiosk user. "
      << "See policy::GetDeviceLocalAccountType for details";
  ScopedListPrefUpdate update(&local_state,
                              prefs::kDeviceLocalAccountsWithSavedData);
  update->Append(user_id);
}

// static
void TestHelper::RegisterWebKioskAppUser(PrefService& local_state,
                                         std::string_view user_id) {
  auto type = policy::GetDeviceLocalAccountType(user_id);
  CHECK_EQ(type, policy::DeviceLocalAccountType::kWebKioskApp)
      << user_id << " did not satisfy to be used for a web kiosk user. "
      << "See policy::GetDeviceLocalAccountType for details";
  ScopedListPrefUpdate update(&local_state,
                              prefs::kDeviceLocalAccountsWithSavedData);
  update->Append(user_id);
}

// static
void TestHelper::RegisterPublicAccountUser(PrefService& local_state,
                                           std::string_view user_id) {
  auto type = policy::GetDeviceLocalAccountType(user_id);
  CHECK_EQ(type, policy::DeviceLocalAccountType::kPublicSession)
      << user_id << " did not satisfy to be used for a public account user. "
      << "See policy::GetDeviceLocalAccountType for details";
  ScopedListPrefUpdate update(&local_state,
                              prefs::kDeviceLocalAccountsWithSavedData);
  update->Append(user_id);
}

// static
void TestHelper::RegisterOwner(PrefService& local_state,
                               std::string_view user_email) {
  UserManagerImpl::RecordOwner(local_state, user_email);
}

// static
std::string TestHelper::GetFakeUsernameHash(const AccountId& account_id) {
  CHECK(account_id.is_valid());
  return ash::UserDataAuthClient::GetStubSanitizedUsername(
      cryptohome::CreateAccountIdentifierFromAccountId(account_id));
}

TestHelper::TestHelper(UserManager* user_manager)
    : user_manager_(CHECK_DEREF(user_manager)) {}

TestHelper::~TestHelper() = default;

User* TestHelper::AddRegularUser(const AccountId& account_id) {
  return AddUserInternal(account_id, UserType::kRegular);
}

User* TestHelper::AddChildUser(const AccountId& account_id) {
  return AddUserInternal(account_id, UserType::kChild);
}

User* TestHelper::AddGuestUser() {
  return AddUserInternal(GuestAccountId(), UserType::kGuest);
}

User* TestHelper::AddUserInternal(const AccountId& account_id,
                                  UserType user_type) {
  // In production, only when there's no logged in users, a user
  // can be added to UserManager. Tests should follow the production
  // manner for setting up testing environment.
  if (!user_manager_->GetLoggedInUsers().empty()) {
    LOG(ERROR) << "There already is logged in user(s).";
    return nullptr;
  }
  if (user_manager_->FindUser(account_id)) {
    LOG(ERROR) << "User for " << account_id << " already exists";
    return nullptr;
  }

  if (!user_manager_->EnsureUser(account_id, user_type,
                                 /*is_ephemeral=*/false)) {
    LOG(ERROR) << "Failed to create a user " << user_type << " for "
               << account_id;
    return nullptr;
  }
  return user_manager_->FindUserAndModify(account_id);
}

User* TestHelper::AddKioskChromeAppUser(std::string_view user_id) {
  return AddKioskUser(user_id, UserType::kKioskChromeApp);
}

User* TestHelper::AddKioskWebAppUser(std::string_view user_id) {
  return AddKioskUser(user_id, UserType::kKioskWebApp);
}

User* TestHelper::AddKioskIwaUser(std::string_view user_id) {
  return AddKioskUser(user_id, UserType::kKioskIWA);
}

User* TestHelper::AddPublicAccountUser(std::string_view user_id) {
  // Quick check that the `user_id` satisfies kiosk-app type.
  auto type = policy::GetDeviceLocalAccountType(user_id);
  if (type != policy::DeviceLocalAccountType::kPublicSession) {
    LOG(ERROR)
        << "user_id (" << user_id << ") did not satisfy to be used for "
        << "a public account user. See policy::GetDeviceLocalAccountType "
        << "for details.";
    return nullptr;
  }

  return AddDeviceLocalAccountUserInternal(user_id, UserType::kPublicAccount);
}

User* TestHelper::AddDeviceLocalAccountUserInternal(std::string_view user_id,
                                                    UserType user_type) {
  // Build DeviceLocalAccountInfo for the existing users.
  std::vector<UserManager::DeviceLocalAccountInfo> device_local_accounts;
  for (const auto& user : user_manager_->GetPersistedUsers()) {
    if (user->GetAccountId().GetUserEmail() == user_id) {
      LOG(ERROR) << "duplicated account is found: " << user_id;
      return nullptr;
    }
    if (!user->IsDeviceLocalAccount()) {
      continue;
    }
    UserManager::DeviceLocalAccountInfo info(
        user->GetAccountId().GetUserEmail(), user->GetType());
    if (user->GetType() == UserType::kPublicAccount) {
      info.display_name = user->GetDisplayName();
    }
    device_local_accounts.push_back(info);
  }

  // Add the given `user_id`.
  device_local_accounts.emplace_back(std::string(user_id), user_type);
  user_manager_->UpdateDeviceLocalAccountUser(device_local_accounts);
  return user_manager_->FindUserAndModify(AccountId::FromUserEmail(user_id));
}

User* TestHelper::AddKioskUser(std::string_view user_id, UserType kiosk_type) {
  if (!IsUserIdMatchingKioskType(user_id, kiosk_type)) {
    LOG(ERROR) << "user_id (" << user_id << ") did not satisfy to be used for "
               << "a kiosk with specified type: " << kiosk_type
               << ". See policy::GetDeviceLocalAccountType for details.";
    return nullptr;
  }
  return AddDeviceLocalAccountUserInternal(user_id, kiosk_type);
}

}  // namespace user_manager
