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

#include "content/browser/digital_credentials/cross_device_transaction_impl.h"

#include <optional>
#include <variant>

#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/task/single_thread_task_runner.h"
#include "build/build_config.h"
#include "components/device_event_log/device_event_log.h"
#include "content/browser/digital_credentials/cross_device_request_dispatcher.h"
#include "content/public/browser/digital_credentials_cross_device.h"
#include "device/bluetooth/bluetooth_adapter.h"
#include "device/bluetooth/bluetooth_adapter_factory.h"
#include "device/fido/ble_adapter_manager.h"
#include "device/fido/fido_discovery_base.h"
#include "device/fido/public/fido_constants.h"
#include "third_party/abseil-cpp/absl/functional/overload.h"

#if BUILDFLAG(IS_MAC)
#include "base/process/process_info.h"
#endif

namespace content::digital_credentials::cross_device {

namespace {

std::optional<Error> CheckConfiguration() {
#if BUILDFLAG(IS_MAC)
  if (!device::BluetoothAdapterFactory::HasSharedInstanceForTesting() &&
      !base::DoesResponsibleProcessHaveBluetoothMetadata()) {
    FIDO_LOG(EVENT)
        << "Cannot use Bluetooth because the responsible app for the process "
           "does not have Bluetooth metadata in its Info.plist. Launch from "
           "Finder or with `open` instead.";
    return SystemError::kNotSelfResponsible;
  }
#endif

  if (!device::BluetoothAdapterFactory::Get()->IsLowEnergySupported()) {
    FIDO_LOG(EVENT) << "No BLE support.";
    return SystemError::kNoBleSupport;
  }

  return std::nullopt;
}

}  // namespace

Transaction::~Transaction() = default;

std::unique_ptr<Transaction> Transaction::New(
    RequestInfo request_info,
    std::array<uint8_t, device::cablev2::kQRKeySize> qr_generator_key,
    device::NetworkContextFactory network_context_factory,
    EventCallback event_callback,
    CompletionCallback callback) {
  return std::make_unique<TransactionImpl>(
      std::move(request_info), qr_generator_key,
      std::move(network_context_factory), std::move(event_callback),
      std::move(callback));
}

TransactionImpl::TransactionImpl(
    RequestInfo request_info,
    std::array<uint8_t, device::cablev2::kQRKeySize> qr_generator_key,
    device::NetworkContextFactory network_context_factory,
    Transaction::EventCallback event_callback,
    Transaction::CompletionCallback callback)
    : request_info_(std::move(request_info)),
      event_callback_(std::move(event_callback)),
      callback_(std::move(callback)) {
  std::optional<Error> error = CheckConfiguration();
  if (error) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE,
        base::BindOnce(std::move(callback_), base::unexpected(*error)));
    return;
  }

  auto v2_discovery = std::make_unique<device::cablev2::Discovery>(
      // This request type argument is unused. It only applies to the payload
      // sent for state-assisted transactions, but those aren't supported for
      // digital credentials.
      device::FidoRequestType::kGetAssertion, network_context_factory,
      qr_generator_key,
      /*contact_device_stream=*/nullptr,
      /*pairing_callback=*/std::nullopt,
      /*invalidated_pairing_callback=*/std::nullopt,
      base::BindRepeating(&TransactionImpl::OnCableEvent,
                          weak_ptr_factory_.GetWeakPtr()),
      /*must_support_ctap=*/false);
  dispatcher_ = std::make_unique<RequestDispatcher>(
      std::move(v2_discovery), std::move(request_info_),
      base::BindOnce(&TransactionImpl::OnHaveResponse,
                     weak_ptr_factory_.GetWeakPtr()));

  device::BluetoothAdapterFactory::Get()->GetAdapter(base::BindOnce(
      &TransactionImpl::OnHaveAdapter, weak_ptr_factory_.GetWeakPtr()));
}

TransactionImpl::~TransactionImpl() {
  if (adapter_) {
    adapter_->RemoveObserver(this);
  }
}

void TransactionImpl::PowerBluetoothAdapter() {
  CHECK(callback_);
  CHECK(waiting_for_power_);
  FIDO_LOG(EVENT) << "Powering BLE adapter";
  adapter_->SetPowered(true, base::DoNothing(), base::DoNothing());
}

