#include "stack/connection_manager/connection_manager.h"

#include <base/bind_helpers.h>
#include <base/functional/bind.h>
#include <base/functional/callback.h>
#include <base/location.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>

#include <memory>

#include "osi/include/alarm.h"
#include "osi/test/alarm_mock.h"
#include "security_device_record.h"
#include "stack/btm/neighbor_inquiry.h"

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

using testing::_;
using testing::DoAll;
using testing::Mock;
using testing::Return;
using testing::SaveArg;

using connection_manager::tAPP_ID;

namespace {
// convenience mock, for verifying acceptlist operations on lower layer are
// actually scheduled
class AcceptlistMock {
public:
  MOCK_METHOD2(AcceptlistAdd, bool(const RawAddress&, bool is_direct));
  MOCK_METHOD1(AcceptlistRemove, void(const RawAddress&));
  MOCK_METHOD0(AcceptlistClear, void());
  MOCK_METHOD2(OnConnectionTimedOut, void(uint8_t, const RawAddress&));

  /* Not really accept list related, btui still BTM - just for testing put it
   * here. */
  MOCK_METHOD2(EnableTargetedAnnouncements, void(bool, tBTM_INQ_RESULTS_CB*));
};

std::unique_ptr<AcceptlistMock> localAcceptlistMock;
}  // namespace

RawAddress address1{{0x01, 0x01, 0x01, 0x01, 0x01, 0x01}};
RawAddress address2{{0x22, 0x22, 0x02, 0x22, 0x33, 0x22}};

constexpr tAPP_ID CLIENT1 = 1;
constexpr tAPP_ID CLIENT2 = 2;
constexpr tAPP_ID CLIENT3 = 3;
constexpr tAPP_ID CLIENT10 = 10;

const tBLE_BD_ADDR BTM_Sec_GetAddressWithType(const RawAddress& bd_addr) {
  return tBLE_BD_ADDR{.type = BLE_ADDR_PUBLIC, .bda = bd_addr};
}

tBTM_SEC_DEV_REC* btm_find_dev(const RawAddress& /* bd_addr */) { return nullptr; }

namespace bluetooth {
namespace shim {

bool ACL_AcceptLeConnectionFrom(const tBLE_BD_ADDR& address, bool is_direct) {
  return localAcceptlistMock->AcceptlistAdd(address.bda, is_direct);
}
void ACL_IgnoreLeConnectionFrom(const tBLE_BD_ADDR& address) {
  return localAcceptlistMock->AcceptlistRemove(address.bda);
}

void ACL_IgnoreAllLeConnections() { return localAcceptlistMock->AcceptlistClear(); }

}  // namespace shim
}  // namespace bluetooth

void BTM_BleTargetAnnouncementObserve(bool enable, tBTM_INQ_RESULTS_CB* p_results_cb) {
  localAcceptlistMock->EnableTargetedAnnouncements(enable, p_results_cb);
}

void BTM_LogHistory(const std::string& /*tag*/, const RawAddress& /*bd_addr*/,
                    const std::string& /*msg*/) {}

namespace bluetooth {
namespace shim {
void set_target_announcements_filter(bool /*enable*/) {}
}  // namespace shim
}  // namespace bluetooth

bool L2CA_ConnectFixedChnl(uint16_t /*fixed_cid*/, const RawAddress& /*bd_addr*/) { return false; }
uint16_t BTM_GetHCIConnHandle(RawAddress const&, unsigned char) { return 0xFFFF; }

namespace connection_manager {
class BleConnectionManager : public testing::Test {
  void SetUp() override {
    localAcceptlistMock = std::make_unique<AcceptlistMock>();
    auto alarm_mock = AlarmMock::Get();
    ON_CALL(*alarm_mock, AlarmNew(_)).WillByDefault(testing::Invoke([](const char* /*name*/) {
      // We must return something from alarm_new in tests, if we just return
      // null, unique_ptr will misbehave.
      return (alarm_t*)new uint8_t[30];
    }));
    ON_CALL(*alarm_mock, AlarmFree(_)).WillByDefault(testing::Invoke([](alarm_t* alarm) {
      if (alarm) {
        uint8_t* ptr = (uint8_t*)alarm;
        delete[] ptr;
      }
    }));
  }

