/*
 * Copyright 2023 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.
 */

#include <base/strings/stringprintf.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <sys/socket.h>

#include "bta/dm/bta_dm_sec_int.h"
#include "bta/test/bta_test_fixtures.h"
#include "btm_status.h"
#include "test/mock/mock_stack_btm_inq.h"
#include "test/mock/mock_stack_btm_interface.h"
#include "types/raw_address.h"

using ::testing::ElementsAre;

namespace {
const RawAddress kRawAddress({0x11, 0x22, 0x33, 0x44, 0x55, 0x66});
const RawAddress kRawAddress2({0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc});
const DEV_CLASS kDeviceClass = {0x11, 0x22, 0x33};

constexpr char kRemoteName[] = "TheRemoteName";

}  // namespace

// Test hooks
namespace bluetooth {
namespace legacy {
namespace testing {

tBTM_STATUS bta_dm_sp_cback(tBTM_SP_EVT event, tBTM_SP_EVT_DATA* p_data);

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

class BtaSecTest : public BtaWithHwOnTest {
 protected:
  void SetUp() override { BtaWithHwOnTest::SetUp(); }

  void TearDown() override { BtaWithHwOnTest::TearDown(); }
};

TEST_F(BtaSecTest, bta_dm_sp_cback__BTM_SP_CFM_REQ_EVT_WithName) {
  constexpr uint32_t kNumVal = 1234;
  static bool callback_sent = false;
  mock_btm_client_interface.peer.BTM_ReadRemoteDeviceName =
      [](const RawAddress& remote_bda, tBTM_NAME_CMPL_CB* p_cb,
         tBT_TRANSPORT transport) -> tBTM_STATUS { return BTM_CMD_STARTED; };

  static tBTA_DM_SP_CFM_REQ cfm_req{};
  bta_dm_sec_enable([](tBTA_DM_SEC_EVT event, tBTA_DM_SEC* p_data) {
    callback_sent = true;
    cfm_req = p_data->cfm_req;
  });

  tBTM_SP_EVT_DATA data = {
      .cfm_req =
          {
              // tBTM_SP_CFM_REQ
              .bd_addr = kRawAddress,
              .dev_class = {},
              .bd_name = {},
              .num_val = kNumVal,
              .just_works = false,
              .loc_auth_req = BTM_AUTH_SP_YES,
              .rmt_auth_req = BTM_AUTH_SP_YES,
              .loc_io_caps = BTM_IO_CAP_NONE,
              .rmt_io_caps = BTM_IO_CAP_NONE,
          },
  };
  data.cfm_req.dev_class = kDeviceClass;
  bd_name_from_char_pointer(data.cfm_req.bd_name, kRemoteName);

  ASSERT_EQ(btm_status_text(BTM_CMD_STARTED),
            btm_status_text(bluetooth::legacy::testing::bta_dm_sp_cback(
                BTM_SP_CFM_REQ_EVT, &data)));
  ASSERT_EQ(kNumVal, bta_dm_sec_cb.num_val);
  ASSERT_TRUE(callback_sent);

  ASSERT_EQ(kRawAddress, cfm_req.bd_addr);
  ASSERT_THAT(cfm_req.dev_class,
              ElementsAre(kDeviceClass[0], kDeviceClass[1], kDeviceClass[2]));
  ASSERT_STREQ(kRemoteName, reinterpret_cast<const char*>(cfm_req.bd_name));
  ASSERT_EQ(kNumVal, cfm_req.num_val);
  ASSERT_EQ(false, cfm_req.just_works);
  ASSERT_EQ(BTM_AUTH_SP_YES, cfm_req.loc_auth_req);
  ASSERT_EQ(BTM_AUTH_SP_YES, cfm_req.rmt_auth_req);
  ASSERT_EQ(BTM_IO_CAP_NONE, cfm_req.loc_io_caps);
  ASSERT_EQ(BTM_IO_CAP_NONE, cfm_req.rmt_io_caps);
}

TEST_F(BtaSecTest, bta_dm_sp_cback__BTM_SP_CFM_REQ_EVT_WithoutName_RNRSuccess) {
  constexpr uint32_t kNumVal = 1234;
  static bool callback_sent = false;
  reset_mock_btm_client_interface();
  mock_btm_client_interface.peer.BTM_ReadRemoteDeviceName =
      [](const RawAddress& remote_bda, tBTM_NAME_CMPL_CB* p_cb,
         tBT_TRANSPORT transport) -> tBTM_STATUS { return BTM_CMD_STARTED; };

  static tBTA_DM_SP_CFM_REQ cfm_req{};
  bta_dm_sec_enable([](tBTA_DM_SEC_EVT event, tBTA_DM_SEC* p_data) {
    callback_sent = true;
    cfm_req = p_data->cfm_req;
  });

  tBTM_SP_EVT_DATA data = {
      .cfm_req =
          {
              // tBTM_SP_CFM_REQ
              .bd_addr = kRawAddress,
              .dev_class = {},
              .bd_name = {0},  // No name available
              .num_val = kNumVal,
              .just_works = false,
              .loc_auth_req = BTM_AUTH_SP_YES,
              .rmt_auth_req = BTM_AUTH_SP_YES,
              .loc_io_caps = BTM_IO_CAP_NONE,
              .rmt_io_caps = BTM_IO_CAP_NONE,
          },
  };
  data.cfm_req.dev_class = kDeviceClass;

  ASSERT_EQ(btm_status_text(BTM_CMD_STARTED),
            btm_status_text(bluetooth::legacy::testing::bta_dm_sp_cback(
                BTM_SP_CFM_REQ_EVT, &data)));
  ASSERT_EQ(kNumVal, bta_dm_sec_cb.num_val);
  ASSERT_FALSE(callback_sent);

  test::mock::stack_btm_inq::BTM_ReadRemoteDeviceName = {};
}

TEST_F(BtaSecTest, bta_dm_sp_cback__BTM_SP_CFM_REQ_EVT_WithoutName_RNRFail) {
  constexpr uint32_t kNumVal = 1234;
  static bool callback_sent = false;
  mock_btm_client_interface.peer.BTM_ReadRemoteDeviceName =
      [](const RawAddress& remote_bda, tBTM_NAME_CMPL_CB* p_cb,
         tBT_TRANSPORT transport) -> tBTM_STATUS { return BTM_SUCCESS; };

  static tBTA_DM_SP_CFM_REQ cfm_req{};
  bta_dm_sec_enable([](tBTA_DM_SEC_EVT event, tBTA_DM_SEC* p_data) {
    callback_sent = true;
    cfm_req = p_data->cfm_req;
  });

  tBTM_SP_EVT_DATA data = {
      .cfm_req =
          {
              // tBTM_SP_CFM_REQ
              .bd_addr = kRawAddress,
              .dev_class = {},
              .bd_name = {0},
              .num_val = kNumVal,
              .just_works = false,
              .loc_auth_req = BTM_AUTH_SP_YES,
              .rmt_auth_req = BTM_AUTH_SP_YES,
              .loc_io_caps = BTM_IO_CAP_NONE,
              .rmt_io_caps = BTM_IO_CAP_NONE,
          },
  };
  data.cfm_req.dev_class = kDeviceClass;

  ASSERT_EQ(btm_status_text(BTM_CMD_STARTED),
            btm_status_text(bluetooth::legacy::testing::bta_dm_sp_cback(
                BTM_SP_CFM_REQ_EVT, &data)));
  ASSERT_EQ(kNumVal, bta_dm_sec_cb.num_val);
  ASSERT_TRUE(callback_sent);

  ASSERT_EQ(kRawAddress, cfm_req.bd_addr);
  ASSERT_THAT(cfm_req.dev_class,
              ElementsAre(kDeviceClass[0], kDeviceClass[1], kDeviceClass[2]));
  ASSERT_EQ(kNumVal, cfm_req.num_val);
  ASSERT_EQ(false, cfm_req.just_works);
  ASSERT_EQ(BTM_AUTH_SP_YES, cfm_req.loc_auth_req);
  ASSERT_EQ(BTM_AUTH_SP_YES, cfm_req.rmt_auth_req);
  ASSERT_EQ(BTM_IO_CAP_NONE, cfm_req.loc_io_caps);
  ASSERT_EQ(BTM_IO_CAP_NONE, cfm_req.rmt_io_caps);
}

TEST_F(BtaSecTest, bta_dm_sp_cback__BTM_SP_KEY_NOTIF_EVT) {
  constexpr uint32_t kPassKey = 1234;
  static bool callback_sent = false;
  mock_btm_client_interface.peer.BTM_ReadRemoteDeviceName =
      [](const RawAddress& remote_bda, tBTM_NAME_CMPL_CB* p_cb,
         tBT_TRANSPORT transport) -> tBTM_STATUS { return BTM_CMD_STARTED; };

  static tBTA_DM_SP_KEY_NOTIF key_notif{};
  bta_dm_sec_enable([](tBTA_DM_SEC_EVT event, tBTA_DM_SEC* p_data) {
    callback_sent = true;
    key_notif = p_data->key_notif;
  });

  tBTM_SP_EVT_DATA data = {
      .key_notif =
          {
              // tBTM_SP_KEY_NOTIF
              .bd_addr = kRawAddress,
              .dev_class = {},
              .bd_name = {},
              .passkey = kPassKey,
          },
  };
  data.key_notif.dev_class = kDeviceClass;
  bd_name_from_char_pointer(data.key_notif.bd_name, kRemoteName);

  ASSERT_EQ(btm_status_text(BTM_CMD_STARTED),
            btm_status_text(bluetooth::legacy::testing::bta_dm_sp_cback(
                BTM_SP_KEY_NOTIF_EVT, &data)));
  ASSERT_EQ(kPassKey, bta_dm_sec_cb.num_val);
  ASSERT_TRUE(callback_sent);

  ASSERT_EQ(kRawAddress, key_notif.bd_addr);
  ASSERT_THAT(key_notif.dev_class,
              ElementsAre(kDeviceClass[0], kDeviceClass[1], kDeviceClass[2]));
  ASSERT_STREQ(kRemoteName, reinterpret_cast<const char*>(key_notif.bd_name));
  ASSERT_EQ(kPassKey, key_notif.passkey);
}