/*
 * Copyright 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#define LOG_TAG "bt_bta_sd"

#include "bta/dm/bta_dm_device_search.h"

#include <base/functional/bind.h>
#include <base/strings/stringprintf.h>
#include <bluetooth/log.h>
#include <com_android_bluetooth_flags.h>
#include <stddef.h>

#include <cstdint>
#include <string>
#include <variant>
#include <vector>

#include "bta/dm/bta_dm_device_search_int.h"
#include "common/circular_buffer.h"
#include "common/strings.h"
#include "device/include/interop.h"
#include "main/shim/dumpsys.h"
#include "os/logging/log_adapter.h"
#include "stack/btm/neighbor_inquiry.h"
#include "stack/include/bt_dev_class.h"
#include "stack/include/bt_name.h"
#include "stack/include/btm_ble_api.h"
#include "stack/include/btm_client_interface.h"
#include "stack/include/btm_inq.h"
#include "stack/include/btm_log_history.h"
#include "stack/include/btm_status.h"
#include "stack/include/main_thread.h"
#include "stack/include/rnr_interface.h"
#include "types/raw_address.h"

// TODO(b/369381361) Enfore -Wmissing-prototypes
#pragma GCC diagnostic ignored "-Wmissing-prototypes"

using namespace bluetooth;

namespace {
constexpr char kBtmLogTag[] = "DEV_SEARCH";

tBTA_DM_SEARCH_CB bta_dm_search_cb;
}  // namespace

static void bta_dm_inq_results_cb(tBTM_INQ_RESULTS* p_inq, const uint8_t* p_eir, uint16_t eir_len);
static void bta_dm_inq_cmpl();
static void bta_dm_inq_cmpl_cb(void* p_result);
static void bta_dm_search_cmpl();
static void bta_dm_discover_next_device(void);
static void bta_dm_remname_cback(const tBTM_REMOTE_DEV_NAME* p);

static bool bta_dm_read_remote_device_name(const RawAddress& bd_addr, tBT_TRANSPORT transport);
static void bta_dm_discover_name(const RawAddress& remote_bd_addr);
static void bta_dm_execute_queued_search_request();
static void bta_dm_search_cancel_notify();

static void bta_dm_search_sm_execute(tBTA_DM_DEV_SEARCH_EVT event,
                                     std::unique_ptr<tBTA_DM_SEARCH_MSG> msg);
static void bta_dm_observe_results_cb(tBTM_INQ_RESULTS* p_inq, const uint8_t* p_eir,
                                      uint16_t eir_len);
static void bta_dm_observe_cmpl_cb(void* p_result);

static void bta_dm_search_set_state(tBTA_DM_DEVICE_SEARCH_STATE state) {
  bta_dm_search_cb.search_state = state;
}
static tBTA_DM_DEVICE_SEARCH_STATE bta_dm_search_get_state() {
  return bta_dm_search_cb.search_state;
}

static void post_search_evt(tBTA_DM_DEV_SEARCH_EVT event, std::unique_ptr<tBTA_DM_SEARCH_MSG> msg) {
  if (do_in_main_thread(base::BindOnce(&bta_dm_search_sm_execute, event, std::move(msg))) !=
      BT_STATUS_SUCCESS) {
    log::error("post_search_evt failed");
  }
}

/*******************************************************************************
 *
 * Function         bta_dm_search_start
 *
 * Description      Starts an inquiry
 *
 *
 * Returns          void
 *
 ******************************************************************************/
static void bta_dm_search_start(tBTA_DM_API_SEARCH& search) {
  if (get_btm_client_interface().db.BTM_ClearInqDb(nullptr) != tBTM_STATUS::BTM_SUCCESS) {
    log::warn("Unable to clear inquiry database for device discovery");
  }
  /* save search params */
  bta_dm_search_cb.p_device_search_cback = search.p_cback;

  const tBTM_STATUS btm_status = BTM_StartInquiry(bta_dm_inq_results_cb, bta_dm_inq_cmpl_cb);
  switch (btm_status) {
    case tBTM_STATUS::BTM_CMD_STARTED:
      // Completion callback will be executed when controller inquiry
      // timer pops or is cancelled by the user
      break;
    default:
      log::warn("Unable to start device discovery search btm_status:{}",
                btm_status_text(btm_status));
      // Not started so completion callback is executed now
      bta_dm_inq_cmpl();
      break;
  }
}

/*******************************************************************************
 *
 * Function         bta_dm_search_cancel
 *
 * Description      Cancels an ongoing search for devices
 *
 *
 * Returns          void
 *
 ******************************************************************************/