  void TearDown() override {
    connection_manager::reset(true);
    AlarmMock::Reset();
    localAcceptlistMock.reset();
  }
};

void on_connection_timed_out(uint8_t app_id, const RawAddress& address) {
  localAcceptlistMock->OnConnectionTimedOut(app_id, address);
}

/** Verify that app can add a device to acceptlist, it is returned as interested
 * app, and then can remove the device later. */
TEST_F(BleConnectionManager, test_background_connection_add_remove) {
  EXPECT_CALL(*localAcceptlistMock, AcceptlistAdd(address1, false)).WillOnce(Return(true));
  EXPECT_CALL(*localAcceptlistMock, AcceptlistRemove(_)).Times(0);

  EXPECT_TRUE(background_connect_add(CLIENT1, address1));

  Mock::VerifyAndClearExpectations(localAcceptlistMock.get());

  std::set<tAPP_ID> apps = get_apps_connecting_to(address1);
  EXPECT_EQ(apps.size(), 1UL);
  EXPECT_EQ(apps.count(CLIENT1), 1UL);

  EXPECT_CALL(*localAcceptlistMock, AcceptlistAdd(_, _)).Times(0);
  EXPECT_CALL(*localAcceptlistMock, AcceptlistRemove(address1)).Times(1);

  EXPECT_TRUE(background_connect_remove(CLIENT1, address1));

  EXPECT_EQ(get_apps_connecting_to(address1).size(), 0UL);

  Mock::VerifyAndClearExpectations(localAcceptlistMock.get());
}

/** Verify that multiple clients adding same device multiple times, result in
 * device being added to whtie list only once, also, that device is removed only
 * after last client removes it. */
TEST_F(BleConnectionManager, test_background_connection_multiple_clients) {
  EXPECT_CALL(*localAcceptlistMock, AcceptlistAdd(address1, false)).WillOnce(Return(true));
  EXPECT_CALL(*localAcceptlistMock, AcceptlistRemove(_)).Times(0);
  EXPECT_TRUE(background_connect_add(CLIENT1, address1));
  EXPECT_TRUE(background_connect_add(CLIENT1, address1));
  EXPECT_TRUE(background_connect_add(CLIENT2, address1));
  EXPECT_TRUE(background_connect_add(CLIENT3, address1));

  EXPECT_EQ(get_apps_connecting_to(address1).size(), 3UL);

  Mock::VerifyAndClearExpectations(localAcceptlistMock.get());

  EXPECT_CALL(*localAcceptlistMock, AcceptlistAdd(_, _)).Times(0);

  // removing from nonexisting client, should fail
  EXPECT_FALSE(background_connect_remove(CLIENT10, address1));

  EXPECT_TRUE(background_connect_remove(CLIENT1, address1));
  // already removed,  removing from same client twice should return false;
  EXPECT_FALSE(background_connect_remove(CLIENT1, address1));
  EXPECT_TRUE(background_connect_remove(CLIENT2, address1));

  EXPECT_CALL(*localAcceptlistMock, AcceptlistRemove(address1)).Times(1);
  EXPECT_TRUE(background_connect_remove(CLIENT3, address1));

  EXPECT_EQ(get_apps_connecting_to(address1).size(), 0UL);

  Mock::VerifyAndClearExpectations(localAcceptlistMock.get());
}

/** Verify adding/removing device to direct connection. */
TEST_F(BleConnectionManager, test_direct_connection_client) {
  // Direct connect attempt: use faster scan parameters, add to acceptlist,
  // start 30 timeout
  EXPECT_CALL(*localAcceptlistMock, AcceptlistAdd(address1, true)).WillOnce(Return(true));
  EXPECT_CALL(*localAcceptlistMock, AcceptlistRemove(_)).Times(0);
  EXPECT_CALL(*AlarmMock::Get(), AlarmNew(_)).Times(1);
  EXPECT_CALL(*AlarmMock::Get(), AlarmSetOnMloop(_, _, _, _)).Times(1);
  EXPECT_TRUE(direct_connect_add(CLIENT1, address1));

  // App already doing a direct connection, attempt to re-add result in failure
  EXPECT_FALSE(direct_connect_add(CLIENT1, address1));

  // Client that don't do direct connection should fail attempt to stop it
  EXPECT_FALSE(direct_connect_remove(CLIENT2, address1));

  Mock::VerifyAndClearExpectations(localAcceptlistMock.get());

  EXPECT_CALL(*localAcceptlistMock, AcceptlistRemove(_)).Times(1);
  EXPECT_CALL(*AlarmMock::Get(), AlarmFree(_)).Times(1);

  // Removal should lower the connection parameters, and free the alarm.
  // Even though we call AcceptlistRemove, it won't be executed over HCI until
  // acceptlist is in use, i.e. next connection attempt
  EXPECT_TRUE(direct_connect_remove(CLIENT1, address1));

  Mock::VerifyAndClearExpectations(localAcceptlistMock.get());
}

/** Verify direct connection timeout does remove device from acceptlist, and
 * lower the connection scan parameters */
TEST_F(BleConnectionManager, test_direct_connect_timeout) {
  EXPECT_CALL(*localAcceptlistMock, AcceptlistAdd(address1, true)).WillOnce(Return(true));
  EXPECT_CALL(*AlarmMock::Get(), AlarmNew(_)).Times(1);
  alarm_callback_t alarm_callback = nullptr;
  void* alarm_data = nullptr;

  EXPECT_CALL(*AlarmMock::Get(), AlarmSetOnMloop(_, _, _, _))
          .Times(1)
          .WillOnce(DoAll(SaveArg<2>(&alarm_callback), SaveArg<3>(&alarm_data)));

  // Start direct connect attempt...
  EXPECT_TRUE(direct_connect_add(CLIENT1, address1));

  Mock::VerifyAndClearExpectations(localAcceptlistMock.get());

  EXPECT_CALL(*localAcceptlistMock, AcceptlistRemove(_)).Times(1);
  EXPECT_CALL(*localAcceptlistMock, OnConnectionTimedOut(CLIENT1, address1)).Times(1);
  EXPECT_CALL(*AlarmMock::Get(), AlarmFree(_)).Times(1);

  // simulate timeout seconds passed, alarm executing
  alarm_callback(alarm_data);

  Mock::VerifyAndClearExpectations(localAcceptlistMock.get());
}

/** Verify that we properly handle successfull direct connection */
TEST_F(BleConnectionManager, test_direct_connection_success) {
  EXPECT_CALL(*localAcceptlistMock, AcceptlistAdd(address1, true)).WillOnce(Return(true));
  EXPECT_CALL(*AlarmMock::Get(), AlarmNew(_)).Times(1);
  EXPECT_CALL(*AlarmMock::Get(), AlarmSetOnMloop(_, _, _, _)).Times(1);

  // Start direct connect attempt...
  EXPECT_TRUE(direct_connect_add(CLIENT1, address1));

  Mock::VerifyAndClearExpectations(localAcceptlistMock.get());

  EXPECT_CALL(*localAcceptlistMock, AcceptlistRemove(address1)).Times(1);
  EXPECT_CALL(*AlarmMock::Get(), AlarmFree(_)).Times(1);
  // simulate event from lower layers - connections was established
  // successfully.
  on_connection_complete(address1);
}

/** Verify that we properly handle application unregistration */
TEST_F(BleConnectionManager, test_app_unregister) {
  /* Test scenario:
   * - Client 1 connecting to address1 and address2.
   * - Client 2 connecting to address2
   * - unregistration of Client1 should trigger address1 removal from acceptlist
   * - unregistration of Client2 should trigger address2 removal
   */

  EXPECT_CALL(*localAcceptlistMock, AcceptlistAdd(address1, true)).WillOnce(Return(true));
  EXPECT_CALL(*localAcceptlistMock, AcceptlistAdd(address2, false)).WillOnce(Return(true));
  EXPECT_TRUE(direct_connect_add(CLIENT1, address1));
  EXPECT_TRUE(background_connect_add(CLIENT1, address2));
  EXPECT_TRUE(direct_connect_add(CLIENT2, address2));
  Mock::VerifyAndClearExpectations(localAcceptlistMock.get());

  EXPECT_CALL(*localAcceptlistMock, AcceptlistRemove(address1)).Times(1);
  on_app_deregistered(CLIENT1);
  Mock::VerifyAndClearExpectations(localAcceptlistMock.get());

  EXPECT_CALL(*localAcceptlistMock, AcceptlistRemove(address2)).Times(1);
  on_app_deregistered(CLIENT2);
}

/** Verify adding device to both direct connection and background connection. */
TEST_F(BleConnectionManager, test_direct_and_background_connect) {
  EXPECT_CALL(*localAcceptlistMock, AcceptlistAdd(address1, true)).WillOnce(Return(true));
  EXPECT_CALL(*localAcceptlistMock, AcceptlistRemove(_)).Times(0);
  EXPECT_CALL(*AlarmMock::Get(), AlarmNew(_)).Times(1);
  EXPECT_CALL(*AlarmMock::Get(), AlarmSetOnMloop(_, _, _, _)).Times(1);
  // add device as both direct and background connection
  EXPECT_TRUE(direct_connect_add(CLIENT1, address1));
  EXPECT_TRUE(background_connect_add(CLIENT1, address1));

  Mock::VerifyAndClearExpectations(localAcceptlistMock.get());

  EXPECT_CALL(*AlarmMock::Get(), AlarmFree(_)).Times(1);
  // not removing from acceptlist yet, as the background connection is still
  // pending.
  EXPECT_TRUE(direct_connect_remove(CLIENT1, address1));

  // remove from acceptlist, because no more interest in device.
  EXPECT_CALL(*localAcceptlistMock, AcceptlistRemove(_)).Times(1);
  EXPECT_TRUE(background_connect_remove(CLIENT1, address1));

  Mock::VerifyAndClearExpectations(localAcceptlistMock.get());
}

TEST_F(BleConnectionManager, test_target_announement_connect) {
  EXPECT_CALL(*localAcceptlistMock, AcceptlistRemove(_)).Times(0);
  EXPECT_TRUE(background_connect_targeted_announcement_add(CLIENT1, address1));
  EXPECT_TRUE(background_connect_targeted_announcement_add(CLIENT1, address1));
}

TEST_F(BleConnectionManager, test_add_targeted_announement_when_allow_list_used) {
  /* Accept adding to allow list */
  EXPECT_CALL(*localAcceptlistMock, AcceptlistAdd(address1, false)).WillOnce(Return(true));

  /* This shall be called when registering announcements */
  EXPECT_CALL(*localAcceptlistMock, AcceptlistRemove(_)).Times(1);
  EXPECT_TRUE(background_connect_add(CLIENT1, address1));
  EXPECT_TRUE(background_connect_targeted_announcement_add(CLIENT2, address1));

  Mock::VerifyAndClearExpectations(localAcceptlistMock.get());
}

TEST_F(BleConnectionManager, test_add_background_connect_when_targeted_announcement_are_enabled) {
  /* Accept adding to allow list */
  EXPECT_CALL(*localAcceptlistMock, AcceptlistAdd(address1, false)).Times(0);

  /* This shall be called when registering announcements */
  EXPECT_CALL(*localAcceptlistMock, AcceptlistRemove(_)).Times(0);

  EXPECT_TRUE(background_connect_targeted_announcement_add(CLIENT2, address1));

  EXPECT_TRUE(background_connect_add(CLIENT1, address1));
  Mock::VerifyAndClearExpectations(localAcceptlistMock.get());
}

TEST_F(BleConnectionManager, test_re_add_background_connect_to_allow_list) {
  EXPECT_CALL(*localAcceptlistMock, AcceptlistAdd(address1, false)).Times(0);
  EXPECT_CALL(*localAcceptlistMock, AcceptlistRemove(_)).Times(0);

  EXPECT_TRUE(background_connect_targeted_announcement_add(CLIENT2, address1));

  EXPECT_TRUE(background_connect_add(CLIENT1, address1));
  Mock::VerifyAndClearExpectations(localAcceptlistMock.get());

  /* Now remove app using targeted announcement and expect device
   * to be added to white list
   */

  /* Accept adding to allow list */
  EXPECT_CALL(*localAcceptlistMock, AcceptlistAdd(address1, false)).WillOnce(Return(true));

  EXPECT_TRUE(background_connect_remove(CLIENT2, address1));
  Mock::VerifyAndClearExpectations(localAcceptlistMock.get());

  EXPECT_CALL(*localAcceptlistMock, AcceptlistRemove(_)).Times(1);
  EXPECT_TRUE(background_connect_remove(CLIENT1, address1));
  Mock::VerifyAndClearExpectations(localAcceptlistMock.get());
}

TEST_F(BleConnectionManager, test_re_add_to_allow_list_after_timeout_with_multiple_clients) {
  EXPECT_CALL(*AlarmMock::Get(), AlarmNew(_)).Times(1);
  alarm_callback_t alarm_callback = nullptr;
  void* alarm_data = nullptr;

  /* Accept adding to allow list */
  ON_CALL(*localAcceptlistMock, AcceptlistAdd(address1, _)).WillByDefault(Return(true));

  EXPECT_CALL(*localAcceptlistMock, AcceptlistAdd(address1, false)).Times(1);
  EXPECT_CALL(*localAcceptlistMock, AcceptlistRemove(_)).Times(0);

  EXPECT_TRUE(background_connect_add(CLIENT1, address1));

  Mock::VerifyAndClearExpectations(localAcceptlistMock.get());

  EXPECT_CALL(*AlarmMock::Get(), AlarmSetOnMloop(_, _, _, _))
          .Times(1)
          .WillOnce(DoAll(SaveArg<2>(&alarm_callback), SaveArg<3>(&alarm_data)));
  // Start direct connect attempt...
  EXPECT_TRUE(direct_connect_add(CLIENT2, address1));

  Mock::VerifyAndClearExpectations(localAcceptlistMock.get());

  // simulate timeout seconds passed, alarm executing
  EXPECT_CALL(*localAcceptlistMock, OnConnectionTimedOut(CLIENT2, address1)).Times(1);
  EXPECT_CALL(*localAcceptlistMock, AcceptlistRemove(_)).Times(0);
  EXPECT_CALL(*localAcceptlistMock, AcceptlistAdd(address1, false)).Times(1);
  EXPECT_CALL(*AlarmMock::Get(), AlarmFree(_)).Times(1);
  alarm_callback(alarm_data);

  Mock::VerifyAndClearExpectations(localAcceptlistMock.get());
}

}  // namespace connection_manager
