/*
 * 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 "util/uuid.h"
#include "services/normal/filesystem/pfs.h"
#include "services/common/regular_timer.h"
#include "services/normal/blob_db/pin_db.h"
#include "services/normal/timeline/timeline.h"
#include "apps/system_apps/timeline/timeline_model.h"
#include "util/size.h"

// Fixture
////////////////////////////////////////////////////////////////

// Fakes
////////////////////////////////////////////////////////////////
#include "fake_spi_flash.h"
#include "fake_pbl_malloc.h"
#include "fake_rtc.h"

static TimezoneInfo tz = {
  .tm_gmtoff = -8 * 60 * 60, // PST
};

// Stubs
////////////////////////////////////////////////////////////////
#include "stubs_analytics.h"
#include "stubs_app_cache.h"
#include "stubs_app_install_manager.h"
#include "stubs_app_manager.h"
#include "stubs_blob_db.h"
#include "stubs_blob_db_sync.h"
#include "stubs_blob_db_sync_util.h"
#include "stubs_calendar.h"
#include "stubs_event_service_client.h"
#include "stubs_events.h"
#include "stubs_fonts.h"
#include "stubs_hexdump.h"
#include "stubs_i18n.h"
#include "stubs_layout_layer.h"
#include "stubs_logging.h"
#include "stubs_modal_manager.h"
#include "stubs_mutex.h"
#include "stubs_passert.h"
#include "stubs_pebble_tasks.h"
#include "stubs_prompt.h"
#include "stubs_rand_ptr.h"
#include "stubs_regular_timer.h"
#include "stubs_resources.h"
#include "stubs_session.h"
#include "stubs_sleep.h"
#include "stubs_syscalls.h"
#include "stubs_task_watchdog.h"
#include "stubs_window_stack.h"

void ancs_notifications_enable_bulk_action_mode(bool enable) {
  return;
}

bool ancs_notifications_is_bulk_action_mode_enabled(void) {
  return false;
}

status_t reminder_db_delete_with_parent(const TimelineItemId *id) {
  return S_SUCCESS;
}

void timeline_action_endpoint_invoke_action(const Uuid *id,
    uint8_t action_id, AttributeList *attributes) {
}

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

void timeline_pin_window_push_modal(TimelineItem *item) {
}

const PebbleProcessMd *timeline_get_app_info(void) {
  return NULL;
}

PebblePhoneCaller* phone_call_util_create_caller(const char *number, const char *name) {
  return NULL;
}

void ancs_perform_action(uint32_t notification_uid, uint8_t action_id) {
}

void notifications_handle_notification_action_result(
    PebbleSysNotificationActionResult *action_result) {
}

void notification_storage_set_status(const Uuid *id, uint8_t status) {
}

void notifications_handle_notification_acted_upon(Uuid *notification_id) {
  return;
}

// Data
/////////////////////////
static TimelineItem s_items[] = {
  {
    .header = { // [0]
        .id = {0x6b, 0xf6, 0x21, 0x5b, 0xc9, 0x7f, 0x40, 0x9e,
                 0x8c, 0x31, 0x4f, 0x55, 0x65, 0x72, 0x22, 0xb1},
        .parent_id = {0},
        .timestamp =  1421178061, // Tue Jan 13 11:41:01 2015 PST
        .duration = 1,
        .type = TimelineItemTypePin,
        .flags = 0,
        .layout = LayoutIdTest,
    },
    .attr_list = {
        .num_attributes = 0,
        .attributes = NULL,
    },
    .action_group = {
        .num_actions = 0,
        .actions = NULL,
    },
    .allocated_buffer = NULL,
  }, {
    .header = { // [1]
        .id = {0x6b, 0xf6, 0x21, 0x5b, 0xc9, 0x7f, 0x40, 0x9e,
                 0x8c, 0x31, 0x4f, 0x55, 0x65, 0x72, 0x22, 0xb2},
        .parent_id = {0},
        .timestamp =  1421183642, // Tue Jan 13 13:14:02 2015 PST
        .duration = 10,
        .type = TimelineItemTypePin,
        .flags = 0,
        .layout = LayoutIdTest,
    },
    .attr_list = {
        .num_attributes = 0,
        .attributes = NULL,
    },
    .action_group = {
        .num_actions = 0,
        .actions = NULL,
    },
    .allocated_buffer = NULL,
  }, {
    .header = { // [2]
        .id = {0x6b, 0xf6, 0x21, 0x5b, 0xc9, 0x7f, 0x40, 0x9e,
                 0x8c, 0x31, 0x4f, 0x55, 0x65, 0x72, 0x22, 0xb3},
        .parent_id = {0},
        .timestamp =  1421183642, // Tue Jan 13 13:14:02 2015 PST
        .duration = 2,
        .type = TimelineItemTypePin,
        .flags = 0,
        .layout = LayoutIdTest,
    },
    .attr_list = {
        .num_attributes = 0,
        .attributes = NULL,
    },
    .action_group = {
        .num_actions = 0,
        .actions = NULL,
    },
    .allocated_buffer = NULL,
  }, {
    .header = { // [3]
        .id = {0x6b, 0xf6, 0x21, 0x5b, 0xc9, 0x7f, 0x40, 0x9e,
                 0x8c, 0x31, 0x4f, 0x55, 0x65, 0x72, 0x22, 0xb4},
        .parent_id = {0},
        .timestamp =  1421183642, // Tue Jan 13 13:14:02 2015 PST
        .duration = 30,
        .type = TimelineItemTypePin,
        .flags = 0,
        .layout = LayoutIdTest,
    },
    .attr_list = {
        .num_attributes = 0,
        .attributes = NULL,
    },
    .action_group = {
        .num_actions = 0,
        .actions = NULL,
    },
    .allocated_buffer = NULL,
  }, {
    .header = { // [4]
        .id = {0x6b, 0xf6, 0x21, 0x5b, 0xc9, 0x7f, 0x40, 0x9e,
                 0x8c, 0x31, 0x4f, 0x55, 0x65, 0x72, 0x22, 0xb5},
        .parent_id = {0},
        .timestamp =  1421178061, // Tue Jan 13 11:41:01 2015 PST
        .duration = 5,
        .type = TimelineItemTypePin,
        .flags = 0,
        .layout = LayoutIdTest,
    },
    .attr_list = {
        .num_attributes = 0,
        .attributes = NULL,
    },
    .action_group = {
        .num_actions = 0,
        .actions = NULL,
    },
    .allocated_buffer = NULL,
  }, {
    .header = { // [5]
        .id = {0x6b, 0xf6, 0x21, 0x5b, 0xc9, 0x7f, 0x40, 0x9e,
                 0x8c, 0x31, 0x4f, 0x55, 0x65, 0x72, 0x22, 0xb6},
        .parent_id = {0},
        .timestamp =  1421183462, // Tue Jan 13 13:11:02 PST 2015
        .duration = 4,
        .type = TimelineItemTypePin,
        .flags = 0,
        .layout = LayoutIdTest,
    },
    .attr_list = {
        .num_attributes = 0,
        .attributes = NULL,
    },
    .action_group = {
        .num_actions = 0,
        .actions = NULL,
    },
    .allocated_buffer = NULL,
  }
};

// Setup
/////////////////////////

void test_timeline_model__initialize(void) {
  fake_spi_flash_init(0, 0x1000000);
  fake_rtc_init(0, 0);
  pfs_init(false);
  // Note: creating a settings file is going to result in one malloc for the FD name
  pin_db_init();
  time_util_update_timezone(&tz);
  fake_pbl_malloc_clear_tracking();
  for (unsigned int i = 0; i < ARRAY_LENGTH(s_items); ++i) {
    cl_assert_equal_i(pin_db_insert_item(&s_items[i]), 0);
  }
  cl_assert_equal_i(fake_pbl_malloc_num_net_allocs(), 0);
}

void test_timeline_model__cleanup(void) {
}

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

static int s_correct_order[] = {0, 4, 5, 2, 1, 3};

void test_timeline_model__future(void) {
  TimelineModel model = {0};
  model.direction = TimelineIterDirectionFuture;
  time_t first_time = 1421178000;
  // Note: 1421178000 = Tue Jan 13 11:40:00 PST 2015
  timeline_model_init(first_time, &model);

  cl_assert_equal_i(timeline_model_get_num_items(), 2);
  cl_assert(uuid_equal(&s_items[s_correct_order[0]].header.id,
      &timeline_model_get_iter_state(0)->pin.header.id));
  cl_assert(uuid_equal(&s_items[s_correct_order[1]].header.id,
      &timeline_model_get_iter_state(1)->pin.header.id));
  cl_assert(timeline_model_get_iter_state(0) == timeline_model_get_iter_state_with_timeline_idx(0));
  cl_assert(timeline_model_get_iter_state(1) == timeline_model_get_iter_state_with_timeline_idx(1));

  int new_idx;
  bool has_next;
  cl_assert(timeline_model_iter_next(&new_idx, &has_next));
  cl_assert(has_next);
  cl_assert_equal_i(new_idx, 2);
  cl_assert_equal_i(timeline_model_get_num_items(), 2);
  cl_assert(uuid_equal(&s_items[s_correct_order[0]].header.id,
      &timeline_model_get_iter_state(-1)->pin.header.id));
  cl_assert(uuid_equal(&s_items[s_correct_order[1]].header.id,
      &timeline_model_get_iter_state(0)->pin.header.id));
  cl_assert(uuid_equal(&s_items[s_correct_order[2]].header.id,
      &timeline_model_get_iter_state(1)->pin.header.id));
  cl_assert(timeline_model_get_iter_state(0) == timeline_model_get_iter_state_with_timeline_idx(1));
  cl_assert(timeline_model_get_iter_state(1) == timeline_model_get_iter_state_with_timeline_idx(2));

  cl_assert(timeline_model_iter_next(&new_idx, &has_next));
  cl_assert(has_next);
  cl_assert_equal_i(new_idx, 3);
  cl_assert_equal_i(timeline_model_get_num_items(), 2);
  cl_assert(uuid_equal(&s_items[s_correct_order[1]].header.id,
      &timeline_model_get_iter_state(-1)->pin.header.id));
  cl_assert(uuid_equal(&s_items[s_correct_order[2]].header.id,
      &timeline_model_get_iter_state(0)->pin.header.id));
  cl_assert(uuid_equal(&s_items[s_correct_order[3]].header.id,
      &timeline_model_get_iter_state(1)->pin.header.id));
  cl_assert(timeline_model_get_iter_state(0) == timeline_model_get_iter_state_with_timeline_idx(2));
  cl_assert(timeline_model_get_iter_state(1) == timeline_model_get_iter_state_with_timeline_idx(3));

  cl_assert(timeline_model_iter_next(&new_idx, &has_next));
  cl_assert(has_next);
  cl_assert_equal_i(new_idx, 4);
  cl_assert_equal_i(timeline_model_get_num_items(), 2);
  cl_assert(uuid_equal(&s_items[s_correct_order[2]].header.id,
      &timeline_model_get_iter_state(-1)->pin.header.id));
  cl_assert(uuid_equal(&s_items[s_correct_order[3]].header.id,
      &timeline_model_get_iter_state(0)->pin.header.id));
  cl_assert(uuid_equal(&s_items[s_correct_order[4]].header.id,
      &timeline_model_get_iter_state(1)->pin.header.id));
  cl_assert(timeline_model_get_iter_state(0) == timeline_model_get_iter_state_with_timeline_idx(3));
  cl_assert(timeline_model_get_iter_state(1) == timeline_model_get_iter_state_with_timeline_idx(4));

  cl_assert(timeline_model_iter_next(&new_idx, &has_next));
  cl_assert(has_next);
  cl_assert_equal_i(new_idx, 5);
  cl_assert_equal_i(timeline_model_get_num_items(), 2);
  cl_assert(uuid_equal(&s_items[s_correct_order[3]].header.id,
      &timeline_model_get_iter_state(-1)->pin.header.id));
  cl_assert(uuid_equal(&s_items[s_correct_order[4]].header.id,
      &timeline_model_get_iter_state(0)->pin.header.id));
  cl_assert(uuid_equal(&s_items[s_correct_order[5]].header.id,
      &timeline_model_get_iter_state(1)->pin.header.id));
  cl_assert(timeline_model_get_iter_state(0) == timeline_model_get_iter_state_with_timeline_idx(4));
  cl_assert(timeline_model_get_iter_state(1) == timeline_model_get_iter_state_with_timeline_idx(5));

  cl_assert(timeline_model_iter_next(&new_idx, &has_next));
  cl_assert(!has_next);
  cl_assert_equal_i(timeline_model_get_num_items(), 1);
  cl_assert(uuid_equal(&s_items[s_correct_order[4]].header.id,
      &timeline_model_get_iter_state(-1)->pin.header.id));
  cl_assert(uuid_equal(&s_items[s_correct_order[5]].header.id,
      &timeline_model_get_iter_state(0)->pin.header.id));

  cl_assert(!timeline_model_iter_next(&new_idx, &has_next));
}

void test_timeline_model__and_back(void) {
  TimelineModel model = {0};
  model.direction = TimelineIterDirectionFuture;
  time_t first_time = 1421178000;
  // Note: 1421178000 = Tue Jan 13 11:40:00 PST 2015
  timeline_model_init(first_time, &model);

  cl_assert(timeline_model_iter_next(NULL, NULL));
  cl_assert(timeline_model_iter_next(NULL, NULL));
  cl_assert(timeline_model_iter_next(NULL, NULL));
  cl_assert(timeline_model_iter_next(NULL, NULL));
  cl_assert(timeline_model_iter_next(NULL, NULL));
  cl_assert(!timeline_model_iter_next(NULL, NULL));

  int new_idx;
  cl_assert(timeline_model_iter_prev(&new_idx, NULL));
  cl_assert_equal_i(new_idx, 4);
  cl_assert_equal_i(timeline_model_get_num_items(), 2);
  cl_assert(uuid_equal(&s_items[s_correct_order[4]].header.id,
      &timeline_model_get_iter_state(0)->pin.header.id));
  cl_assert(uuid_equal(&s_items[s_correct_order[5]].header.id,
      &timeline_model_get_iter_state(1)->pin.header.id));
  cl_assert(timeline_model_get_iter_state(0) == timeline_model_get_iter_state_with_timeline_idx(4));

  cl_assert(timeline_model_iter_prev(&new_idx, NULL));
  cl_assert_equal_i(new_idx, 3);
  cl_assert_equal_i(timeline_model_get_num_items(), 2);
  cl_assert(uuid_equal(&s_items[s_correct_order[3]].header.id,
      &timeline_model_get_iter_state(0)->pin.header.id));
  cl_assert(uuid_equal(&s_items[s_correct_order[4]].header.id,
      &timeline_model_get_iter_state(1)->pin.header.id));
  cl_assert(uuid_equal(&s_items[s_correct_order[5]].header.id,
      &timeline_model_get_iter_state(2)->pin.header.id));
  cl_assert(timeline_model_get_iter_state(0) == timeline_model_get_iter_state_with_timeline_idx(3));
  cl_assert(timeline_model_get_iter_state(1) == timeline_model_get_iter_state_with_timeline_idx(4));
  cl_assert(timeline_model_get_iter_state(2) == timeline_model_get_iter_state_with_timeline_idx(5));

  cl_assert(timeline_model_iter_prev(&new_idx, NULL));
  cl_assert_equal_i(new_idx, 2);
  cl_assert_equal_i(timeline_model_get_num_items(), 2);
  cl_assert(uuid_equal(&s_items[s_correct_order[2]].header.id,
      &timeline_model_get_iter_state(0)->pin.header.id));
  cl_assert(uuid_equal(&s_items[s_correct_order[3]].header.id,
      &timeline_model_get_iter_state(1)->pin.header.id));
  cl_assert(uuid_equal(&s_items[s_correct_order[4]].header.id,
      &timeline_model_get_iter_state(2)->pin.header.id));
  cl_assert(timeline_model_get_iter_state(0) == timeline_model_get_iter_state_with_timeline_idx(2));
  cl_assert(timeline_model_get_iter_state(1) == timeline_model_get_iter_state_with_timeline_idx(3));
  cl_assert(timeline_model_get_iter_state(2) == timeline_model_get_iter_state_with_timeline_idx(4));

  cl_assert(timeline_model_iter_prev(&new_idx, NULL));
  cl_assert_equal_i(new_idx, 1);
  cl_assert_equal_i(timeline_model_get_num_items(), 2);
  cl_assert(uuid_equal(&s_items[s_correct_order[1]].header.id,
      &timeline_model_get_iter_state(0)->pin.header.id));
  cl_assert(uuid_equal(&s_items[s_correct_order[2]].header.id,
      &timeline_model_get_iter_state(1)->pin.header.id));
  cl_assert(uuid_equal(&s_items[s_correct_order[3]].header.id,
      &timeline_model_get_iter_state(2)->pin.header.id));
  cl_assert(timeline_model_get_iter_state(0) == timeline_model_get_iter_state_with_timeline_idx(1));
  cl_assert(timeline_model_get_iter_state(1) == timeline_model_get_iter_state_with_timeline_idx(2));
  cl_assert(timeline_model_get_iter_state(2) == timeline_model_get_iter_state_with_timeline_idx(3));

  cl_assert(timeline_model_iter_prev(&new_idx, NULL));
  cl_assert_equal_i(new_idx, 0);
  cl_assert_equal_i(timeline_model_get_num_items(), 2);
  cl_assert(uuid_equal(&s_items[s_correct_order[0]].header.id,
      &timeline_model_get_iter_state(0)->pin.header.id));
  cl_assert(uuid_equal(&s_items[s_correct_order[1]].header.id,
      &timeline_model_get_iter_state(1)->pin.header.id));
  cl_assert(uuid_equal(&s_items[s_correct_order[2]].header.id,
      &timeline_model_get_iter_state(2)->pin.header.id));
  cl_assert(timeline_model_get_iter_state(0) == timeline_model_get_iter_state_with_timeline_idx(0));
  cl_assert(timeline_model_get_iter_state(1) == timeline_model_get_iter_state_with_timeline_idx(1));
  cl_assert(timeline_model_get_iter_state(2) == timeline_model_get_iter_state_with_timeline_idx(2));

  cl_assert(!timeline_model_iter_prev(&new_idx, NULL));
}

void test_timeline_model__graceful_delete_middle(void) {
  TimelineModel model = {0};
  model.direction = TimelineIterDirectionFuture;
  time_t first_time = 1421178000;
  // Note: 1421178000 = Tue Jan 13 11:40:00 PST 2015
  timeline_model_init(first_time, &model);

  timeline_model_remove(&s_items[s_correct_order[1]].header.id);
  cl_assert_equal_i(timeline_model_get_num_items(), 2);
  cl_assert(uuid_equal(&s_items[s_correct_order[0]].header.id,
      &timeline_model_get_iter_state(0)->pin.header.id));
  cl_assert(uuid_equal(&s_items[s_correct_order[2]].header.id,
      &timeline_model_get_iter_state(1)->pin.header.id));
  cl_assert(timeline_model_get_iter_state(0) == timeline_model_get_iter_state_with_timeline_idx(0));
  cl_assert(timeline_model_get_iter_state(1) == timeline_model_get_iter_state_with_timeline_idx(2));
}

void test_timeline_model__graceful_delete_first(void) {
  TimelineModel model = {0};
  model.direction = TimelineIterDirectionFuture;
  time_t first_time = 1421178000;
  // Note: 1421178000 = Tue Jan 13 11:40:00 PST 2015
  timeline_model_init(first_time, &model);

  timeline_model_remove(&s_items[s_correct_order[0]].header.id);
  cl_assert_equal_i(timeline_model_get_num_items(), 2);
  cl_assert(uuid_equal(&s_items[s_correct_order[1]].header.id,
      &timeline_model_get_iter_state(0)->pin.header.id));
  cl_assert(uuid_equal(&s_items[s_correct_order[2]].header.id,
      &timeline_model_get_iter_state(1)->pin.header.id));
  cl_assert(timeline_model_get_iter_state(0) == timeline_model_get_iter_state_with_timeline_idx(1));
  cl_assert(timeline_model_get_iter_state(1) == timeline_model_get_iter_state_with_timeline_idx(2));
}

void test_timeline_model__graceful_delete_all(void) {
  TimelineModel model = {0};
  model.direction = TimelineIterDirectionFuture;
  time_t first_time = 1421178000;
  // Note: 1421178000 = Tue Jan 13 11:40:00 PST 2015
  timeline_model_init(first_time, &model);

  for (int i = 0; i < ARRAY_LENGTH(s_items); i++) {
    timeline_model_remove(&s_items[i].header.id);
  }
  cl_assert_equal_i(timeline_model_get_num_items(), 0);
  cl_assert(!timeline_model_iter_next(NULL, NULL));
  cl_assert(!timeline_model_iter_prev(NULL, NULL));
}

void test_timeline_model__is_empty(void) {
  TimelineModel model = {0};
  model.direction = TimelineIterDirectionFuture;
  time_t first_time = 1421178000;
  // Note: 1421178000 = Tue Jan 13 11:40:00 PST 2015
  timeline_model_init(first_time, &model);

  cl_assert(!timeline_model_is_empty());

  for (int i = 0; i < ARRAY_LENGTH(s_items); i++) {
    timeline_model_remove(&s_items[i].header.id);
  }

  cl_assert(timeline_model_is_empty());
}

void test_timeline_model__is_empty_immediate(void) {
  TimelineModel model = {0};
  model.direction = TimelineIterDirectionFuture;
  time_t first_time = 1421178000;
  // Note: 1421178000 = Tue Jan 13 11:40:00 PST 2015
  for (int i = 0; i < ARRAY_LENGTH(s_items); i++) {
    pin_db_delete((uint8_t *)&s_items[i].header.id, sizeof(TimelineItemId));
  }

  timeline_model_init(first_time, &model);

  cl_assert(timeline_model_is_empty());
}