static void bta_dm_search_cancel() {
  if (BTM_IsInquiryActive()) {
    BTM_CancelInquiry();
    bta_dm_search_cancel_notify();
    bta_dm_search_cmpl();
  } else if (!bta_dm_search_cb.name_discover_done) {
    /* If no Service Search going on then issue cancel remote name in case it is active */
    if (get_stack_rnr_interface().BTM_CancelRemoteDeviceName() != tBTM_STATUS::BTM_CMD_STARTED) {
      log::warn("Unable to cancel RNR");
    }
    /* bta_dm_search_cmpl is called when receiving the remote name cancel evt */
    if (!com::android::bluetooth::flags::
                bta_dm_defer_device_discovery_state_change_until_rnr_complete()) {
      bta_dm_search_cmpl();
    }
  } else {
    bta_dm_inq_cmpl();
  }
}

/*******************************************************************************
 *
 * Function         bta_dm_inq_cmpl_cb
 *
 * Description      Inquiry complete callback from BTM
 *
 * Returns          void
 *
 ******************************************************************************/
static void bta_dm_inq_cmpl_cb(void* /* p_result */) {
  log::verbose("");

  bta_dm_inq_cmpl();
}

/*******************************************************************************
 *
 * Function         bta_dm_inq_results_cb
 *
 * Description      Inquiry results callback from BTM
 *
 * Returns          void
 *
 ******************************************************************************/
static void bta_dm_inq_results_cb(tBTM_INQ_RESULTS* p_inq, const uint8_t* p_eir, uint16_t eir_len) {
  tBTA_DM_SEARCH result;
  tBTM_INQ_INFO* p_inq_info;
  uint16_t service_class;

  result.inq_res.bd_addr = p_inq->remote_bd_addr;

  // Pass the original address to GattService#onScanResult
  result.inq_res.original_bda = p_inq->original_bda;

  result.inq_res.dev_class = p_inq->dev_class;
  BTM_COD_SERVICE_CLASS(service_class, p_inq->dev_class);
  result.inq_res.is_limited = (service_class & BTM_COD_SERVICE_LMTD_DISCOVER) ? true : false;
  result.inq_res.rssi = p_inq->rssi;

  result.inq_res.ble_addr_type = p_inq->ble_addr_type;
  result.inq_res.inq_result_type = p_inq->inq_result_type;
  result.inq_res.device_type = p_inq->device_type;
  result.inq_res.flag = p_inq->flag;
  result.inq_res.include_rsi = p_inq->include_rsi;
  result.inq_res.clock_offset = p_inq->clock_offset;

  /* application will parse EIR to find out remote device name */
  result.inq_res.p_eir = const_cast<uint8_t*>(p_eir);
  result.inq_res.eir_len = eir_len;

  result.inq_res.ble_evt_type = p_inq->ble_evt_type;

  p_inq_info = get_btm_client_interface().db.BTM_InqDbRead(p_inq->remote_bd_addr);
  if (p_inq_info != NULL) {
    /* initialize remt_name_not_required to false so that we get the name by
     * default */
    result.inq_res.remt_name_not_required = false;
  }

  if (bta_dm_search_cb.p_device_search_cback) {
    bta_dm_search_cb.p_device_search_cback(BTA_DM_INQ_RES_EVT, &result);
  }

  if (p_inq_info) {
    /* application indicates if it knows the remote name, inside the callback
     copy that to the inquiry data base*/
    if (result.inq_res.remt_name_not_required) {
      p_inq_info->appl_knows_rem_name = true;
    }
  }
}

/*******************************************************************************
 *
 * Function         bta_dm_remname_cback
 *
 * Description      Remote name complete call back from BTM
 *
 * Returns          void
 *
 ******************************************************************************/
static void bta_dm_remname_cback(const tBTM_REMOTE_DEV_NAME* p_remote_name) {
  log::assert_that(p_remote_name != nullptr, "assert failed: p_remote_name != nullptr");

  log::info(
          "Remote name request complete peer:{} btm_status:{} hci_status:{} "
          "name[0]:{:c} length:{}",
          p_remote_name->bd_addr, btm_status_text(p_remote_name->btm_status),
          hci_error_code_text(p_remote_name->hci_status), p_remote_name->remote_bd_name[0],
          strnlen((const char*)p_remote_name->remote_bd_name, BD_NAME_LEN));

  if (bta_dm_search_cb.peer_bdaddr != p_remote_name->bd_addr) {
    // if we got a different response, maybe ignore it
    // we will have made a request directly from BTM_ReadRemoteDeviceName so we
    // expect a dedicated response for us
    if (p_remote_name->hci_status == HCI_ERR_CONNECTION_EXISTS) {
      log::info("Assume command failed due to disconnection hci_status:{} peer:{}",
                hci_error_code_text(p_remote_name->hci_status), p_remote_name->bd_addr);
    } else {
      log::info("Ignored remote name response for the wrong address exp:{} act:{}",
                bta_dm_search_cb.peer_bdaddr, p_remote_name->bd_addr);
      return;
    }
  }

  /* remote name discovery is done but it could be failed */
  bta_dm_search_cb.name_discover_done = true;
  bd_name_copy(bta_dm_search_cb.peer_name, p_remote_name->remote_bd_name);

  auto msg = std::make_unique<tBTA_DM_SEARCH_MSG>(tBTA_DM_REMOTE_NAME{});
  auto& rmt_name_msg = std::get<tBTA_DM_REMOTE_NAME>(*msg);
  rmt_name_msg.bd_addr = bta_dm_search_cb.peer_bdaddr;
  rmt_name_msg.hci_status = p_remote_name->hci_status;
  bd_name_copy(rmt_name_msg.bd_name, p_remote_name->remote_bd_name);

  post_search_evt(BTA_DM_REMT_NAME_EVT, std::move(msg));
}

