/*
 * 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 "services/common/bluetooth/bluetooth_persistent_storage.h"
#include "services/common/comm_session/session_internal.h"
#include "services/common/comm_session/session_remote_os.h"
#include "services/common/comm_session/session_remote_version.h"
#include "util/net.h"

static CommSession s_session;

extern void session_remote_version_protocol_msg_callback(CommSession *session,
                                                         const uint8_t *data, size_t length);

// Fakes & Stubs
////////////////////////////////////////////////////////////////////////////////////////////////////

#include "fake_pbl_malloc.h"
#include "fake_events.h"
#include "fake_new_timer.h"

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

void bt_driver_reconnect_notify_platform_bitfield(uint32_t p) {}

static bool s_session_is_system;
bool comm_session_is_system(CommSession *session) {
  return s_session_is_system;
}

static bool s_session_is_valid;
bool comm_session_is_valid(const CommSession *session) {
  return (session == &s_session) && s_session_is_valid;
}

static bool s_data_sent;
bool comm_session_send_data(CommSession *session, uint16_t endpoint_id,
                            const uint8_t* data, size_t length, uint32_t timeout_ms) {
  // The request is just a single 0x00 byte to endpoint 0x11:
  cl_assert_equal_i(endpoint_id, 0x11);
  cl_assert_equal_i(length, 1);
  cl_assert_equal_i(*data, 0x00 /* Command ID 'Request' */);
  s_data_sent = true;
  return true;
}

CommSessionCapability s_capability_flags;
void comm_session_set_capabilities(CommSession *session, CommSessionCapability capability_flags) {
  cl_assert_equal_p(session, &s_session);
  s_capability_flags = capability_flags;
}

BTBondingID bt_persistent_storage_store_bt_classic_pairing(BTDeviceAddress *address, SM128BitKey *link_key,
                                                    char *name, uint8_t *platform_bits) {
  return 1;
}

// Helpers
////////////////////////////////////////////////////////////////////////////////////////////////////

static void (*s_launcher_task_callback)(void *data);
static void *s_launcher_task_callback_data;

void launcher_task_add_callback(void (*callback)(void *data), void *data) {
  // Simple fake, can only handle one call
  cl_assert_equal_p(s_launcher_task_callback, NULL);

  s_launcher_task_callback = callback;
  s_launcher_task_callback_data = data;
}

static void prv_process_and_assert_sent_request_data(bool expect_request_data_sent) {
  s_data_sent = false;
  s_launcher_task_callback(s_launcher_task_callback_data);
  cl_assert_equal_b(s_data_sent, expect_request_data_sent);
}

static void prv_receive_v3_response(uint8_t major, uint8_t minor, uint8_t bugfix,
                                    CommSessionCapability protocol_capabilities) {
  union {
    CommSessionCapability protocol_capabilities;
    uint8_t byte[8];  // Little-endian!
  } capabilities;
  capabilities.protocol_capabilities = protocol_capabilities;

  uint8_t response_data[] = {
    0x01,                   // Command ID 'Response'
    0x00, 0x00, 0x00, 0x00, // Deprecated library version
    0x00, 0x00, 0x00, 0x00, // Deprecated capabilities
    0x00, 0x00, 0x00, 0x00, // Platform (OS) bitfield
    0x02,                   // Response version
    major, minor, bugfix,
    capabilities.byte[0],
    capabilities.byte[1],
    capabilities.byte[2],
    capabilities.byte[3],
    capabilities.byte[4],
    capabilities.byte[5],
    capabilities.byte[7],
  };
  session_remote_version_protocol_msg_callback(&s_session, response_data, sizeof(response_data));
}

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

#define MAX_ATTEMPTS (3)

void test_session_remote_version__initialize(void) {
  fake_event_init();
  s_session = (CommSession) {};
  s_data_sent = false;
  s_session_is_valid = true;
  s_session_is_system = true;
  s_capability_flags = 0;
  session_remote_version_start_requests(&s_session);
}

void test_session_remote_version__cleanup(void) {
  s_launcher_task_callback = NULL;
  s_launcher_task_callback_data = NULL;

  fake_pbl_malloc_clear_tracking();
}

void test_session_remote_version__receive_invalid_msg(void) {
  uint8_t invalid_msg = 0xff;
  session_remote_version_protocol_msg_callback(&s_session, &invalid_msg, sizeof(invalid_msg));
  cl_assert_equal_i(fake_event_get_count(), 0);
}

static const CommSessionCapability s_expected_capabilities =
    (CommSessionAppMessage8kSupport |
     CommSessionVoiceApiSupport);

void test_session_remote_version__system_session(void) {
  s_session_is_system = true;
  prv_receive_v3_response(3, 2, 1, s_expected_capabilities);
  // Triggers PEBBLE_REMOTE_APP_INFO_EVENT:
  cl_assert_equal_i(fake_event_get_count(), 1);
  PebbleEvent e = fake_event_get_last();
  cl_assert_equal_i(e.type, PEBBLE_REMOTE_APP_INFO_EVENT);
  cl_assert_equal_i(s_capability_flags, s_expected_capabilities);
}

void test_session_remote_version__app_session(void) {
  s_session_is_system = false;
  prv_receive_v3_response(3, 2, 1, s_expected_capabilities);
  // Triggers no event:
  cl_assert_equal_i(fake_event_get_count(), 0);
  cl_assert_equal_i(s_capability_flags, s_expected_capabilities);
}
