/* SPDX-FileCopyrightText: 2024 Google LLC */
/* SPDX-License-Identifier: Apache-2.0 */

#include "clar.h"

#include "services/normal/filesystem/pfs.h"
#include "services/normal/blob_db/health_db.h"
#include "util/size.h"

#include <limits.h>

#include "fake_settings_file.h"

// Stubs
////////////////////////////////////////////////////////////////
#include "stubs_app_state.h"
#include "stubs_hexdump.h"
#include "stubs_logging.h"
#include "stubs_mutex.h"
#include "stubs_passert.h"
#include "stubs_pebble_tasks.h"
#include "stubs_pbl_malloc.h"
#include "stubs_prompt.h"
#include "stubs_worker_state.h"

status_t pfs_remove(const char *name) {
  fake_settings_file_reset();
  return S_SUCCESS;
}

status_t blob_db_insert(BlobDBId db_id,
    const uint8_t *key, int key_len, const uint8_t *val, int val_len) {
  return settings_file_set(NULL, key, key_len, val, val_len);
}

RtcTicks rtc_get_ticks(void) {
  return 0;
}

// Fakes
////////////////////////////////////////////////////////////////
static const time_t NOW = 1471269600; // Mon, 15 Aug 2016 14:00:00 GMT
time_t rtc_get_time(void) {
  return NOW;
}

bool activity_get_metric(ActivityMetric metric, uint32_t history_len, int32_t *history) {
  *history = 0;
  return true;
}


int s_metric_updated_count = 0;
void activity_metrics_prv_set_metric(ActivityMetric metric, DayInWeek day, int32_t value) {
  printf("s_metric_updated_count: %d\n", s_metric_updated_count);
  s_metric_updated_count++;
}

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

void test_health_db__initialize(void) {
  fake_settings_file_reset();
  health_db_init();
  s_metric_updated_count = 0;
}


void test_health_db__cleanup(void) {
}

// Dummy Data
////////////////////////////////////////////////////////////////

#define NUM_CURRENT_MOVEMENT_METRICS 5
#define NUM_CURRENT_SLEEP_METRICS 4
#define NUM_CURRENT_HR_ZONE_METRICS 3

typedef enum MovementDataFields {
  MD_Version,
  MD_Timestamp,
  MD_Steps,
  MD_ActiveKCalories,
  MD_RestingKCalories,
  MD_Distance,
  MD_ActiveTime
} MovementDataFields;

static uint32_t s_movement_data[] = {
  1,            // Version
  NOW,          // Timestamp
  1234,         // Steps
  1111,         // Active K Calories
  2222,         // Resting K Calories
  3333,         // Distance
  4444,         // Active Time
};

static uint32_t s_old_movement_data[] = {
  1,            // Version
  NOW - (7 * SECONDS_PER_DAY),
  1234,         // Steps
  1111,         // Active K Calories
  2222,         // Resting K Calories
  3333,         // Distance
  4444,         // Active Time
};

static uint32_t s_future_movement_data[] = {
  1,            // Version
  NOW + SECONDS_PER_DAY,
  1234,         // Steps
  1111,         // Active K Calories
  2222,         // Resting K Calories
  3333,         // Distance
  4444,         // Active Time
};

typedef enum SleepDataFields {
  SD_Version,
  SD_Timestamp,
  SD_SleepDuration,
  SD_DeepSleepDuration,
  SD_FallAsleepTime,
  SD_WakeupTime,
  SD_TypicalSleepDuration,
  SD_TypicalDeepSleepDuration,
  SD_TypicalFallAsleepTime,
  SD_TypicalWakeupTime,
} SleepDataFields;

static uint32_t s_sleep_data[] = {
  1,            // Version
  NOW,          // Timestamp
  1234,         // Sleep Duration
  1111,         // Deep Sleep Duration
  2222,         // Fall Asleep Time
  3333,         // Wakeup Time
  4444,         // Active Time

  5555,         // Typical sleep duration
  6666,         // Typical deep sleep duration
  7777,         // Typical fall asleep time
  8888,         // Typical wakeup time
};