/*******************************************************************************
 *
 * Function         bta_dm_read_remote_device_name
 *
 * Description      Initiate to get remote device name
 *
 * Returns          true if started to get remote name
 *
 ******************************************************************************/
static bool bta_dm_read_remote_device_name(const RawAddress& bd_addr, tBT_TRANSPORT transport) {
  tBTM_STATUS btm_status;

  log::verbose("");

  bta_dm_search_cb.peer_bdaddr = bd_addr;
  bta_dm_search_cb.peer_name[0] = 0;

  btm_status = get_stack_rnr_interface().BTM_ReadRemoteDeviceName(bta_dm_search_cb.peer_bdaddr,
                                                                  bta_dm_remname_cback, transport);

  if (btm_status == tBTM_STATUS::BTM_CMD_STARTED) {
    log::verbose("BTM_ReadRemoteDeviceName is started");

    return true;
  } else if (btm_status == tBTM_STATUS::BTM_BUSY) {
    log::verbose("BTM_ReadRemoteDeviceName is busy");

    return true;
  } else {
    log::warn("BTM_ReadRemoteDeviceName returns 0x{:02X}", btm_status);

    return false;
  }
}

/*******************************************************************************
 *
 * Function         bta_dm_inq_cmpl
 *
 * Description      Process the inquiry complete event from BTM
 *
 * Returns          void
 *
 ******************************************************************************/
static void bta_dm_inq_cmpl() {
  if (bta_dm_search_get_state() == BTA_DM_SEARCH_CANCELLING) {
    bta_dm_search_set_state(BTA_DM_SEARCH_IDLE);
    bta_dm_execute_queued_search_request();
    return;
  }

  if (bta_dm_search_get_state() != BTA_DM_SEARCH_ACTIVE) {
    return;
  }

  log::verbose("bta_dm_inq_cmpl");

  bta_dm_search_cb.p_btm_inq_info = get_btm_client_interface().db.BTM_InqDbFirst();
  if (bta_dm_search_cb.p_btm_inq_info != NULL) {
    /* start name discovery from the first device on inquiry result
     */
    bta_dm_search_cb.name_discover_done = false;
    bta_dm_search_cb.peer_name[0] = 0;
    bta_dm_discover_name(bta_dm_search_cb.p_btm_inq_info->results.remote_bd_addr);
  } else {
    bta_dm_search_cmpl();
  }
}

static void bta_dm_remote_name_cmpl(const tBTA_DM_REMOTE_NAME& remote_name_msg) {
  BTM_LogHistory(
          kBtmLogTag, remote_name_msg.bd_addr, "Remote name completed",
          base::StringPrintf("status:%s state:%s name:\"%s\"",
                             hci_status_code_text(remote_name_msg.hci_status).c_str(),
                             bta_dm_state_text(bta_dm_search_get_state()).c_str(),
                             PRIVATE_NAME(reinterpret_cast<char const*>(remote_name_msg.bd_name))));

  tBTM_INQ_INFO* p_btm_inq_info =
          get_btm_client_interface().db.BTM_InqDbRead(remote_name_msg.bd_addr);
  if (!bd_name_is_empty(remote_name_msg.bd_name) && p_btm_inq_info) {
    p_btm_inq_info->appl_knows_rem_name = true;
  }

  // Callback with this property
  if (bta_dm_search_cb.p_device_search_cback != nullptr) {
    tBTA_DM_SEARCH search_data = {
            .name_res = {.bd_addr = remote_name_msg.bd_addr, .bd_name = {}},
    };
    if (remote_name_msg.hci_status == HCI_SUCCESS) {
      bd_name_copy(search_data.name_res.bd_name, remote_name_msg.bd_name);
    }
    bta_dm_search_cb.p_device_search_cback(BTA_DM_NAME_READ_EVT, &search_data);
  } else {
    log::warn("Received remote name complete without callback");
  }

  switch (bta_dm_search_get_state()) {
    case BTA_DM_SEARCH_ACTIVE:
      bta_dm_discover_name(bta_dm_search_cb.peer_bdaddr);
      break;
    case BTA_DM_SEARCH_IDLE:
    case BTA_DM_SEARCH_CANCELLING:
      log::warn("Received remote name request in state:{}",
                bta_dm_state_text(bta_dm_search_get_state()));
      break;
  }
}

