// Copyright 2014 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/session_manager/core/session_manager.h"

#include <algorithm>

#include "base/check.h"
#include "base/check_deref.h"
#include "base/logging.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
#include "components/session_manager/core/session.h"
#include "components/session_manager/core/session_manager_delegate.h"
#include "components/session_manager/core/session_manager_observer.h"
#include "components/user_manager/user_manager.h"

namespace session_manager {

// static
SessionManager* SessionManager::instance = nullptr;

SessionManager::SessionManager(
    std::unique_ptr<session_manager::SessionManagerDelegate> delegate)
    : delegate_(std::move(delegate)) {
  CHECK(delegate_);
  DCHECK(!SessionManager::Get());
  SessionManager::SetInstance(this);
}

SessionManager::~SessionManager() {
  DCHECK_EQ(instance, this);
  SessionManager::SetInstance(nullptr);
}

// static
SessionManager* SessionManager::Get() {
  return SessionManager::instance;
}

void SessionManager::SetSessionState(SessionState state) {
  if (session_state_ == state)
    return;

  VLOG(1) << "Changing session state to: " << static_cast<int>(state);

  session_state_ = state;
  for (auto& observer : observers_)
    observer.OnSessionStateChanged();
}

void SessionManager::CreateSession(const AccountId& user_account_id,
                                   const std::string& username_hash,
                                   bool new_user,
                                   bool has_active_session) {
  // For secondary user log-in in common cases, we switch the active
  // session after user Profile is created, so data needed for UI update,
  // such as wallpaper, can be ready on activation.
  // We do not switch if this is for recovering multi-sign-in user sessions
  // because in the case we do not switch every user on starting, but
  // only for the last active user after all sessions are created.
  if (!has_active_session && !sessions_.empty()) {
    pending_active_account_id_ = user_account_id;
  }
  CreateSessionInternal(user_account_id, username_hash, new_user);
}

void SessionManager::SwitchActiveSession(const AccountId& account_id) {
  CHECK(user_manager_);
  CHECK(HasSessionForAccountId(account_id));
  user_manager_->SwitchActiveUser(account_id);
}

void SessionManager::RequestSignOut() {
  delegate_->RequestSignOut();
  observers_.Notify(&SessionManagerObserver::OnSignOutRequested);
}

void SessionManager::RequestRestart() {
  delegate_->RequestRestart();
}

void SessionManager::OnUserManagerCreated(
    user_manager::UserManager* user_manager) {
  user_manager_ = user_manager;
  user_manager_observation_.Observe(user_manager_);
}

bool SessionManager::IsSessionStarted() const {
  return session_started_;
}

bool SessionManager::IsUserSessionStartUpTaskCompleted() const {
  return user_session_start_up_task_completed_;
}

void SessionManager::SessionStarted() {
  TRACE_EVENT0("login", "SessionManager::SessionStarted");
  session_started_ = true;

  bool is_primary = sessions_.size() == 1;
  for (auto& observer : observers_)
    observer.OnUserSessionStarted(is_primary);

  SetSessionState(session_manager::SessionState::ACTIVE);

  // Notifies UserManager so that it can update login state.
  user_manager_->OnSessionStarted();
}

bool SessionManager::HasSessionForAccountId(const AccountId& account_id) const {
  return FindSession(account_id) != nullptr;
}

const Session* SessionManager::FindSession(const AccountId& account_id) const {
  auto it = std::ranges::find(sessions_, account_id, [](const auto& session) {
    return session->account_id();
  });
  return it == sessions_.end() ? nullptr : it->get();
}

const Session* SessionManager::GetActiveSession() const {
  CHECK(user_manager_);
  const auto* active_user = user_manager_->GetActiveUser();
  if (!active_user) {
    return nullptr;
  }
  return FindSession(active_user->GetAccountId());
}

const Session* SessionManager::GetPrimarySession() const {
  CHECK(user_manager_);
  const auto* primary_user = user_manager_->GetPrimaryUser();
  CHECK_EQ(!!primary_user, !sessions_.empty());
  if (sessions_.empty()) {
    return nullptr;
  }
  const Session* primary_session = sessions_[0].get();
  CHECK_EQ(primary_user->GetAccountId(), primary_session->account_id());
  return primary_session;
}

bool SessionManager::IsInSecondaryLoginScreen() const {
  return session_state_ == SessionState::LOGIN_SECONDARY;
}

bool SessionManager::IsScreenLocked() const {
  return session_state_ == SessionState::LOCKED;
}

bool SessionManager::IsUserSessionBlocked() const {
  return session_state_ != SessionState::ACTIVE;
}

void SessionManager::AddObserver(SessionManagerObserver* observer) {
  observers_.AddObserver(observer);
}

void SessionManager::RemoveObserver(SessionManagerObserver* observer) {
  observers_.RemoveObserver(observer);
}

void SessionManager::NotifyUserProfileLoaded(const AccountId& account_id) {
  for (auto& observer : observers_)
    observer.OnUserProfileLoaded(account_id);
}

void SessionManager::NotifyLoginOrLockScreenVisible() {
  login_or_lock_screen_shown_for_test_ = true;
  for (auto& observer : observers_)
    observer.OnLoginOrLockScreenVisible();
}

void SessionManager::NotifyUnlockAttempt(const bool success,
                                         const UnlockType unlock_type) {
  for (auto& observer : observers_)
    observer.OnUnlockScreenAttempt(success, unlock_type);
}

void SessionManager::HandleUserSessionStartUpTaskCompleted() {
  // This method must not be called twice.
  CHECK(!user_session_start_up_task_completed_);
  user_session_start_up_task_completed_ = true;
  for (auto& observer : observers_) {
    observer.OnUserSessionStartUpTaskCompleted();
  }
}

// Note: there're *two* types of timing that are considered "profile created".
// 1) ProfileManager has responsibility to create a Profile
// then it invokes ProfileManagerObserver::OnProfileAdded().
// UserManager::OnUserProfileCreated() is called at the timing.
// 2) After finishing Profile creation, there are several more tasks to
// run for ash-chrome's Profile initialization. After all the tasks
// UserSessionManager (indirectly) calls NotifyUserProfileLoaded(). This
// practically happens after 1).
// The gap of the timing looks source of the confusion. We probably want to
// revisit here to unify the timing of "profile creation completion".
void SessionManager::OnUserProfileCreated(const user_manager::User& user) {
  if (pending_active_account_id_.is_valid()) {
    user_manager_->SwitchActiveUser(
        std::exchange(pending_active_account_id_, EmptyAccountId()));
  }
}

// static
void SessionManager::SetInstance(SessionManager* session_manager) {
  SessionManager::instance = session_manager;
}

void SessionManager::CreateSessionInternal(const AccountId& user_account_id,
                                           const std::string& username_hash,
                                           bool new_user) {
  CHECK(user_manager_);
  DCHECK(!HasSessionForAccountId(user_account_id));

  const auto& user = CHECK_DEREF(user_manager_->FindUser(user_account_id));

  // TODO(crbug.com/278643115): This attribute looks like the one for Session
  // rather than UserManager. Move the field.
  // Note: For KioskApp user, this may be updated later in UserSessionManager.
  user_manager_->SetIsCurrentUserNew(
      (new_user && user.HasGaiaAccount()) ||
      user.GetType() == user_manager::UserType::kPublicAccount);

  observers_.Notify(&SessionManagerObserver::OnSessionCreationStarted,
                    user_account_id);
  sessions_.push_back(std::make_unique<Session>(next_id_++, user_account_id));
  user_manager_->UserLoggedIn(user_account_id, username_hash);
  // The created sessions and logged-in users in UserManager should be the
  // same list.
  const auto& logged_in_users = user_manager_->GetLoggedInUsers();
  CHECK_EQ(sessions_.size(), logged_in_users.size());
  for (size_t i = 0; i < sessions_.size(); ++i) {
    CHECK_EQ(sessions_[i]->account_id(), logged_in_users[i]->GetAccountId());
  }
  observers_.Notify(&SessionManagerObserver::OnSessionCreated, user_account_id);
}

}  // namespace session_manager
