// 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 "remoting/host/register_support_host_request_base.h"

#include "base/logging.h"
#include "base/strings/stringize_macros.h"
#include "remoting/base/environment_details.h"
#include "remoting/base/errors.h"
#include "remoting/base/http_status.h"
#include "remoting/signaling/signaling_address.h"

namespace remoting {

namespace {

ErrorCode MapError(HttpStatus::Code status_code) {
  switch (status_code) {
    case HttpStatus::Code::OK:
      return ErrorCode::OK;
    case HttpStatus::Code::DEADLINE_EXCEEDED:
      return ErrorCode::OPERATION_TIMEOUT;
    case HttpStatus::Code::INVALID_ARGUMENT:
      return ErrorCode::INVALID_ARGUMENT;
    case HttpStatus::Code::PERMISSION_DENIED:
      return ErrorCode::UNAUTHORIZED_ACCOUNT;
    case HttpStatus::Code::UNAUTHENTICATED:
      return ErrorCode::AUTHENTICATION_FAILED;
    case HttpStatus::Code::FAILED_PRECONDITION:
      return ErrorCode::INVALID_STATE;
    case HttpStatus::Code::NETWORK_ERROR:
      return ErrorCode::NETWORK_FAILURE;
    default:
      return ErrorCode::UNKNOWN_ERROR;
  }
}

}  // namespace

RegisterSupportHostRequestBase::RegisterSupportHostRequestBase() = default;

RegisterSupportHostRequestBase::~RegisterSupportHostRequestBase() {
  if (signal_strategy_) {
    signal_strategy_->RemoveListener(this);
  }
}

void RegisterSupportHostRequestBase::StartRequest(
    SignalStrategy* signal_strategy,
    std::unique_ptr<net::ClientCertStore> client_cert_store,
    scoped_refptr<RsaKeyPair> key_pair,
    const std::string& authorized_helper,
    std::optional<ChromeOsEnterpriseParams> params,
    RegisterCallback callback) {
  DCHECK_EQ(State::NOT_STARTED, state_);
  DCHECK(signal_strategy);
  DCHECK(key_pair);
  DCHECK(callback);
  signal_strategy_ = signal_strategy;
  key_pair_ = key_pair;
  callback_ = std::move(callback);
  enterprise_params_ = std::move(params);
  authorized_helper_ = authorized_helper;

  signal_strategy_->AddListener(this);
  Initialize(std::move(client_cert_store));
}

void RegisterSupportHostRequestBase::OnSignalingStateChanged(
    SignalStrategy::State state) {
  switch (state) {
    case SignalStrategy::State::CONNECTED:
      RegisterHostInternal();
      break;
    case SignalStrategy::State::DISCONNECTED:
      RunCallback({}, {}, ErrorCode::SIGNALING_ERROR);
      break;
    default:
      // Do nothing.
      break;
  }
}

void RegisterSupportHostRequestBase::RegisterHostInternal() {
  DCHECK_EQ(SignalStrategy::CONNECTED, signal_strategy_->GetState());
  if (state_ != State::NOT_STARTED) {
    return;
  }
  state_ = State::REGISTERING;
  internal::RemoteSupportHostStruct host;
  host.public_key = key_pair_->GetPublicKey();
  host.version = STRINGIZE(VERSION);
  host.authorized_helper_email = authorized_helper_;
  host.operating_system_info.name = GetOperatingSystemName();
  host.operating_system_info.version = GetOperatingSystemVersion();
  const SignalingAddress& local_address = signal_strategy_->GetLocalAddress();
  if (!local_address.GetFtlInfo(&host.tachyon_account_info.account_id,
                                &host.tachyon_account_info.registration_id)) {
    LOG(ERROR) << "Local signaling ID is not a valid Tachyon ID: "
               << local_address.id();
  }

  RegisterHost(
      host, enterprise_params_,
      base::BindOnce(&RegisterSupportHostRequestBase::OnRegisterHostResult,
                     base::Unretained(this)));
}

void RegisterSupportHostRequestBase::OnRegisterHostResult(
    const HttpStatus& status,
    std::string_view support_id,
    base::TimeDelta support_id_lifetime) {
  if (!status.ok()) {
    state_ = State::NOT_STARTED;
    LOG(ERROR) << "Failed to register support host: " << status.error_message()
               << " (" << static_cast<int>(status.error_code()) << ")";
    RunCallback({}, {}, MapError(status.error_code()));
    return;
  }
  state_ = State::REGISTERED;
  RunCallback(support_id, support_id_lifetime, ErrorCode::OK);
}

void RegisterSupportHostRequestBase::RunCallback(std::string_view support_id,
                                                 base::TimeDelta lifetime,
                                                 ErrorCode error_code) {
  if (!callback_) {
    // Callback has already been run, so just return.
    return;
  }

  // Cleanup state before calling the callback.
  CancelPendingRequests();
  signal_strategy_->RemoveListener(this);
  signal_strategy_ = nullptr;

  // TODO: yuweih - make callback take std::string_view.
  std::move(callback_).Run(std::string(support_id), lifetime, error_code);
}

}  // namespace remoting