static uint32_t s_old_sleep_data[] = {
  1,            // Version
  NOW - (7 * SECONDS_PER_DAY),
  1234,         // Sleep Duration
  1111,         // Deep Sleep Duration
  2222,         // Fall Asleep Time
  3333,         // Wakeup Time
  4444,         // Active Time

  5555,         // Typical sleep duration
  6666,         // Typical deep sleep duration
  7777,         // Typical fall asleep time
  8888,         // Typical wakeup time
};

static uint32_t s_invalid_sleep_data[] = {
  5,            // Version
  NOW,          // Timestamp
  1234,         // Sleep Duration
  1111,         // Deep Sleep Duration
  2222,         // Fall Asleep Time
  3333,         // Wakeup Time
  4444,         // Active Time

  5555,         // Typical sleep duration
  6666,         // Typical deep sleep duration
  7777,         // Typical fall asleep time
  8888,         // Typical wakeup time
};

static uint32_t s_hr_zone_data[] = {
  1,            // Version
  NOW,          // Timestamp
  3,            // Number of zones
  60,           // Minutes in zone 1
  30,           // Minutes in zone 2
  15,           // Minutes in zone 3
};

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

void test_health_db__blob_db_api(void) {
  const char *key = "monday_sleepData";

  // insert one
  cl_assert_equal_i(health_db_insert((uint8_t *)key, strlen(key),
                                     (uint8_t *)s_sleep_data, sizeof(s_sleep_data)),
                    S_SUCCESS);

  // check
  int32_t val_out;
  cl_assert(health_db_get_typical_value(ActivityMetricSleepTotalSeconds, Monday, &val_out));
  cl_assert_equal_i(val_out, s_sleep_data[SD_TypicalSleepDuration]);

  // delete
  cl_assert_equal_i(health_db_delete((uint8_t *)key, strlen(key)),
                    S_SUCCESS);

  // check
  cl_assert(!health_db_get_typical_value(ActivityMetricSleepTotalSeconds, Monday, &val_out));

  // insert again
  cl_assert_equal_i(health_db_insert((uint8_t *)key, strlen(key),
                                     (uint8_t *)s_sleep_data, sizeof(s_sleep_data)),
                    S_SUCCESS);

  // check
  cl_assert(health_db_get_typical_value(ActivityMetricSleepTotalSeconds, Monday, &val_out));
  cl_assert_equal_i(val_out, s_sleep_data[SD_TypicalSleepDuration]);

  // flush
  cl_assert_equal_i(health_db_flush(), S_SUCCESS);

  // check
  cl_assert(!health_db_get_typical_value(ActivityMetricSleepTotalSeconds, Monday, &val_out));

  // insert something with an older version (this will succeed)
  cl_assert_equal_i(health_db_insert((uint8_t *)key, strlen(key),
                                     (uint8_t *)s_invalid_sleep_data, sizeof(s_sleep_data)),
                    S_SUCCESS);
  // check
  cl_assert(!health_db_get_typical_value(ActivityMetricSleepTotalSeconds, Monday, &val_out));
}

void test_health_db__movement_data(void) {
  const char *key = "monday_movementData";
  cl_assert_equal_i(health_db_insert((uint8_t *)key, strlen(key),
                                     (uint8_t *)s_movement_data, sizeof(s_movement_data)),
                    S_SUCCESS);

  cl_assert_equal_i(s_metric_updated_count, NUM_CURRENT_MOVEMENT_METRICS);

  // check typicals (not stored)
  int32_t val_out;

  cl_assert(!health_db_get_typical_value(ActivityMetricStepCount, Monday, &val_out));

  cl_assert(!health_db_get_typical_value(ActivityMetricActiveSeconds, Monday, &val_out));

  cl_assert(!health_db_get_typical_value(ActivityMetricRestingKCalories, Monday, &val_out));

  cl_assert(!health_db_get_typical_value(ActivityMetricActiveKCalories, Monday, &val_out));

  cl_assert(!health_db_get_typical_value(ActivityMetricDistanceMeters, Monday, &val_out));
}

