// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "remoting/host/linux/remote_display_session_manager.h"

#include <pwd.h>
#include <unistd.h>

#include <optional>
#include <string_view>
#include <utility>
#include <vector>

#include "base/barrier_callback.h"
#include "base/base_paths.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/json/json_reader.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/path_service.h"
#include "base/process/launch.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/task/thread_pool.h"
#include "base/types/expected.h"
#include "base/version.h"
#include "remoting/base/loggable.h"
#include "remoting/base/logging.h"
#include "remoting/base/passwd_utils.h"
#include "remoting/host/base/switches.h"
#include "remoting/host/linux/gvariant_ref.h"
#include "remoting/host/linux/login_session_manager.h"

namespace remoting {

namespace {

constexpr std::string_view kRemoteDisplayIdPrefix =
    "/com/google/ChromeRemoteDesktop/RemoteDisplays/";

std::string GetRemoteDisplayName(const gvariant::ObjectPath& remote_id) {
  if (!base::StartsWith(remote_id.value(), kRemoteDisplayIdPrefix)) {
    return {};
  }
  return remote_id.value().substr(kRemoteDisplayIdPrefix.size());
}

}  // namespace

// RemoteDisplaySession

RemoteDisplaySessionManager::RemoteDisplaySession::RemoteDisplaySession() =
    default;
RemoteDisplaySessionManager::RemoteDisplaySession::RemoteDisplaySession(
    RemoteDisplaySession&&) = default;
RemoteDisplaySessionManager::RemoteDisplaySession::RemoteDisplaySession(
    const RemoteDisplaySession&) = default;
RemoteDisplaySessionManager::RemoteDisplaySession::~RemoteDisplaySession() =
    default;

RemoteDisplaySessionManager::RemoteDisplaySession&
RemoteDisplaySessionManager::RemoteDisplaySession::operator=(
    RemoteDisplaySession&&) = default;
RemoteDisplaySessionManager::RemoteDisplaySession&
RemoteDisplaySessionManager::RemoteDisplaySession::operator=(
    const RemoteDisplaySession&) = default;

// RemoteDisplayInfo

RemoteDisplaySessionManager::RemoteDisplayInfo::RemoteDisplayInfo() = default;
RemoteDisplaySessionManager::RemoteDisplayInfo::RemoteDisplayInfo(
    RemoteDisplayInfo&&) = default;
RemoteDisplaySessionManager::RemoteDisplayInfo::RemoteDisplayInfo(
    const RemoteDisplayInfo&) = default;
RemoteDisplaySessionManager::RemoteDisplayInfo::~RemoteDisplayInfo() = default;

RemoteDisplaySessionManager::RemoteDisplayInfo&
RemoteDisplaySessionManager::RemoteDisplayInfo::operator=(RemoteDisplayInfo&&) =
    default;
RemoteDisplaySessionManager::RemoteDisplayInfo&
RemoteDisplaySessionManager::RemoteDisplayInfo::operator=(
    const RemoteDisplayInfo&) = default;

// RemoteDisplaySessionManager

RemoteDisplaySessionManager::RemoteDisplaySessionManager() = default;
RemoteDisplaySessionManager::~RemoteDisplaySessionManager() = default;

void RemoteDisplaySessionManager::Start(Delegate* delegate, Callback callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK_EQ(start_state_, StartState::NOT_STARTED);
  DCHECK(delegate);
  DCHECK(callback);

  start_state_ = StartState::STARTING;
  delegate_ = delegate;

  init_callback_ = std::move(callback);
  GDBusConnectionRef::CreateForSystemBus(
      base::BindOnce(&RemoteDisplaySessionManager::OnCreateDbusConnectionResult,
                     weak_ptr_factory_.GetWeakPtr()));
}

void RemoteDisplaySessionManager::CreateRemoteDisplay(
    std::string_view display_name,
    Callback callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK_EQ(start_state_, StartState::STARTED);

  auto result = gvariant::ObjectPath::TryFrom(
      std::string(kRemoteDisplayIdPrefix) + std::string(display_name));
  if (!result.has_value()) {
    std::move(callback).Run(base::unexpected(std::move(result).error()));
    return;
  }
  remote_display_manager_.CreateRemoteDisplay(*result, std::move(callback));
}

void RemoteDisplaySessionManager::TerminateRemoteDisplay(
    std::string_view display_name,
    Callback callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK_EQ(start_state_, StartState::STARTED);

  const auto it = remote_displays_.find(display_name);
  if (it == remote_displays_.end()) {
    std::move(callback).Run(base::unexpected(Loggable(
        FROM_HERE,
        "Remote display " + std::string(display_name) + " not found.")));
    return;
  }

  auto terminate_all_callback =
      base::BarrierCallback<base::expected<void, Loggable>>(
          it->second.sessions.size(),
          base::BindOnce(
              [](const std::string& display_name, Callback callback,
                 std::vector<base::expected<void, Loggable>> results) {
                for (auto& result : results) {
                  if (!result.has_value()) {
                    auto loggable = std::move(result).error();
                    loggable.AddContext(
                        FROM_HERE,
                        std::string(
                            "Failed to terminate a session for display") +
                            display_name);
                    std::move(callback).Run(
                        base::unexpected(std::move(loggable)));
                    return;
                  }
                }
                std::move(callback).Run(base::ok());
              },
              std::string(display_name), std::move(callback)));

  for (const auto& [_, session] : it->second.sessions) {
    TerminateRemoteDisplaySession(session, terminate_all_callback);
  }
}

void RemoteDisplaySessionManager::TerminateRemoteDisplaySession(
    const RemoteDisplaySession& session,
    Callback callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK_EQ(start_state_, StartState::STARTED);

  if (!session.session_info.has_value()) {
    std::move(callback).Run(base::unexpected(
        Loggable(FROM_HERE, "Remote display session has no session info.")));
    return;
  }

  login_session_manager_->TerminateSession(session.session_info->object_path,
                                           std::move(callback));
}

const RemoteDisplaySessionManager::RemoteDisplayInfo*
RemoteDisplaySessionManager::GetRemoteDisplayInfo(
    std::string_view display_name) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  const auto it = remote_displays_.find(display_name);
  if (it == remote_displays_.end()) {
    return nullptr;
  }
  return &it->second;
}