void TransactionImpl::AdapterPoweredChanged(device::BluetoothAdapter* adapter,
                                            bool powered) {
  CHECK(adapter_);

  if (!callback_) {
    return;
  }
  if (!powered && !waiting_for_power_) {
    FIDO_LOG(EVENT) << "Lost BLE power during digital identity transaction.";
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback_),
                                  base::unexpected(SystemError::kLostPower)));
    return;
  }
  if (powered && waiting_for_power_) {
    FIDO_LOG(EVENT) << "BLE adapter was powered.";
    waiting_for_power_ = false;
  }

  MaybeSignalReady();
}

void TransactionImpl::OnCableEvent(device::cablev2::Event event) {
  if (!callback_) {
    return;
  }
  event_callback_.Run(event);
}

void TransactionImpl::OnHaveAdapter(
    scoped_refptr<device::BluetoothAdapter> adapter) {
  CHECK(!adapter_);
  adapter_ = adapter;
  adapter_->AddObserver(this);

  if (!adapter_->IsPresent()) {
    FIDO_LOG(EVENT) << "No BLE adapter.";
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE,
        base::BindOnce(std::move(callback_),
                       base::unexpected(SystemError::kNoBleSupport)));
    return;
  }

  switch (adapter_->GetOsPermissionStatus()) {
    case device::BluetoothAdapter::PermissionStatus::kUndetermined:
      FIDO_LOG(EVENT) << "Need BLE permission.";
      waiting_for_permission_ = true;
      event_callback_.Run(SystemEvent::kNeedPermission);
      adapter_->RequestSystemPermission(
          base::BindOnce(&TransactionImpl::OnHaveBluetoothPermission,
                         weak_ptr_factory_.GetWeakPtr()));
      return;
    case device::BluetoothAdapter::PermissionStatus::kDenied:
      FIDO_LOG(EVENT) << "BLE permission denied.";
      base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
          FROM_HERE,
          base::BindOnce(std::move(callback_),
                         base::unexpected(SystemError::kPermissionDenied)));
      return;
    case device::BluetoothAdapter::PermissionStatus::kAllowed:
      break;
  }

  ConsiderPowerState();
}

void TransactionImpl::ConsiderPowerState() {
  if (!adapter_->IsPowered()) {
    FIDO_LOG(EVENT) << "BLE adapter not powered.";
    waiting_for_power_ = true;
    event_callback_.Run(SystemEvent::kBluetoothNotPowered);
    return;
  }

  MaybeSignalReady();
}

void TransactionImpl::OnHaveBluetoothPermission(
    device::BluetoothAdapter::PermissionStatus status) {
  if (status == device::BluetoothAdapter::PermissionStatus::kDenied) {
    FIDO_LOG(EVENT) << "BLE permission denied.";
    if (callback_) {
      base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
          FROM_HERE,
          base::BindOnce(std::move(callback_),
                         base::unexpected(SystemError::kPermissionDenied)));
    }
    return;
  }

  waiting_for_permission_ = false;
  ConsiderPowerState();
}

void TransactionImpl::MaybeSignalReady() {
  if (!running_signaled_ && !waiting_for_permission_ && !waiting_for_power_) {
    FIDO_LOG(EVENT) << "Digital identity request ready.";
    running_signaled_ = true;
    event_callback_.Run(SystemEvent::kReady);
  }
}

void TransactionImpl::OnHaveResponse(
    base::expected<Response, RequestDispatcher::Error> response) {
  if (!callback_) {
    return;
  }

  if (response.has_value()) {
    FIDO_LOG(EVENT) << "Have response from digital identity request.";
    std::move(callback_).Run(std::move(response).value());
  } else {
    std::visit(absl::Overload{
                   [this](ProtocolError error) {
                     FIDO_LOG(EVENT)
                         << "Protocol error from digital identity request: "
                         << static_cast<int>(error);
                     std::move(callback_).Run(base::unexpected(error));
                   },
                   [this](RemoteError error) {
                     FIDO_LOG(EVENT)
                         << "Remote error from digital identity request: "
                         << static_cast<int>(error);
                     std::move(callback_).Run(base::unexpected(error));
                   },
               },
               response.error());
  }
}

}  // namespace content::digital_credentials::cross_device