void test_health_db__sleep_data(void) {
  const char *key = "monday_sleepData";
  cl_assert_equal_i(health_db_insert((uint8_t *)key, strlen(key),
                                     (uint8_t *)s_sleep_data, sizeof(s_sleep_data)),
                    S_SUCCESS);

  cl_assert_equal_i(s_metric_updated_count, NUM_CURRENT_SLEEP_METRICS);

  // check typicals
  int32_t val_out;

  cl_assert(health_db_get_typical_value(ActivityMetricSleepTotalSeconds, Monday, &val_out));
  cl_assert_equal_i(val_out, s_sleep_data[SD_TypicalSleepDuration]);

  cl_assert(health_db_get_typical_value(ActivityMetricSleepRestfulSeconds, Monday, &val_out));
  cl_assert_equal_i(val_out, s_sleep_data[SD_TypicalDeepSleepDuration]);

  cl_assert(health_db_get_typical_value(ActivityMetricSleepEnterAtSeconds, Monday, &val_out));
  cl_assert_equal_i(val_out, s_sleep_data[SD_TypicalFallAsleepTime]);

  cl_assert(health_db_get_typical_value(ActivityMetricSleepExitAtSeconds, Monday, &val_out));
  cl_assert_equal_i(val_out, s_sleep_data[SD_TypicalWakeupTime]);
}

void test_health_db__hr_zone_data(void) {
  const char *key = "monday_heartRateZoneData";
  cl_assert_equal_i(health_db_insert((uint8_t *)key, strlen(key),
                                     (uint8_t *)s_hr_zone_data, sizeof(s_hr_zone_data)),
                    S_SUCCESS);

  cl_assert_equal_i(s_metric_updated_count, NUM_CURRENT_HR_ZONE_METRICS);

  // check typicals (not stored)
  int32_t val_out;

  cl_assert(!health_db_get_typical_value(ActivityMetricHeartRateZone1Minutes, Monday, &val_out));
  cl_assert(!health_db_get_typical_value(ActivityMetricHeartRateZone2Minutes, Monday, &val_out));
  cl_assert(!health_db_get_typical_value(ActivityMetricHeartRateZone3Minutes, Monday, &val_out));
}

