/*
 * 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 "clar.h"

#include "comm/ble/gap_le_connection.h"
#include "comm/bt_conn_mgr.h"
#include "comm/bt_conn_mgr_impl.h"
#include "services/common/regular_timer.h"

// Fakes
#include "fake_gap_le_connect_params.h"
#include "fake_new_timer.h"
#include "fake_rtc.h"
#include "fake_system_task.h"
#include "fake_pbl_malloc.h"

// Stubs
#include "stubs_bluetopia_interface.h"
#include "stubs_bt_lock.h"
#include "stubs_logging.h"
#include "stubs_mutex.h"
#include "stubs_passert.h"

extern void conn_mgr_handle_desired_state_granted(GAPLEConnection *hdl,
                                                  ResponseTimeState granted_state);

// Stubs
/////
bool gap_le_connection_is_valid(const GAPLEConnection *conn) {
  return (conn != NULL);
}

GAPLEConnection *gap_le_connection_any(void) {
  return NULL;
}

void prv_regular_timer_spend_seconds(uint32_t seconds) {
  for (uint32_t i = 0; i < seconds; ++i) {
    fake_rtc_increment_ticks(RTC_TICKS_HZ);
    regular_timer_fire_seconds(1);

    // bt_conn_mgr offloads the callback to KernelBG
    fake_system_task_callbacks_invoke_pending();
  }
}

void launcher_task_add_callback(void (*callback)(void *data), void *data) {
  callback(data);
}

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

static int s_granted_count;
static GAPLEConnection s_hdl;

void test_bt_conn_mgr__initialize(void) {
  s_granted_count = 0;
  fake_rtc_init(0, 0);
  regular_timer_init();
  fake_gap_le_connect_params_init();
  s_hdl.conn_mgr_info = bt_conn_mgr_info_init();
}

void test_bt_conn_mgr__cleanup(void) {
  regular_timer_deinit();
}

void test_bt_conn_mgr__ble_latency_mgr(void) {
  // 1 consumer at fastest rate should result in fastest rate getting scheduled
  conn_mgr_set_ble_conn_response_time(
      &s_hdl, BtConsumerLeServiceDiscovery, ResponseTimeMin, 100);

  uint16_t secs_to_wait;
  ResponseTimeState state;

  state = conn_mgr_get_latency_for_le_connection(&s_hdl, &secs_to_wait);
  cl_assert_equal_i(state, ResponseTimeMin);
  cl_assert_equal_i(secs_to_wait, 100);
  cl_assert_equal_i(fake_gap_le_connect_params_get_last_requested(), ResponseTimeMin);

  // another consumer at lower rate should not have any effect
  fake_gap_le_connect_params_reset_last_requested();
  conn_mgr_set_ble_conn_response_time(
      &s_hdl, BtConsumerUnitTests, ResponseTimeMiddle, 30);
  state = conn_mgr_get_latency_for_le_connection(&s_hdl, &secs_to_wait);
  cl_assert_equal_i(state, ResponseTimeMin);
  cl_assert_equal_i(secs_to_wait, 100);
  cl_assert_equal_i(fake_gap_le_connect_params_get_last_requested(), ResponseTimeInvalid);

  // removing the fastest consumer should result in the next fastest being scheduled, but only
  // after an "inactivity timeout":
  conn_mgr_set_ble_conn_response_time(
      &s_hdl, BtConsumerLeServiceDiscovery, ResponseTimeMax, 0);

  state = conn_mgr_get_latency_for_le_connection(&s_hdl, &secs_to_wait);
  cl_assert_equal_i(state, ResponseTimeMin);
  cl_assert_equal_i(secs_to_wait, BT_CONN_MGR_INACTIVITY_TIMEOUT_SECS);
  cl_assert_equal_i(fake_gap_le_connect_params_get_last_requested(), ResponseTimeInvalid);

  prv_regular_timer_spend_seconds(BT_CONN_MGR_INACTIVITY_TIMEOUT_SECS);

  state = conn_mgr_get_latency_for_le_connection(&s_hdl, &secs_to_wait);
  cl_assert_equal_i(state, ResponseTimeMiddle);
  cl_assert_equal_i(secs_to_wait, 30 - BT_CONN_MGR_INACTIVITY_TIMEOUT_SECS);
  cl_assert_equal_i(fake_gap_le_connect_params_get_last_requested(), ResponseTimeMiddle);

  // removing all consumers we should fall back to slowest interval, but only
  // after an "inactivity timeout":
  fake_gap_le_connect_params_reset_last_requested();
  conn_mgr_set_ble_conn_response_time(
      &s_hdl, BtConsumerUnitTests, ResponseTimeMax, 0);

  prv_regular_timer_spend_seconds(BT_CONN_MGR_INACTIVITY_TIMEOUT_SECS);
  state = conn_mgr_get_latency_for_le_connection(&s_hdl, &secs_to_wait);
  cl_assert_equal_i(state, ResponseTimeMax);
  cl_assert_equal_i(fake_gap_le_connect_params_get_last_requested(), ResponseTimeMax);

  // if nothing else is scheduled, middle rate should get picked up right away
  conn_mgr_set_ble_conn_response_time(
      &s_hdl, BtConsumerUnitTests, ResponseTimeMiddle, 30);
  state = conn_mgr_get_latency_for_le_connection(&s_hdl, &secs_to_wait);
  cl_assert_equal_i(state, ResponseTimeMiddle);
  cl_assert_equal_i(secs_to_wait, 30);
  cl_assert_equal_i(fake_gap_le_connect_params_get_last_requested(), ResponseTimeMiddle);

  // higher rate should take over lower rate already scheduled
  conn_mgr_set_ble_conn_response_time(
      &s_hdl, BtConsumerLeServiceDiscovery, ResponseTimeMin, 25);
  state = conn_mgr_get_latency_for_le_connection(&s_hdl, &secs_to_wait);
  cl_assert_equal_i(state, ResponseTimeMin);
  cl_assert_equal_i(secs_to_wait, 25);
  cl_assert_equal_i(fake_gap_le_connect_params_get_last_requested(), ResponseTimeMin);

  // two requests at same high rate, longest time should be selected as timeout
  fake_gap_le_connect_params_reset_last_requested();
  conn_mgr_set_ble_conn_response_time(
      &s_hdl, BtConsumerUnitTests, ResponseTimeMin, 250);
  state = conn_mgr_get_latency_for_le_connection(&s_hdl, &secs_to_wait);
  cl_assert_equal_i(state, ResponseTimeMin);
  cl_assert_equal_i(secs_to_wait, 250);
  cl_assert_equal_i(fake_gap_le_connect_params_get_last_requested(), ResponseTimeInvalid);

  bt_conn_mgr_info_deinit(&s_hdl.conn_mgr_info);
}

static void prv_granted_handler(void) {
  ++s_granted_count;
}

void test_bt_conn_mgr__granted_handler_request_max_no_existing_node(void) {
  fake_gap_le_connect_params_set_actual_state(ResponseTimeMax);
  conn_mgr_set_ble_conn_response_time_ext(&s_hdl, BtConsumerLeServiceDiscovery, ResponseTimeMax, 1,
                                          prv_granted_handler);
  // Expect granted handler to be called immediately:
  cl_assert_equal_i(s_granted_count, 1);
}

void test_bt_conn_mgr__granted_handler_request_existing(void) {
  fake_gap_le_connect_params_set_actual_state(ResponseTimeMax);
  conn_mgr_set_ble_conn_response_time_ext(&s_hdl, BtConsumerLeServiceDiscovery, ResponseTimeMin, 1,
                                          prv_granted_handler);
  cl_assert_equal_i(s_granted_count, 0);

  // Simulate that the requested state takes effect:
  fake_gap_le_connect_params_set_actual_state(ResponseTimeMin);
  conn_mgr_handle_desired_state_granted(&s_hdl, ResponseTimeMin);
  cl_assert_equal_i(s_granted_count, 1);

  conn_mgr_set_ble_conn_response_time_ext(&s_hdl, BtConsumerLeServiceDiscovery, ResponseTimeMin, 1,
                                          prv_granted_handler);
  cl_assert_equal_i(s_granted_count, 2);

  conn_mgr_set_ble_conn_response_time_ext(&s_hdl, BtConsumerLeServiceDiscovery, ResponseTimeMiddle,
                                          1, prv_granted_handler);
  cl_assert_equal_i(s_granted_count, 3);

  conn_mgr_set_ble_conn_response_time_ext(&s_hdl, BtConsumerLeServiceDiscovery, ResponseTimeMax,
                                          1, prv_granted_handler);
  cl_assert_equal_i(s_granted_count, 4);
}

void test_bt_conn_mgr__request_max_time_while_no_requests_are_running(void) {
  uint16_t secs_to_wait;
  ResponseTimeState state;

  // Always start off with ResponseTimeMax:
  state = conn_mgr_get_latency_for_le_connection(&s_hdl, &secs_to_wait);
  cl_assert_equal_i(state, ResponseTimeMax);

  // Requesting ResponseTimeMax should have no effect:
  conn_mgr_set_ble_conn_response_time(&s_hdl, BtConsumerLeServiceDiscovery, ResponseTimeMax, 1);
  state = conn_mgr_get_latency_for_le_connection(&s_hdl, &secs_to_wait);
  cl_assert_equal_i(state, ResponseTimeMax);

  // Not even after waiting 10 seconds:
  prv_regular_timer_spend_seconds(10);
  state = conn_mgr_get_latency_for_le_connection(&s_hdl, &secs_to_wait);
  cl_assert_equal_i(state, ResponseTimeMax);

  bt_conn_mgr_info_deinit(&s_hdl.conn_mgr_info);
}