static void bta_dm_search_cmpl() {
  bta_dm_search_set_state(BTA_DM_SEARCH_IDLE);

  if (bta_dm_search_cb.p_device_search_cback) {
    bta_dm_search_cb.p_device_search_cback(BTA_DM_DISC_CMPL_EVT, nullptr);
  }

  bta_dm_execute_queued_search_request();
}

static void bta_dm_execute_queued_search_request() {
  if (!bta_dm_search_cb.p_pending_search) {
    return;
  }

  log::info("Start pending search");
  post_search_evt(BTA_DM_API_SEARCH_EVT, std::move(bta_dm_search_cb.p_pending_search));
  bta_dm_search_cb.p_pending_search.reset();
}

/*******************************************************************************
 *
 * Function         bta_dm_search_clear_queue
 *
 * Description      Clears the queue if API search cancel is called
 *
 * Returns          void
 *
 ******************************************************************************/
static void bta_dm_search_clear_queue() { bta_dm_search_cb.p_pending_search.reset(); }

/*******************************************************************************
 *
 * Function         bta_dm_search_cancel_notify
 *
 * Description      Notify application that search has been cancelled
 *
 * Returns          void
 *
 ******************************************************************************/
static void bta_dm_search_cancel_notify() {
  if (bta_dm_search_cb.p_device_search_cback) {
    bta_dm_search_cb.p_device_search_cback(BTA_DM_SEARCH_CANCEL_CMPL_EVT, NULL);
  }
  switch (bta_dm_search_get_state()) {
    case BTA_DM_SEARCH_ACTIVE:
    case BTA_DM_SEARCH_CANCELLING:
      if (!bta_dm_search_cb.name_discover_done) {
        if (get_stack_rnr_interface().BTM_CancelRemoteDeviceName() !=
            tBTM_STATUS::BTM_CMD_STARTED) {
          log::warn("Unable to cancel RNR");
        }
      }
      break;
    case BTA_DM_SEARCH_IDLE:
      // Nothing to do
      break;
  }
}

/*******************************************************************************
 *
 * Function         bta_dm_discover_next_device
 *
 * Description      Starts discovery on the next device in Inquiry data base
 *
 * Returns          void
 *
 ******************************************************************************/
static void bta_dm_discover_next_device(void) {
  log::verbose("bta_dm_discover_next_device");

  /* searching next device on inquiry result */
  bta_dm_search_cb.p_btm_inq_info =
          get_btm_client_interface().db.BTM_InqDbNext(bta_dm_search_cb.p_btm_inq_info);
  if (bta_dm_search_cb.p_btm_inq_info != NULL) {
    bta_dm_search_cb.name_discover_done = false;
    bta_dm_search_cb.peer_name[0] = 0;
    bta_dm_discover_name(bta_dm_search_cb.p_btm_inq_info->results.remote_bd_addr);
  } else {
    post_search_evt(BTA_DM_SEARCH_CMPL_EVT, nullptr);
  }
}

/*TODO: this function is duplicated, make it common ?*/
static tBT_TRANSPORT bta_dm_determine_discovery_transport(const RawAddress& remote_bd_addr) {
  tBT_DEVICE_TYPE dev_type;
  tBLE_ADDR_TYPE addr_type;

  get_btm_client_interface().peer.BTM_ReadDevInfo(remote_bd_addr, &dev_type, &addr_type);
  if (dev_type == BT_DEVICE_TYPE_BLE || addr_type == BLE_ADDR_RANDOM) {
    return BT_TRANSPORT_LE;
  } else if (dev_type == BT_DEVICE_TYPE_DUMO) {
    if (get_btm_client_interface().peer.BTM_IsAclConnectionUp(remote_bd_addr,
                                                              BT_TRANSPORT_BR_EDR)) {
      return BT_TRANSPORT_BR_EDR;
    } else if (get_btm_client_interface().peer.BTM_IsAclConnectionUp(remote_bd_addr,
                                                                     BT_TRANSPORT_LE)) {
      return BT_TRANSPORT_LE;
    }
  }
  return BT_TRANSPORT_BR_EDR;
}