void test_health_db__step_averages(void) {
  const struct {
    const char *key;
    const char *val;
  } entries[] = {
    {
      .key = "sunday_steps",
      .val = "l4tHpFsFGE6UINneFPMnf2lgINlYuXlDS6xh6vizK9jbDen5mHQgWF6E8jOzBVnEdV0j2DNOzONfJbsWoSWH0QoQpPmm1NSW" \
             "l4tHpFsFGE6UINneFPMnf2lgINlYuXlDS6xh6vizK9jbDen5mHQgWF6E8jOzBVnEdV0j2DNOzONfJbsWoSWH0QoQpPmm1NSW",
    }, {
      .key = "monday_steps",
      .val = "Rhgc3Q7ajjydH8CA9qxVJH0FpVDjdGwwoKCLE2F55x62EZZ6MCIjUMynVq13U8vOHhaWoygDf0zwOIdAEUOrZRwvJmYVzW7J" \
             "Rhgc3Q7ajjydH8CA9qxVJH0FpVDjdGwwoKCLE2F55x62EZZ6MCIjUMynVq13U8vOHhaWoygDf0zwOIdAEUOrZRwvJmYVzW7J",
    }, {
      .key = "tuesday_steps",
      .val = "V6PrBVc4suqCYjLceUl6a1UXYO8qwL5w3WZY00KeGoHAcuST7OxGnMBVCEskty0q4OIdTeyyZOljrGif09kZOFldu3BjJqJO" \
             "V6PrBVc4suqCYjLceUl6a1UXYO8qwL5w3WZY00KeGoHAcuST7OxGnMBVCEskty0q4OIdTeyyZOljrGif09kZOFldu3BjJqJO",
    }, {
      .key = "wednesday_steps",
      .val = "wufD6hzhFUrkZkLObfn2dFKUDs0kNNWp6CFiS2XBS3spSFDQUnFLuxWPEq7Dql2HjdkVobMcOA8DiOcanhZvziN6hbteMbg8" \
             "wufD6hzhFUrkZkLObfn2dFKUDs0kNNWp6CFiS2XBS3spSFDQUnFLuxWPEq7Dql2HjdkVobMcOA8DiOcanhZvziN6hbteMbg8",
    }, {
      .key = "thursday_steps",
      .val = "FXKAfWwOueL4jLJfZRxzINDITxaThvFIpOrzYfgPVmqbbYoCZKkKkbgyvP1UaCEstr9WjptLszgMocgGSEsqmoipqqWdk7dq" \
             "FXKAfWwOueL4jLJfZRxzINDITxaThvFIpOrzYfgPVmqbbYoCZKkKkbgyvP1UaCEstr9WjptLszgMocgGSEsqmoipqqWdk7dq",
    }, {
      .key = "friday_steps",
      .val = "uxFhoWTzJxDOmyBX2g3n7wdoPKxeleBR7iwKGn7utn8qTEj0tB7aw65EEFZ5QldgAkg6lctSmamf2p95l2CpHXNgVL22hQFx" \
             "uxFhoWTzJxDOmyBX2g3n7wdoPKxeleBR7iwKGn7utn8qTEj0tB7aw65EEFZ5QldgAkg6lctSmamf2p95l2CpHXNgVL22hQFx",
    }, {
      .key = "saturday_steps",
      .val = "SSxw7WtwGnhobAOXwqbvGDDwElpRG6cll8CwM9Wysh01Mj0aFWxEVN0z5w7yQHt8bwiWVabrMeUUAek2J5zCoXiGIkav4cW8" \
             "SSxw7WtwGnhobAOXwqbvGDDwElpRG6cll8CwM9Wysh01Mj0aFWxEVN0z5w7yQHt8bwiWVabrMeUUAek2J5zCoXiGIkav4cW8",
    },
  };


  for (int i = 0; i < ARRAY_LENGTH(entries); ++i) {
    cl_assert_equal_i(health_db_insert((uint8_t *)entries[i].key,
                                       strlen(entries[i].key),
                                       (uint8_t *)entries[i].val,
                                       strlen(entries[i].val)),
                      S_SUCCESS);

    ActivityMetricAverages averages;
    health_db_get_typical_step_averages(i, &averages);

    int idx = i * 10;
    uint16_t val_expected = ((uint16_t *)entries[i].val)[idx];
    cl_assert_equal_i(averages.average[idx], val_expected);
  }
}

void test_health_db__monthly_averages(void) {
  int32_t val_out;

  const char *average_step_key = "average_dailySteps";
  int32_t average_steps_val = 123456;
  cl_assert_equal_i(health_db_insert((uint8_t *)average_step_key, strlen(average_step_key),
                                     (uint8_t *)&average_steps_val, sizeof(average_steps_val)),
                    S_SUCCESS);

  cl_assert(health_db_get_monthly_average_value(ActivityMetricStepCount, &val_out));
  cl_assert_equal_i(val_out, average_steps_val);

  const char *average_sleep_key = "average_sleepDuration";
  int32_t average_sleep_val = 654321;
  cl_assert_equal_i(health_db_insert((uint8_t *)average_sleep_key, strlen(average_sleep_key),
                                     (uint8_t *)&average_sleep_val, sizeof(average_sleep_val)),
                    S_SUCCESS);

  cl_assert(health_db_get_monthly_average_value(ActivityMetricSleepTotalSeconds, &val_out));
  cl_assert_equal_i(val_out, average_sleep_val);
}

