/*
 * Copyright 2024 Google LLC
 *
 * 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 "services/common/bluetooth/bluetooth_persistent_storage.h"
#include "services/common/bluetooth/local_addr.h"

#include <bluetooth/bluetooth_types.h>

#include <stdbool.h>

#include <clar.h>

////////////////////////////////////////////////////////////////////////////////////////////////////
// Fakes / Stubs

#include "stubs_bt_lock.h"
#include "stubs_logging.h"
#include "stubs_passert.h"

static bool s_last_driver_allow_cycling;
BTDeviceAddress s_last_driver_addr;
bool s_last_driver_addr_is_null;
void bt_driver_set_local_address(bool allow_cycling,
                                 const BTDeviceAddress *pinned_address) {
  s_last_driver_allow_cycling = allow_cycling;
  if (pinned_address) {
    s_last_driver_addr_is_null = false;
    s_last_driver_addr = *pinned_address;
  } else {
    s_last_driver_addr_is_null = true;
    memset(&s_last_driver_addr, 0x00, sizeof(s_last_driver_addr));
  }
  return cl_mock_type(void);
}

BTDeviceAddress s_last_bt_persist_pinned_addr;
bool s_last_bt_persist_pinned_addr_is_null;

bool bt_persistent_storage_get_ble_pinned_address(BTDeviceAddress *address_out) {
  if (s_last_bt_persist_pinned_addr_is_null) {
    return false;
  }
  if (address_out) {
    *address_out = s_last_bt_persist_pinned_addr;
  }
  return true;
}

bool bt_persistent_storage_set_ble_pinned_address(const BTDeviceAddress *addr) {
  if (addr) {
    s_last_bt_persist_pinned_addr_is_null = false;
    s_last_bt_persist_pinned_addr = *addr;
  } else {
    s_last_bt_persist_pinned_addr_is_null = true;
    memset(&s_last_bt_persist_pinned_addr, 0x00, sizeof(s_last_bt_persist_pinned_addr));
  }
  return cl_mock_type(bool);
}

bool bt_persistent_storage_has_pinned_ble_pairings(void) {
  return cl_mock_type(bool);
}

#define TEST_PINNED_ADDR_1 ((BTDeviceAddress){ .octets = { 0x11, 0x22, 0x33, 0x33, 0x44, 0x55 } })
#define TEST_PINNED_ADDR_2 ((BTDeviceAddress){ .octets = { 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff } })

bool bt_driver_id_generate_private_resolvable_address(BTDeviceAddress *root_pinned_address_out) {
  *root_pinned_address_out = TEST_PINNED_ADDR_1;
  return true;
}

////////////////////////////////////////////////////////////////////////////////////////////////////
// Tests

#define TEST_BONDING_ID ((BTBondingID) 1)

static void prv_init_no_pinnings_no_pinned_address(void) {
  cl_will_return(bt_persistent_storage_has_pinned_ble_pairings, false);
  cl_will_return(bt_persistent_storage_set_ble_pinned_address, true);
  cl_will_return(bt_driver_set_local_address, 0);
  s_last_bt_persist_pinned_addr_is_null = true;
  bt_local_addr_init();
}

static void prv_assert_driver_addr_is_null(void) {
  cl_assert_equal_b(s_last_driver_addr_is_null, true);
  cl_assert_equal_m(&s_last_driver_addr, &(BTDeviceAddress){}, sizeof(s_last_driver_addr));
}

static void prv_assert_driver_addr_is_addr1(void) {
  cl_assert_equal_b(s_last_driver_addr_is_null, false);
  cl_assert_equal_m(&s_last_driver_addr, &TEST_PINNED_ADDR_1, sizeof(s_last_driver_addr));
}

static void prv_assert_driver_addr_is_addr2(void) {
  cl_assert_equal_b(s_last_driver_addr_is_null, false);
  cl_assert_equal_m(&s_last_driver_addr, &TEST_PINNED_ADDR_2, sizeof(s_last_driver_addr));
}

void test_local_addr__initialize(void) {
  memset(&s_last_bt_persist_pinned_addr, 0xff, sizeof(s_last_bt_persist_pinned_addr));
  memset(&s_last_driver_addr, 0xff, sizeof(s_last_driver_addr));
  s_last_bt_persist_pinned_addr_is_null = false;
  s_last_driver_allow_cycling = false;
  s_last_driver_addr_is_null = false;
}

void test_local_addr__cleanup(void) {
}

void test_local_addr__init_generates_pinned_address_if_needed(void) {
  prv_init_no_pinnings_no_pinned_address();
  cl_assert_equal_b(false, s_last_bt_persist_pinned_addr_is_null);
  cl_assert_equal_m(&s_last_bt_persist_pinned_addr,
                    &TEST_PINNED_ADDR_1, sizeof(s_last_bt_persist_pinned_addr));
  cl_assert_equal_b(true, s_last_driver_allow_cycling);
}

void test_local_addr__init_loads_stored_pinned_address(void) {
  cl_will_return(bt_persistent_storage_has_pinned_ble_pairings, true);
  cl_will_return(bt_persistent_storage_set_ble_pinned_address, true);
  cl_will_return(bt_driver_set_local_address, 0);
  s_last_bt_persist_pinned_addr_is_null = false;
  s_last_bt_persist_pinned_addr = TEST_PINNED_ADDR_2;
  bt_local_addr_init();
  cl_assert_equal_b(false, s_last_driver_allow_cycling);
  prv_assert_driver_addr_is_addr2();
}

void test_local_addr__pause_resume(void) {
  prv_init_no_pinnings_no_pinned_address();

  // flip to make sure that bt_local_addr_pause_cycling() will set these again:
  s_last_driver_allow_cycling = true;
  s_last_driver_addr_is_null = false;

  cl_will_return(bt_driver_set_local_address, 0);
  bt_local_addr_pause_cycling();
  cl_assert_equal_b(false, s_last_driver_allow_cycling);
  // Check that it's using the pinned address that was generated upon initialization:
  prv_assert_driver_addr_is_addr1();

  bt_local_addr_pause_cycling();
  // Already paused, shouldn't result in a bt_driver_set_local_address() call.

  bt_local_addr_resume_cycling();
  // Still paused, shouldn't result in a bt_driver_set_local_address() call.

  // flip to make sure that bt_local_addr_pause_cycling() will set these again:
  s_last_driver_allow_cycling = true;
  s_last_driver_addr_is_null = false;

  cl_will_return(bt_driver_set_local_address, 0);
  bt_local_addr_resume_cycling();
  cl_assert_equal_b(true, s_last_driver_allow_cycling);
  prv_assert_driver_addr_is_null();
}

void test_local_addr__pin_unpin(void) {
  prv_init_no_pinnings_no_pinned_address();

  // Pin:
  cl_will_return(bt_driver_set_local_address, 0);
  cl_will_return(bt_persistent_storage_has_pinned_ble_pairings, true);
  bt_local_addr_pin(&TEST_PINNED_ADDR_1);
  bt_local_addr_handle_bonding_change(TEST_BONDING_ID, BtPersistBondingOpDidAdd);
  cl_assert_equal_b(false, s_last_driver_allow_cycling);
  prv_assert_driver_addr_is_addr1();

  // Unpin (done implicitly when bonding is removed):
  cl_will_return(bt_persistent_storage_has_pinned_ble_pairings, false);
  cl_will_return(bt_driver_set_local_address, 0);
  bt_local_addr_handle_bonding_change(TEST_BONDING_ID, BtPersistBondingOpWillDelete);
  cl_assert_equal_b(true, s_last_driver_allow_cycling);
  prv_assert_driver_addr_is_null();
}

void test_local_addr__pause_then_pin(void) {
  prv_init_no_pinnings_no_pinned_address();

  // Pause:
  cl_will_return(bt_driver_set_local_address, 0);
  bt_local_addr_pause_cycling();
  cl_assert_equal_b(false, s_last_driver_allow_cycling);
  prv_assert_driver_addr_is_addr1();

  // Pin, expect pinned address to be sent to BT driver:
  cl_will_return(bt_driver_set_local_address, 0);
  cl_will_return(bt_persistent_storage_has_pinned_ble_pairings, true);
  bt_local_addr_pin(&TEST_PINNED_ADDR_1);
  bt_local_addr_handle_bonding_change(TEST_BONDING_ID, BtPersistBondingOpDidAdd);
  cl_assert_equal_b(false, s_last_driver_allow_cycling);
  prv_assert_driver_addr_is_addr1();
}