static void bta_dm_discover_name(const RawAddress& remote_bd_addr) {
  const tBT_TRANSPORT transport = bta_dm_determine_discovery_transport(remote_bd_addr);

  log::verbose("BDA: {}", remote_bd_addr);

  bta_dm_search_cb.peer_bdaddr = remote_bd_addr;

  log::verbose("name_discover_done = {} p_btm_inq_info 0x{} state = {}, transport={}",
               bta_dm_search_cb.name_discover_done,
               std::format_ptr(bta_dm_search_cb.p_btm_inq_info), bta_dm_search_get_state(),
               transport);

  if (bta_dm_search_cb.p_btm_inq_info) {
    log::verbose("appl_knows_rem_name {}", bta_dm_search_cb.p_btm_inq_info->appl_knows_rem_name);
  }
  if (((bta_dm_search_cb.p_btm_inq_info) &&
       (bta_dm_search_cb.p_btm_inq_info->results.device_type == BT_DEVICE_TYPE_BLE) &&
       (bta_dm_search_get_state() == BTA_DM_SEARCH_ACTIVE)) ||
      (transport == BT_TRANSPORT_LE &&
       interop_match_addr(INTEROP_DISABLE_NAME_REQUEST, &bta_dm_search_cb.peer_bdaddr))) {
    /* Do not perform RNR for LE devices at inquiry complete*/
    bta_dm_search_cb.name_discover_done = true;
  }
  // If we already have the name we can skip getting the name
  if (get_stack_rnr_interface().BTM_IsRemoteNameKnown(remote_bd_addr, transport)) {
    log::debug("Security record already known skipping read remote name peer:{}", remote_bd_addr);
    bta_dm_search_cb.name_discover_done = true;
  }

  /* if name discovery is not done and application needs remote name */
  if ((!bta_dm_search_cb.name_discover_done) &&
      ((bta_dm_search_cb.p_btm_inq_info == NULL) ||
       (bta_dm_search_cb.p_btm_inq_info &&
        (!bta_dm_search_cb.p_btm_inq_info->appl_knows_rem_name)))) {
    if (bta_dm_read_remote_device_name(bta_dm_search_cb.peer_bdaddr, transport)) {
      BTM_LogHistory(kBtmLogTag, bta_dm_search_cb.peer_bdaddr, "Read remote name",
                     base::StringPrintf("Transport:%s", bt_transport_text(transport).c_str()));
      return;
    } else {
      log::error("Unable to start read remote device name");
    }

    /* starting name discovery failed */
    bta_dm_search_cb.name_discover_done = true;
  }

  /* name discovery is done for this device */
  if (bta_dm_search_get_state() == BTA_DM_SEARCH_ACTIVE) {
    // if p_btm_inq_info is nullptr, there is no more inquiry results to
    // discover name for
    if (bta_dm_search_cb.p_btm_inq_info) {
      bta_dm_discover_next_device();
    } else {
      log::info("end of parsing inquiry result");
    }
  } else {
    log::info("name discovery finished in bad state: {}",
              bta_dm_state_text(bta_dm_search_get_state()));
  }
}

/*******************************************************************************
 *
 * Function         bta_dm_is_search_request_queued
 *
 * Description      Checks if there is a queued search request
 *
 * Returns          bool
 *
 ******************************************************************************/
bool bta_dm_is_search_request_queued() { return bta_dm_search_cb.p_pending_search != NULL; }

/*******************************************************************************
 *
 * Function         bta_dm_queue_search
 *
 * Description      Queues search command
 *
 * Returns          void
 *
 ******************************************************************************/
static void bta_dm_queue_search(tBTA_DM_API_SEARCH& search) {
  if (bta_dm_search_cb.p_pending_search) {
    log::warn("Overwrote previous device discovery inquiry scan request");
  }
  bta_dm_search_cb.p_pending_search.reset(new tBTA_DM_SEARCH_MSG(search));
  log::info("Queued device discovery inquiry scan request");
}

/*******************************************************************************
 *
 * Function         bta_dm_observe_results_cb
 *
 * Description      Callback for BLE Observe result
 *
 *
 * Returns          void
 *
 ******************************************************************************/
static void bta_dm_observe_results_cb(tBTM_INQ_RESULTS* p_inq, const uint8_t* p_eir,
                                      uint16_t eir_len) {
  tBTA_DM_SEARCH result;
  tBTM_INQ_INFO* p_inq_info;
  log::verbose("bta_dm_observe_results_cb");

  result.inq_res.bd_addr = p_inq->remote_bd_addr;
  result.inq_res.original_bda = p_inq->original_bda;
  result.inq_res.rssi = p_inq->rssi;
  result.inq_res.ble_addr_type = p_inq->ble_addr_type;
  result.inq_res.inq_result_type = p_inq->inq_result_type;
  result.inq_res.device_type = p_inq->device_type;
  result.inq_res.flag = p_inq->flag;
  result.inq_res.ble_evt_type = p_inq->ble_evt_type;
  result.inq_res.ble_primary_phy = p_inq->ble_primary_phy;
  result.inq_res.ble_secondary_phy = p_inq->ble_secondary_phy;
  result.inq_res.ble_advertising_sid = p_inq->ble_advertising_sid;
  result.inq_res.ble_tx_power = p_inq->ble_tx_power;
  result.inq_res.ble_periodic_adv_int = p_inq->ble_periodic_adv_int;

  /* application will parse EIR to find out remote device name */
  result.inq_res.p_eir = const_cast<uint8_t*>(p_eir);
  result.inq_res.eir_len = eir_len;

  p_inq_info = get_btm_client_interface().db.BTM_InqDbRead(p_inq->remote_bd_addr);
  if (p_inq_info != NULL) {
    /* initialize remt_name_not_required to false so that we get the name by
     * default */
    result.inq_res.remt_name_not_required = false;
  }

  if (p_inq_info) {
    /* application indicates if it knows the remote name, inside the callback
     copy that to the inquiry data base*/
    if (result.inq_res.remt_name_not_required) {
      p_inq_info->appl_knows_rem_name = true;
    }
  }
}