void test_health_db__notify_listeners(void) {
  char *key;

  s_metric_updated_count = 0;
  key = "tuesday_sleepData";
  cl_assert_equal_i(health_db_insert((uint8_t *)key, strlen(key),
                                     (uint8_t *)s_sleep_data, sizeof(s_sleep_data)),
                    S_SUCCESS);
  cl_assert_equal_i(s_metric_updated_count, NUM_CURRENT_SLEEP_METRICS);

  s_metric_updated_count = 0;
  key = "wednesday_movementData";
  cl_assert_equal_i(health_db_insert((uint8_t *)key, strlen(key),
                                     (uint8_t *)s_movement_data, sizeof(s_movement_data)),
                    S_SUCCESS);
  cl_assert_equal_i(s_metric_updated_count, NUM_CURRENT_MOVEMENT_METRICS);

  s_metric_updated_count = 0;
  key = "thursday_sleepData";
  cl_assert_equal_i(health_db_insert((uint8_t *)key, strlen(key),
                                     (uint8_t *)s_sleep_data, sizeof(s_sleep_data)),
                    S_SUCCESS);
  cl_assert_equal_i(s_metric_updated_count, NUM_CURRENT_SLEEP_METRICS);

  s_metric_updated_count = 0;
  key = "friday_movementData";
  cl_assert_equal_i(health_db_insert((uint8_t *)key, strlen(key),
                                     (uint8_t *)s_movement_data, sizeof(s_movement_data)),
                    S_SUCCESS);
  cl_assert_equal_i(s_metric_updated_count, NUM_CURRENT_MOVEMENT_METRICS);

  s_metric_updated_count = 0;
  key = "saturday_sleepData";
  cl_assert_equal_i(health_db_insert((uint8_t *)key, strlen(key),
                                     (uint8_t *)s_sleep_data, sizeof(s_sleep_data)),
                    S_SUCCESS);
  cl_assert_equal_i(s_metric_updated_count, NUM_CURRENT_SLEEP_METRICS);

  s_metric_updated_count = 0;
  key = "sunday_movementData";
  cl_assert_equal_i(health_db_insert((uint8_t *)key, strlen(key),
                                     (uint8_t *)s_movement_data, sizeof(s_movement_data)),
                    S_SUCCESS);
  cl_assert_equal_i(s_metric_updated_count, NUM_CURRENT_MOVEMENT_METRICS);


  s_metric_updated_count = 0;
  key = "monday_movementData";
  cl_assert_equal_i(health_db_insert((uint8_t *)key, strlen(key),
                                     (uint8_t *)s_movement_data, sizeof(s_movement_data)),
                    S_SUCCESS);
  cl_assert_equal_i(s_metric_updated_count, NUM_CURRENT_MOVEMENT_METRICS);

  s_metric_updated_count = 0;
  key = "monday_sleepData";
  cl_assert_equal_i(health_db_insert((uint8_t *)key, strlen(key),
                                     (uint8_t *)s_sleep_data, sizeof(s_sleep_data)),
                    S_SUCCESS);
  cl_assert_equal_i(s_metric_updated_count, NUM_CURRENT_SLEEP_METRICS);


  // Test inserting something which is more than a week old.
  // We shouldn't update our internal storage
  s_metric_updated_count = 0;
  key = "monday_movementData";
  cl_assert_equal_i(health_db_insert((uint8_t *)key, strlen(key),
                                     (uint8_t *)s_old_movement_data, sizeof(s_old_movement_data)),
                    S_SUCCESS);
  cl_assert_equal_i(s_metric_updated_count, 0);

  s_metric_updated_count = 0;
  key = "monday_sleepData";
  cl_assert_equal_i(health_db_insert((uint8_t *)key, strlen(key),
                                     (uint8_t *)s_old_sleep_data, sizeof(s_old_sleep_data)),
                    S_SUCCESS);
  cl_assert_equal_i(s_metric_updated_count, 0);

  // Insert something which has a future timestamp
  s_metric_updated_count = 0;
  key = "monday_movementData";
  cl_assert_equal_i(health_db_insert((uint8_t *)key, strlen(key),
                                     (uint8_t *)s_future_movement_data, sizeof(s_movement_data)),
                    S_SUCCESS);
  cl_assert_equal_i(s_metric_updated_count, 0);

}
