/*
 * 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 "applib/rockyjs/rocky_res.h"
#include "services/common/comm_session/session.h"
#include "services/normal/app_fetch_endpoint.h"
#include "system/logging.h"
#include "util/attributes.h"


#include <stdio.h>

// Fakes
////////////////////////////////////
#include "fake_events.h"
#include "fake_new_timer.h"
#include "fake_pbl_malloc.h"
#include "fake_session.h"
#include "fake_system_task.h"

// Stubs
////////////////////////////////////
#include "stubs_app_cache.h"
#include "stubs_bt_lock.h"
#include "stubs_hexdump.h"
#include "stubs_logging.h"
#include "stubs_passert.h"
#include "stubs_rand_ptr.h"
#include "stubs_queue.h"

typedef struct {} EventServiceInfo;

void app_event_service_subscribe(EventServiceInfo * service_info) {
  return;
}

void put_bytes_cancel(void) {
}

void put_bytes_expect_init(uint32_t timeout_ms) {
}

void app_storage_delete_bank(uint32_t bank) {
}

const PebbleProcessMd *app_install_get_md(AppInstallId id, bool worker) {
  return NULL;
}

void app_install_release_md(const PebbleProcessMd *md) {
}

RockyResourceValidation s_rocky_app_validate_resources__result;
RockyResourceValidation rocky_app_validate_resources(const PebbleProcessMd *md) {
  return s_rocky_app_validate_resources__result;
}

typedef struct PACKED {
  uint16_t length;
  uint16_t endpoint_id;
} PebbleProtocolHeader;

enum {
  APP_FETCH_INSTALL_COMMAND = 0x01,
} AppFetchCommand;

enum {
  APP_FETCH_INSTALL_RESPONSE = 0x01,
} AppFetchResponse;

typedef struct AppFetchData {
  CommSession *session;
  size_t length;
  uint8_t data[];
} AppFetchData;

typedef struct PACKED {
  uint8_t command;
  Uuid uuid;
  uint32_t app_id;
} AppFetchRequest;

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

static const uint16_t APP_FETCH_ENDPOINT_ID = 6001;

static const uint32_t app_id_1 = 42;
static const Uuid uuid_1 = {0x6b, 0xf6, 0x21, 0x5b, 0xc9, 0x7f, 0x40, 0x9e,
                                 0x8c, 0x31, 0x4f, 0x55, 0x65, 0x72, 0x22, 0xb4};

Transport *s_transport;

/* Start of test */

void test_app_fetch_endpoint__initialize(void) {
  fake_comm_session_init();
  s_transport = fake_transport_create(TransportDestinationSystem, NULL, NULL);
  fake_transport_set_connected(s_transport, true /* connected */);
}

void test_app_fetch_endpoint__cleanup(void) {
  fake_transport_destroy(s_transport);
  s_transport = NULL;
  fake_comm_session_cleanup();
  fake_system_task_callbacks_cleanup();
}

/*************************************
 * Checking for valid INSERT command *
 *************************************/

 static const uint8_t s_app_fetch_success[] = {
   // Message Header
   0x01,                     // APP_FETCH_INSTALL_RESPONSE
   0x01,                     // ACK: 0x01
 };

static void prv_check_valid_app_fetch_request(uint16_t endpoint_id, const uint8_t* data,
    unsigned int data_length) {

  // AppFetchRequest request = *(AppFetchRequest *)data;
  PBL_LOG(LOG_LEVEL_DEBUG, "sizeof: %lu length: %u", sizeof(AppFetchRequest), data_length);
  AppFetchRequest request = *(AppFetchRequest *)data;

  cl_assert_equal_i(endpoint_id, APP_FETCH_ENDPOINT_ID);
  cl_assert_equal_i(request.command, APP_FETCH_INSTALL_COMMAND);
  cl_assert_equal_i(request.app_id, app_id_1);
  cl_assert_equal_b(true, uuid_equal(&request.uuid, &uuid_1));

  // if anything is planning on being done relating to this ACK, then it will be done after this
  app_fetch_protocol_msg_callback(comm_session_get_system_session(),
                                  s_app_fetch_success, sizeof(s_app_fetch_success));
}

void test_app_fetch_endpoint__app_fetch_binaries(void) {
  // set the function that will validate the data in the send buffer
  fake_transport_set_sent_cb(s_transport, prv_check_valid_app_fetch_request);

  // queue of system task to send an app_fetch request
  app_fetch_binaries(&uuid_1, app_id_1, 0);

  // process system task events
  fake_system_task_callbacks_invoke_pending();
  fake_comm_session_process_send_next();

  // if anything is planning on being done relating to the ACK sent back from phone,
  // then it should be checked here.
}

static void prv_fetch_complete_app() {
  app_fetch_binaries(&uuid_1, app_id_1, false);
  app_fetch_put_bytes_event_handler(&(PebblePutBytesEvent){
    .type = PebblePutBytesEventTypeCleanup,
    .object_type = ObjectAppResources,
    .has_cookie = true,
  });
  fake_system_task_callbacks_invoke_pending();
  app_fetch_put_bytes_event_handler(&(PebblePutBytesEvent){
    .type = PebblePutBytesEventTypeCleanup,
    .object_type = ObjectWatchApp,
    .has_cookie = true,
  });
  fake_system_task_callbacks_invoke_pending();
}

void test_app_fetch_endpoint__no_incompatible_js(void) {
  s_rocky_app_validate_resources__result = RockyResourceValidation_Valid;
  prv_fetch_complete_app();

  const PebbleEvent e = fake_event_get_last();
  cl_assert_equal_i(PEBBLE_APP_FETCH_EVENT, e.type);
  cl_assert_equal_i(AppFetchEventTypeFinish, e.app_fetch.type);
}

void test_app_fetch_endpoint__incompatible_js(void) {
  s_rocky_app_validate_resources__result = RockyResourceValidation_Invalid;
  prv_fetch_complete_app();

  const PebbleEvent e = fake_event_get_last();
  cl_assert_equal_i(PEBBLE_APP_FETCH_EVENT, e.type);
  cl_assert_equal_i(AppFetchEventTypeError, e.app_fetch.type);
  cl_assert_equal_i(AppFetchResultIncompatibleJSFailure, e.app_fetch.error_code);
}