void RemoteDisplaySessionManager::QuerySessionInfo(
    const std::string& display_name,
    const gvariant::ObjectPath& display_path,
    const std::string& session_id) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  login_session_manager_->GetSessionInfo(
      session_id,
      base::BindOnce(&RemoteDisplaySessionManager::OnSessionInfoReady,
                     weak_ptr_factory_.GetWeakPtr(), std::string(display_name),
                     display_path));
}

void RemoteDisplaySessionManager::HandleSessionInfoQueriesBlockingStartup() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (start_state_ == StartState::STARTED) {
    return;
  }

  DCHECK_EQ(start_state_, StartState::STARTING);
  if (session_info_queries_blocking_startup_.empty()) {
    start_state_ = StartState::STARTED;
    std::move(init_callback_).Run(base::ok());
  }
}

void RemoteDisplaySessionManager::OnCreateDbusConnectionResult(
    base::expected<GDBusConnectionRef, Loggable> result) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (!result.has_value()) {
    start_state_ = StartState::NOT_STARTED;
    std::move(init_callback_).Run(base::unexpected(std::move(result).error()));
    return;
  }

  connection_ = std::move(*result);
  login_session_manager_ = std::make_unique<LoginSessionManager>(connection_);
  remote_display_manager_.Init(
      connection_, this,
      base::BindOnce(
          &RemoteDisplaySessionManager::OnGdmRemoteDisplayManagerStarted,
          weak_ptr_factory_.GetWeakPtr()));
}

