#include "watch_config.h"

#include <stdbool.h>

#include "esp_check.h"
#include "esp_log.h"
#include "nvs.h"
#include "nvs_flash.h"

#define WATCH_CONFIG_NAMESPACE "watchcfg"
#define KEY_HB_PERIOD "hb_period"
#define KEY_DCDC_OFF "dcdc_off"
#define KEY_HB_START "hb_start"
#define KEY_HB_MON "hb_mon"
#define KEY_HB_MISS "hb_miss"

#define DEFAULT_HB_PERIOD_SEC 4U
#define DEFAULT_DCDC_OFF_SEC 2U
#define DEFAULT_HB_START_SEC 2U
#define DEFAULT_HB_MISS_LIMIT 3U

static watch_config_t s_config = {
    .heartbeat_period_sec = DEFAULT_HB_PERIOD_SEC,
    .dcdc_restart_off_sec = DEFAULT_DCDC_OFF_SEC,
    .heartbeat_start_delay_sec = DEFAULT_HB_START_SEC,
    .heartbeat_monitor_enabled = true,
    .heartbeat_miss_limit = DEFAULT_HB_MISS_LIMIT,
};
static const char *TAG = "watch_cfg";
static bool s_initialized;

static void watch_config_apply_defaults(void)
{
    s_config.heartbeat_period_sec = DEFAULT_HB_PERIOD_SEC;
    s_config.dcdc_restart_off_sec = DEFAULT_DCDC_OFF_SEC;
    s_config.heartbeat_start_delay_sec = DEFAULT_HB_START_SEC;
    s_config.heartbeat_monitor_enabled = true;
    s_config.heartbeat_miss_limit = DEFAULT_HB_MISS_LIMIT;
}

static void watch_config_clamp(watch_config_t *cfg)
{
    if (!cfg->heartbeat_period_sec) {
        cfg->heartbeat_period_sec = DEFAULT_HB_PERIOD_SEC;
    }
    if (!cfg->dcdc_restart_off_sec) {
        cfg->dcdc_restart_off_sec = DEFAULT_DCDC_OFF_SEC;
    }
    if (!cfg->heartbeat_start_delay_sec) {
        cfg->heartbeat_start_delay_sec = DEFAULT_HB_START_SEC;
    }
    cfg->heartbeat_monitor_enabled = cfg->heartbeat_monitor_enabled ? true : false;
    if (!cfg->heartbeat_miss_limit) {
        cfg->heartbeat_miss_limit = DEFAULT_HB_MISS_LIMIT;
    }
}

esp_err_t watch_config_init(void)
{
    if (s_initialized) {
        return ESP_OK;
    }

    esp_err_t err = nvs_flash_init();
    if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
        ESP_RETURN_ON_ERROR(nvs_flash_erase(), TAG, "NVS erase failed");
        err = nvs_flash_init();
    }
    ESP_RETURN_ON_ERROR(err, TAG, "NVS init failed");

    watch_config_apply_defaults();

    nvs_handle_t handle = 0;
    err = nvs_open(WATCH_CONFIG_NAMESPACE, NVS_READWRITE, &handle);
    ESP_RETURN_ON_ERROR(err, TAG, "NVS open failed");

    uint32_t value = 0;
    if (nvs_get_u32(handle, KEY_HB_PERIOD, &value) == ESP_OK && value > 0) {
        s_config.heartbeat_period_sec = value;
    }
    if (nvs_get_u32(handle, KEY_DCDC_OFF, &value) == ESP_OK && value > 0) {
        s_config.dcdc_restart_off_sec = value;
    }
    if (nvs_get_u32(handle, KEY_HB_START, &value) == ESP_OK && value > 0) {
        s_config.heartbeat_start_delay_sec = value;
    }
    uint8_t hb_mon = 1;
    if (nvs_get_u8(handle, KEY_HB_MON, &hb_mon) == ESP_OK) {
        s_config.heartbeat_monitor_enabled = (hb_mon != 0);
    }
    if (nvs_get_u32(handle, KEY_HB_MISS, &value) == ESP_OK && value > 0) {
        s_config.heartbeat_miss_limit = value;
    }

    nvs_close(handle);
    s_initialized = true;
    return ESP_OK;
}

const watch_config_t *watch_config_get(void)
{
    return &s_config;
}

esp_err_t watch_config_save(const watch_config_t *cfg)
{
    if (!cfg) {
        return ESP_ERR_INVALID_ARG;
    }

    watch_config_t tmp = *cfg;
    watch_config_clamp(&tmp);

    nvs_handle_t handle = 0;
    ESP_RETURN_ON_ERROR(nvs_open(WATCH_CONFIG_NAMESPACE, NVS_READWRITE, &handle),
                        TAG, "NVS open failed");

    esp_err_t err = nvs_set_u32(handle, KEY_HB_PERIOD, tmp.heartbeat_period_sec);
    if (err == ESP_OK) {
        err = nvs_set_u32(handle, KEY_DCDC_OFF, tmp.dcdc_restart_off_sec);
    }
    if (err == ESP_OK) {
        err = nvs_set_u32(handle, KEY_HB_START, tmp.heartbeat_start_delay_sec);
    }
    if (err == ESP_OK) {
        err = nvs_set_u8(handle, KEY_HB_MON, tmp.heartbeat_monitor_enabled ? 1 : 0);
    }
    if (err == ESP_OK) {
        err = nvs_set_u32(handle, KEY_HB_MISS, tmp.heartbeat_miss_limit);
    }
    if (err == ESP_OK) {
        err = nvs_commit(handle);
    }
    nvs_close(handle);
    if (err != ESP_OK) {
        return err;
    }

    s_config = tmp;
    return ESP_OK;
}