/*******************************************************************************
 *
 * Function         bta_dm_opportunistic_observe_results_cb
 *
 * Description      Callback for BLE Observe result
 *
 *
 * Returns          void
 *
 ******************************************************************************/
static void bta_dm_opportunistic_observe_results_cb(tBTM_INQ_RESULTS* p_inq, const uint8_t* p_eir,
                                                    uint16_t eir_len) {
  tBTA_DM_SEARCH result;
  tBTM_INQ_INFO* p_inq_info;

  result.inq_res.bd_addr = p_inq->remote_bd_addr;
  result.inq_res.rssi = p_inq->rssi;
  result.inq_res.ble_addr_type = p_inq->ble_addr_type;
  result.inq_res.inq_result_type = p_inq->inq_result_type;
  result.inq_res.device_type = p_inq->device_type;
  result.inq_res.flag = p_inq->flag;
  result.inq_res.ble_evt_type = p_inq->ble_evt_type;
  result.inq_res.ble_primary_phy = p_inq->ble_primary_phy;
  result.inq_res.ble_secondary_phy = p_inq->ble_secondary_phy;
  result.inq_res.ble_advertising_sid = p_inq->ble_advertising_sid;
  result.inq_res.ble_tx_power = p_inq->ble_tx_power;
  result.inq_res.ble_periodic_adv_int = p_inq->ble_periodic_adv_int;

  /* application will parse EIR to find out remote device name */
  result.inq_res.p_eir = const_cast<uint8_t*>(p_eir);
  result.inq_res.eir_len = eir_len;

  p_inq_info = get_btm_client_interface().db.BTM_InqDbRead(p_inq->remote_bd_addr);
  if (p_inq_info != NULL) {
    /* initialize remt_name_not_required to false so that we get the name by
     * default */
    result.inq_res.remt_name_not_required = false;
  }

  if (bta_dm_search_cb.p_csis_scan_cback) {
    bta_dm_search_cb.p_csis_scan_cback(BTA_DM_INQ_RES_EVT, &result);
  }

  if (p_inq_info) {
    /* application indicates if it knows the remote name, inside the callback
     copy that to the inquiry data base*/
    if (result.inq_res.remt_name_not_required) {
      p_inq_info->appl_knows_rem_name = true;
    }
  }
}

/*******************************************************************************
 *
 * Function         bta_dm_observe_cmpl_cb
 *
 * Description      Callback for BLE Observe complete
 *
 *
 * Returns          void
 *
 ******************************************************************************/
static void bta_dm_observe_cmpl_cb(void* p_result) {
  log::verbose("bta_dm_observe_cmpl_cb");

  if (bta_dm_search_cb.p_csis_scan_cback) {
    auto num_resps = ((tBTM_INQUIRY_CMPL*)p_result)->num_resp;
    tBTA_DM_SEARCH data{.observe_cmpl{.num_resps = num_resps}};
    bta_dm_search_cb.p_csis_scan_cback(BTA_DM_OBSERVE_CMPL_EVT, &data);
  }
}

static void bta_dm_start_scan(uint8_t duration_sec) {
  tBTM_STATUS status = get_btm_client_interface().ble.BTM_BleObserve(
          true, duration_sec, bta_dm_observe_results_cb, bta_dm_observe_cmpl_cb);

  if (status != tBTM_STATUS::BTM_CMD_STARTED) {
    log::warn("BTM_BleObserve  failed. status {}", status);
    if (bta_dm_search_cb.p_csis_scan_cback) {
      tBTA_DM_SEARCH data{.observe_cmpl = {.num_resps = 0}};
      bta_dm_search_cb.p_csis_scan_cback(BTA_DM_OBSERVE_CMPL_EVT, &data);
    }
  }
}

void bta_dm_ble_scan(bool start, uint8_t duration_sec) {
  if (!start) {
    if (get_btm_client_interface().ble.BTM_BleObserve(false, 0, NULL, NULL) !=
        tBTM_STATUS::BTM_CMD_STARTED) {
      log::warn("Unable to start ble observe");
    }
    return;
  }

  bta_dm_start_scan(duration_sec);
}