void RemoteDisplaySessionManager::OnGdmRemoteDisplayManagerStarted(
    base::expected<void, Loggable> result) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (!result.has_value()) {
    start_state_ = StartState::NOT_STARTED;
    std::move(init_callback_).Run(base::unexpected(std::move(result).error()));
    return;
  }

  login_session_server_.StartServer();
  for (const auto& [display_path, remote_display] :
       remote_display_manager_.remote_displays()) {
    std::string display_name = GetRemoteDisplayName(remote_display.remote_id);
    if (display_name.empty()) {
      VLOG(1) << "Ignoring unrelated remote display: "
              << remote_display.remote_id.value();
      continue;
    }
    auto& display_info = remote_displays_[display_name];
    display_info.sessions[display_path] = RemoteDisplaySession();
    if (!remote_display.session_id.empty()) {
      session_info_queries_blocking_startup_.insert(display_path);
      QuerySessionInfo(display_name, display_path, remote_display.session_id);
    }
  }
  HandleSessionInfoQueriesBlockingStartup();
}

void RemoteDisplaySessionManager::OnRemoteDisplayCreated(
    const gvariant::ObjectPath& display_path,
    const GdmRemoteDisplayManager::RemoteDisplay& display) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  std::string display_name = GetRemoteDisplayName(display.remote_id);
  if (display_name.empty()) {
    VLOG(1) << "Ignoring unrelated remote display: "
            << display.remote_id.value();
    return;
  }
  auto& display_info = remote_displays_[display_name];
  display_info.sessions[display_path] = RemoteDisplaySession();
  if (!display.session_id.empty()) {
    HOST_LOG << "Querying session info for remote display: "
             << display.remote_id.value()
             << ", session id: " << display.session_id;
    QuerySessionInfo(display_name, display_path, display.session_id);
  }
}

void RemoteDisplaySessionManager::OnRemoteDisplayRemoved(
    const gvariant::ObjectPath& display_path,
    const gvariant::ObjectPath& remote_id) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  std::string display_name = GetRemoteDisplayName(remote_id);
  if (display_name.empty()) {
    VLOG(1) << "Ignoring unrelated remote display: " << remote_id.value();
    return;
  }
  HOST_LOG << "GDM remote display session removed: " << display_path.value()
           << ", remote id: " << remote_id.value();
  auto it = remote_displays_.find(display_name);
  if (it == remote_displays_.end()) {
    LOG(WARNING) << "Cannot find remote display with name: " << display_name;
    return;
  }
  RemoteDisplayInfo& display_info = it->second;
  display_info.sessions.erase(display_path);
  if (!display_info.sessions.empty()) {
    delegate_->OnRemoteDisplayChanged(display_name, display_info);
    return;
  }
  remote_displays_.erase(it);
  delegate_->OnRemoteDisplayTerminated(display_name);
}

void RemoteDisplaySessionManager::OnRemoteDisplayChanged(
    const gvariant::ObjectPath& display_path,
    const GdmRemoteDisplayManager::RemoteDisplay& display) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  std::string display_name = GetRemoteDisplayName(display.remote_id);
  if (display_name.empty()) {
    VLOG(1) << "Ignoring unrelated remote display: "
            << display.remote_id.value();
    return;
  }
  HOST_LOG << "Remote display session changed: " << display.remote_id.value()
           << ", session id: " << display.session_id
           << ", display path: " << display_path.value();
  auto display_it = remote_displays_.find(display_name);
  if (display_it == remote_displays_.end()) {
    LOG(WARNING) << "Cannot find remote display with name: " << display_name;
    return;
  }
  RemoteDisplayInfo& display_info = display_it->second;
  // Find `display_path`. In case it is not found, create the session.
  auto [session_it, inserted] = display_info.sessions.try_emplace(display_path);
  if (inserted) {
    LOG(WARNING) << "Cannot find remote display session with display path: "
                 << display_path.value();
  }
  RemoteDisplaySession& session = session_it->second;
  session.session_info = std::nullopt;
  session.user_info = std::nullopt;
  if (display.session_id.empty()) {
    delegate_->OnRemoteDisplayChanged(display_name, display_info);
    return;
  }

  QuerySessionInfo(display_name, display_path, display.session_id);
}

void RemoteDisplaySessionManager::IsRunningInCrdSession(
    const std::string& session_id,
    LoginSessionServer::Delegate::IsRunningInCrdSessionCallback callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  IsRunningInCrdSessionInternal(session_id, std::move(callback),
                                /*can_wait=*/true);
}