void bta_dm_ble_csis_observe(bool observe, tBTA_DM_SEARCH_CBACK* p_cback) {
  if (!observe) {
    bta_dm_search_cb.p_csis_scan_cback = NULL;
    BTM_BleOpportunisticObserve(false, NULL);
    return;
  }

  /* Save the callback to be called when a scan results are available */
  bta_dm_search_cb.p_csis_scan_cback = p_cback;
  BTM_BleOpportunisticObserve(true, bta_dm_opportunistic_observe_results_cb);
}

namespace bluetooth {
namespace legacy {
namespace testing {

void bta_dm_remname_cback(const tBTM_REMOTE_DEV_NAME* p) { ::bta_dm_remname_cback(p); }

void bta_dm_remote_name_cmpl(const tBTA_DM_REMOTE_NAME& remote_name_msg) {
  ::bta_dm_remote_name_cmpl(remote_name_msg);
}

}  // namespace testing
}  // namespace legacy
}  // namespace bluetooth

namespace {
constexpr size_t kSearchStateHistorySize = 50;
constexpr char kTimeFormatString[] = "%Y-%m-%d %H:%M:%S";

constexpr unsigned MillisPerSecond = 1000;
std::string EpochMillisToString(uint64_t time_ms) {
  time_t time_sec = time_ms / MillisPerSecond;
  struct tm tm;
  localtime_r(&time_sec, &tm);
  std::string s = bluetooth::common::StringFormatTime(kTimeFormatString, tm);
  return base::StringPrintf("%s.%03u", s.c_str(),
                            static_cast<unsigned int>(time_ms % MillisPerSecond));
}

}  // namespace

struct tSEARCH_STATE_HISTORY {
  const tBTA_DM_DEVICE_SEARCH_STATE state;
  const tBTA_DM_DEV_SEARCH_EVT event;
  std::string ToString() const {
    return base::StringPrintf("state:%25s event:%s", bta_dm_state_text(state).c_str(),
                              bta_dm_event_text(event).c_str());
  }
};

bluetooth::common::TimestampedCircularBuffer<tSEARCH_STATE_HISTORY> search_state_history_(
        kSearchStateHistorySize);

/*******************************************************************************
 *
 * Function         bta_dm_search_sm_execute
 *
 * Description      State machine event handling function for DM
 *
 *
 * Returns          void
 *
 ******************************************************************************/
static void bta_dm_search_sm_execute(tBTA_DM_DEV_SEARCH_EVT event,
                                     std::unique_ptr<tBTA_DM_SEARCH_MSG> msg) {
  log::info("state:{}, event:{}[0x{:x}]", bta_dm_state_text(bta_dm_search_get_state()),
            bta_dm_event_text(event), event);
  search_state_history_.Push({
          .state = bta_dm_search_get_state(),
          .event = event,
  });

  switch (bta_dm_search_get_state()) {
    case BTA_DM_SEARCH_IDLE:
      switch (event) {
        case BTA_DM_API_SEARCH_EVT:
          bta_dm_search_set_state(BTA_DM_SEARCH_ACTIVE);
          log::assert_that(std::holds_alternative<tBTA_DM_API_SEARCH>(*msg), "bad message type: {}",
                           msg->index());

          bta_dm_search_start(std::get<tBTA_DM_API_SEARCH>(*msg));
          break;
        case BTA_DM_API_SEARCH_CANCEL_EVT:
          bta_dm_search_clear_queue();
          bta_dm_search_cancel_notify();
          break;
        default:
          log::info("Received unexpected event {}[0x{:x}] in state {}", bta_dm_event_text(event),
                    event, bta_dm_state_text(bta_dm_search_get_state()));
      }
      break;
    case BTA_DM_SEARCH_ACTIVE:
      switch (event) {
        case BTA_DM_REMT_NAME_EVT:
          log::assert_that(std::holds_alternative<tBTA_DM_REMOTE_NAME>(*msg),
                           "bad message type: {}", msg->index());

          bta_dm_remote_name_cmpl(std::get<tBTA_DM_REMOTE_NAME>(*msg));
          break;
        case BTA_DM_SEARCH_CMPL_EVT:
          bta_dm_search_cmpl();
          break;
        case BTA_DM_API_SEARCH_CANCEL_EVT:
          bta_dm_search_clear_queue();
          bta_dm_search_set_state(BTA_DM_SEARCH_CANCELLING);
          bta_dm_search_cancel();
          break;
        default:
          log::info("Received unexpected event {}[0x{:x}] in state {}", bta_dm_event_text(event),
                    event, bta_dm_state_text(bta_dm_search_get_state()));
      }
      break;
    case BTA_DM_SEARCH_CANCELLING:
      switch (event) {
        case BTA_DM_API_SEARCH_EVT:
          log::assert_that(std::holds_alternative<tBTA_DM_API_SEARCH>(*msg), "bad message type: {}",
                           msg->index());

          bta_dm_queue_search(std::get<tBTA_DM_API_SEARCH>(*msg));
          break;
        case BTA_DM_API_SEARCH_CANCEL_EVT:
          bta_dm_search_clear_queue();
          bta_dm_search_cancel_notify();
          break;
        case BTA_DM_REMT_NAME_EVT:
        case BTA_DM_SEARCH_CMPL_EVT:
          bta_dm_search_set_state(BTA_DM_SEARCH_IDLE);
          bta_dm_search_cancel_notify();
          bta_dm_execute_queued_search_request();
          break;
        default:
          log::info("Received unexpected event {}[0x{:x}] in state {}", bta_dm_event_text(event),
                    event, bta_dm_state_text(bta_dm_search_get_state()));
      }
      break;
  }
}

void bta_dm_disc_disable_search(void) {
  switch (bta_dm_search_get_state()) {
    case BTA_DM_SEARCH_IDLE:
      break;
    case BTA_DM_SEARCH_ACTIVE:
    case BTA_DM_SEARCH_CANCELLING:
    default:
      log::debug(
              "Search state machine is not idle so issuing search cancel current "
              "state:{}",
              bta_dm_state_text(bta_dm_search_get_state()));
      bta_dm_search_cancel();
  }
}

void bta_dm_disc_start_device_discovery(tBTA_DM_SEARCH_CBACK* p_cback) {
  bta_dm_search_sm_execute(BTA_DM_API_SEARCH_EVT, std::make_unique<tBTA_DM_SEARCH_MSG>(
                                                          tBTA_DM_API_SEARCH{.p_cback = p_cback}));
}

void bta_dm_disc_stop_device_discovery() {
  bta_dm_search_sm_execute(BTA_DM_API_SEARCH_CANCEL_EVT, nullptr);
}

static void bta_dm_disc_init_search_cb(tBTA_DM_SEARCH_CB& bta_dm_search_cb) {
  bta_dm_search_cb = {};
  bta_dm_search_cb.search_state = BTA_DM_SEARCH_IDLE;
}

static void bta_dm_search_reset() {
  bta_dm_search_cb.p_pending_search.reset();
  bta_dm_disc_init_search_cb(::bta_dm_search_cb);
}

void bta_dm_search_stop() { bta_dm_search_reset(); }

void bta_dm_disc_discover_next_device() { bta_dm_discover_next_device(); }

#define DUMPSYS_TAG "shim::legacy::bta::dm"
void DumpsysBtaDmSearch(int fd) {
  auto copy = search_state_history_.Pull();
  LOG_DUMPSYS(fd, " last %zu search state transitions", copy.size());
  for (const auto& it : copy) {
    LOG_DUMPSYS(fd, "   %s %s", EpochMillisToString(it.timestamp).c_str(),
                it.entry.ToString().c_str());
  }
  LOG_DUMPSYS(fd, " current bta_dm_search_state:%s",
              bta_dm_state_text(bta_dm_search_get_state()).c_str());
}
#undef DUMPSYS_TAG

namespace bluetooth {
namespace legacy {
namespace testing {

void bta_dm_disc_init_search_cb(tBTA_DM_SEARCH_CB& bta_dm_search_cb) {
  ::bta_dm_disc_init_search_cb(bta_dm_search_cb);
}
void bta_dm_discover_next_device() { ::bta_dm_discover_next_device(); }

tBTA_DM_SEARCH_CB bta_dm_disc_get_search_cb() {
  tBTA_DM_SEARCH_CB search_cb = {};
  ::bta_dm_disc_init_search_cb(search_cb);
  return search_cb;
}
tBTA_DM_SEARCH_CB& bta_dm_disc_search_cb() { return ::bta_dm_search_cb; }
bool bta_dm_read_remote_device_name(const RawAddress& bd_addr, tBT_TRANSPORT transport) {
  return ::bta_dm_read_remote_device_name(bd_addr, transport);
}

void bta_dm_inq_cmpl() { ::bta_dm_inq_cmpl(); }
void bta_dm_inq_cmpl_cb(void* p_result) { ::bta_dm_inq_cmpl_cb(p_result); }
void bta_dm_observe_cmpl_cb(void* p_result) { ::bta_dm_observe_cmpl_cb(p_result); }
void bta_dm_observe_results_cb(tBTM_INQ_RESULTS* p_inq, const uint8_t* p_eir, uint16_t eir_len) {
  ::bta_dm_observe_results_cb(p_inq, p_eir, eir_len);
}
void bta_dm_opportunistic_observe_results_cb(tBTM_INQ_RESULTS* p_inq, const uint8_t* p_eir,
                                             uint16_t eir_len) {
  ::bta_dm_opportunistic_observe_results_cb(p_inq, p_eir, eir_len);
}
void bta_dm_queue_search(tBTA_DM_API_SEARCH& search) { ::bta_dm_queue_search(search); }

void bta_dm_start_scan(uint8_t duration_sec) { ::bta_dm_start_scan(duration_sec); }

}  // namespace testing
}  // namespace legacy
}  // namespace bluetooth