void RemoteDisplaySessionManager::IsRunningInCrdSessionInternal(
    const std::string& session_id,
    LoginSessionServer::Delegate::IsRunningInCrdSessionCallback callback,
    bool can_wait) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (IsRunningInCrdSessionSynchronous(session_id)) {
    std::move(callback).Run(true);
    return;
  }

  if (!can_wait) {
    std::move(callback).Run(false);
    return;
  }

  // See if there is any active greeter session. If we see an unrecognized
  // session while there is an active greeter session, then it is possible that
  // the new session is transitioned from the greeter session during user login
  // but we haven't observed it yet.
  // TODO: yuweih - See if it is possible to "tag" a login session as
  // CRD-managed so that we don't need this somewhat fragile logic, but this may
  // require some new API from GDM.
  bool has_greeter_session = false;
  for (auto& [_, d_info] : remote_displays_) {
    for (auto& [_, s] : d_info.sessions) {
      if (s.session_info.has_value() &&
          s.session_info->session_class == "greeter") {
        has_greeter_session = true;
        break;
      }
    }
    if (has_greeter_session) {
      break;
    }
  }

  if (!has_greeter_session) {
    std::move(callback).Run(false);
    return;
  }

  // Query the session info to check if it's a remote user session.
  login_session_manager_->GetSessionInfo(
      session_id,
      base::BindOnce(&RemoteDisplaySessionManager::
                         OnIsRunningInCrdSessionGetSessionInfoResult,
                     weak_ptr_factory_.GetWeakPtr(), session_id,
                     std::move(callback)));
}

void RemoteDisplaySessionManager::OnIsRunningInCrdSessionGetSessionInfoResult(
    const std::string& session_id,
    LoginSessionServer::Delegate::IsRunningInCrdSessionCallback callback,
    base::expected<LoginSessionManager::SessionInfo, Loggable> result) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (!result.has_value()) {
    LOG(ERROR) << result.error();
    std::move(callback).Run(false);
    return;
  }

  // Not a remote user session, so it's definitely not managed by CRD.
  if (!result->is_remote || result->session_class != "user") {
    std::move(callback).Run(false);
    return;
  }

  // Try again in 500ms without waiting again.
  base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
      FROM_HERE,
      base::BindOnce(
          &RemoteDisplaySessionManager::IsRunningInCrdSessionInternal,
          weak_ptr_factory_.GetWeakPtr(), session_id, std::move(callback),
          /*can_wait=*/false),
      base::Milliseconds(500));
}

bool RemoteDisplaySessionManager::IsRunningInCrdSessionSynchronous(
    const std::string& session_id) const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  for (auto& [_, d_info] : remote_displays_) {
    for (auto& [_, s] : d_info.sessions) {
      if (s.session_info.has_value() &&
          s.session_info->session_id == session_id) {
        return true;
      }
    }
  }
  return false;
}

void RemoteDisplaySessionManager::OnSessionInfoReady(
    const std::string& display_name,
    const gvariant::ObjectPath& display_path,
    base::expected<LoginSessionManager::SessionInfo, Loggable> result) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (!result.has_value()) {
    LOG(ERROR) << "Failed to get session info for " << display_name << ": "
               << result.error();
    session_info_queries_blocking_startup_.erase(display_path);
    HandleSessionInfoQueriesBlockingStartup();
    return;
  }
  DCHECK(result->is_remote);

  auto remote_display_it = remote_displays_.find(display_name);
  DCHECK(remote_display_it != remote_displays_.end());
  auto& remote_display_info = remote_display_it->second;
  auto& session = remote_display_info.sessions[display_path];
  session.session_info = std::move(*result);
  auto user_info_expected = GetPasswdUserInfo(session.session_info->username);
  if (user_info_expected.has_value()) {
    session.user_info = std::move(user_info_expected.value());
  } else {
    LOG(ERROR) << user_info_expected.error();
  }

  if (start_state_ == StartState::STARTED) {
    delegate_->OnRemoteDisplayChanged(display_name, remote_display_info);
  }

  session_info_queries_blocking_startup_.erase(display_path);
  HandleSessionInfoQueriesBlockingStartup();
}

}  // namespace remoting
