#include <inttypes.h>
#include <stdbool.h>

#include "freertos/FreeRTOS.h"

#include "evse_config.h"
#include "board_config.h"
#include "evse_limits.h"
#include "evse_api.h"
#include "evse_state.h"

#include "esp_log.h"
#include "esp_err.h"
#include "esp_check.h"

#include "storage_service.h"

static const char *TAG = "evse_config";
#define NVS_NAMESPACE "evse_config"

// ========================
// 3 variáveis (semântica simples)
// ========================

// 1) Hardware (FIXO)
static const uint8_t max_charging_current = MAX_CHARGING_CURRENT_LIMIT;

// 2) Configurável (persistido)
static uint16_t charging_current = MAX_CHARGING_CURRENT_LIMIT;

// 3) Runtime (RAM)
static uint16_t charging_current_runtime = 0;

// Outros parâmetros (persistidos)
static bool socket_outlet = false;
static bool rcm = false;
static uint8_t temp_threshold = 60;

// Availability / Enable flags (persistidos)
static bool is_available = true;
static bool is_enabled = true;

static inline TickType_t TO_TICKS_MS(uint32_t ms) { return pdMS_TO_TICKS(ms); }

// Ajusta conforme o teu boot:
// 1000ms pode ser curto com Wi-Fi/FS/tasks; 2000ms é mais robusto em produto.
static inline TickType_t BOOT_TO(void) { return TO_TICKS_MS(2000); }

// ========================
// Initialization
// ========================
esp_err_t evse_config_init(void)
{
    // garante storage iniciado
    ESP_RETURN_ON_ERROR(storage_service_init(), TAG, "storage init failed");
    ESP_LOGI(TAG, "EVSE config init OK (storage-backed)");
    return ESP_OK;
}

void evse_check_defaults(void)
{
    esp_err_t err;
    uint8_t u8 = 0;
    uint16_t u16 = 0;

    // Timeouts: leitura e escrita no boot
    const TickType_t rd_to = BOOT_TO();
    const TickType_t wr_to = TO_TICKS_MS(2000);

    ESP_LOGD(TAG, "Checking default parameters (sync persistence)...");

    // -----------------------------------------
    // Charging current (default, persisted)
    // -----------------------------------------
    err = storage_get_u16_sync(NVS_NAMESPACE, "def_chrg_curr", &u16, rd_to);
    if (err != ESP_OK || u16 < MIN_CHARGING_CURRENT_LIMIT || u16 > max_charging_current)
    {
        charging_current = max_charging_current;

        esp_err_t se = storage_set_u16_sync(NVS_NAMESPACE, "def_chrg_curr", charging_current, wr_to);
        if (se != ESP_OK)
        {
            ESP_LOGE(TAG, "Failed to persist def_chrg_curr=%u: %s",
                     (unsigned)charging_current, esp_err_to_name(se));
            // seguimos com RAM correta; persist pode falhar por flash/partição
        }

        ESP_LOGW(TAG, "Invalid/missing def_chrg_curr (%s) -> reset to %u (sync persisted)",
                 esp_err_to_name(err), (unsigned)charging_current);
    }
    else
    {
        charging_current = u16;
    }

    // runtime inicializa a partir do default
    charging_current_runtime = charging_current;
    ESP_LOGD(TAG, "Runtime charging current initialized from default: %u",
             (unsigned)charging_current_runtime);

    // -----------------------------------------
    // Socket outlet (persisted) + capability gate
    // -----------------------------------------
    err = storage_get_u8_sync(NVS_NAMESPACE, "socket_outlet", &u8, rd_to);
    if (err == ESP_OK && u8 <= 1)
    {
        bool wanted = (u8 != 0);

        if (wanted && !board_config.proximity)
        {
            // NVS dizia 1, mas HW não suporta -> runtime false e persistir 0
            socket_outlet = false;

            esp_err_t se = storage_set_u8_sync(NVS_NAMESPACE, "socket_outlet", 0, wr_to);
            if (se != ESP_OK)
            {
                ESP_LOGE(TAG, "Failed to persist socket_outlet=0 (capability mismatch): %s",
                         esp_err_to_name(se));
            }

            ESP_LOGW(TAG, "socket_outlet requested but HW has no proximity -> forcing false (sync persisted)");
        }
        else
        {
            socket_outlet = wanted;
        }
    }
    else
    {
        socket_outlet = false;

        esp_err_t se = storage_set_u8_sync(NVS_NAMESPACE, "socket_outlet", 0, wr_to);
        if (se != ESP_OK)
        {
            ESP_LOGE(TAG, "Failed to persist socket_outlet default=0: %s", esp_err_to_name(se));
        }

        ESP_LOGW(TAG, "Missing/invalid socket_outlet (%s) -> default=false (sync persisted).",
                 esp_err_to_name(err));
    }

    // -----------------------------------------
    // RCM (persisted) + capability gate
    // -----------------------------------------
    err = storage_get_u8_sync(NVS_NAMESPACE, "rcm", &u8, rd_to);
    if (err == ESP_OK && u8 <= 1)
    {
        bool wanted = (u8 != 0);

        if (wanted && !board_config.rcm)
        {
            rcm = false;

            esp_err_t se = storage_set_u8_sync(NVS_NAMESPACE, "rcm", 0, wr_to);
            if (se != ESP_OK)
            {
                ESP_LOGE(TAG, "Failed to persist rcm=0 (capability mismatch): %s",
                         esp_err_to_name(se));
            }

            ESP_LOGW(TAG, "rcm requested but HW has no RCM -> forcing false (sync persisted)");
        }
        else
        {
            rcm = wanted;
        }
    }
    else
    {
        rcm = false;

        esp_err_t se = storage_set_u8_sync(NVS_NAMESPACE, "rcm", 0, wr_to);
        if (se != ESP_OK)
        {
            ESP_LOGE(TAG, "Failed to persist rcm default=0: %s", esp_err_to_name(se));
        }

        ESP_LOGW(TAG, "Missing/invalid rcm (%s) -> default=false (sync persisted).",
                 esp_err_to_name(err));
    }

    // -----------------------------------------
    // Temp threshold (persisted)
    // -----------------------------------------
    err = storage_get_u8_sync(NVS_NAMESPACE, "temp_threshold", &u8, rd_to);
    if (err == ESP_OK && u8 >= 40 && u8 <= 80)
    {
        temp_threshold = u8;
    }
    else
    {
        temp_threshold = 60;

        esp_err_t se = storage_set_u8_sync(NVS_NAMESPACE, "temp_threshold", temp_threshold, wr_to);
        if (se != ESP_OK)
        {
            ESP_LOGE(TAG, "Failed to persist temp_threshold=%u: %s",
                     (unsigned)temp_threshold, esp_err_to_name(se));
        }

        ESP_LOGW(TAG, "Invalid/missing temp_threshold (%s) -> default=60 (sync persisted).",
                 esp_err_to_name(err));
    }

    // -----------------------------------------
    // Availability (persisted) [0/1]
    // -----------------------------------------
    err = storage_get_u8_sync(NVS_NAMESPACE, "available", &u8, rd_to);
    if (err == ESP_OK && u8 <= 1)
    {
        is_available = (u8 != 0);
    }
    else
    {
        is_available = true;

        esp_err_t se = storage_set_u8_sync(NVS_NAMESPACE, "available", 1, wr_to);
        if (se != ESP_OK)
        {
            ESP_LOGE(TAG, "Failed to persist available=1: %s", esp_err_to_name(se));
        }

        ESP_LOGW(TAG, "Missing/invalid 'available' (%s) -> default=true (sync persisted).",
                 esp_err_to_name(err));
    }

    // -----------------------------------------
    // Enabled (persisted) [0/1]
    // -----------------------------------------
    err = storage_get_u8_sync(NVS_NAMESPACE, "enabled", &u8, rd_to);
    if (err == ESP_OK && u8 <= 1)
    {
        is_enabled = (u8 != 0);
    }
    else
    {
        is_enabled = true;

        esp_err_t se = storage_set_u8_sync(NVS_NAMESPACE, "enabled", 1, wr_to);
        if (se != ESP_OK)
        {
            ESP_LOGE(TAG, "Failed to persist enabled=1: %s", esp_err_to_name(se));
        }

        ESP_LOGW(TAG, "Missing/invalid 'enabled' (%s) -> default=true (sync persisted).",
                 esp_err_to_name(err));
    }

    // Flush explícito no boot:
    // - ajuda a garantir commit determinístico antes do resto do sistema avançar
    // - mantém-se útil mesmo com setters sync se o teu storage ainda estiver com debounce interno
    esp_err_t fe = storage_flush_sync(wr_to);
    if (fe != ESP_OK)
        ESP_LOGE(TAG, "storage_flush_sync failed: %s", esp_err_to_name(fe));
}

// ========================
// Charging current getters/setters
// ========================
uint8_t evse_get_max_charging_current(void) { return max_charging_current; }

uint16_t evse_get_charging_current(void) { return charging_current; }

esp_err_t evse_set_charging_current(uint16_t value)
{
    if (value < MIN_CHARGING_CURRENT_LIMIT || value > max_charging_current)
        return ESP_ERR_INVALID_ARG;

    if (value == charging_current)
    {
        evse_set_runtime_charging_current(value);
        return ESP_OK;
    }

    charging_current = value;

    esp_err_t err = storage_set_u16_async(NVS_NAMESPACE, "def_chrg_curr", value);
    if (err != ESP_OK)
    {
        // Em runtime, isto pode falhar por fila cheia. RAM fica correta; persistência é best-effort.
        ESP_LOGE(TAG, "Failed to persist def_chrg_curr async=%u: %s", (unsigned)value, esp_err_to_name(err));
        return err;
    }

    evse_set_runtime_charging_current(value);
    return ESP_OK;
}

// ========================
// Runtime current (not saved)
// ========================
void evse_set_runtime_charging_current(uint16_t value)
{
    if (value > max_charging_current)
        value = max_charging_current;
    else if (value < MIN_CHARGING_CURRENT_LIMIT)
        value = MIN_CHARGING_CURRENT_LIMIT;

    charging_current_runtime = value;
}

uint16_t evse_get_runtime_charging_current(void) { return charging_current_runtime; }

// ========================
// Socket outlet
// ========================
bool evse_get_socket_outlet(void) { return socket_outlet; }

esp_err_t evse_set_socket_outlet(bool value)
{
    if (value && !board_config.proximity)
        return ESP_ERR_INVALID_ARG;
    if (value == socket_outlet)
        return ESP_OK;

    socket_outlet = value;

    esp_err_t err = storage_set_u8_async(NVS_NAMESPACE, "socket_outlet", (uint8_t)value);
    if (err != ESP_OK)
    {
        ESP_LOGE(TAG, "Failed to persist socket_outlet async=%u: %s", (unsigned)value, esp_err_to_name(err));
        return err;
    }

    return ESP_OK;
}

// ========================
// RCM
// ========================
bool evse_is_rcm(void) { return rcm; }

esp_err_t evse_set_rcm(bool value)
{
    if (value && !board_config.rcm)
        return ESP_ERR_INVALID_ARG;
    if (value == rcm)
        return ESP_OK;

    rcm = value;

    esp_err_t err = storage_set_u8_async(NVS_NAMESPACE, "rcm", (uint8_t)value);
    if (err != ESP_OK)
    {
        ESP_LOGE(TAG, "Failed to persist rcm async=%u: %s", (unsigned)value, esp_err_to_name(err));
        return err;
    }

    return ESP_OK;
}

// ========================
// Temperature
// ========================
uint8_t evse_get_temp_threshold(void) { return temp_threshold; }

esp_err_t evse_set_temp_threshold(uint8_t value)
{
    if (value < 40 || value > 80)
        return ESP_ERR_INVALID_ARG;
    if (value == temp_threshold)
        return ESP_OK;

    temp_threshold = value;

    esp_err_t err = storage_set_u8_async(NVS_NAMESPACE, "temp_threshold", value);
    if (err != ESP_OK)
    {
        ESP_LOGE(TAG, "Failed to persist temp_threshold async=%u: %s", (unsigned)value, esp_err_to_name(err));
        return err;
    }

    return ESP_OK;
}

// ========================
// Availability
// ========================
bool evse_config_is_available(void) { return is_available; }

void evse_config_set_available(bool available)
{
    bool newv = available;
    if (newv == is_available)
        return;

    is_available = newv;

    esp_err_t err = storage_set_u8_async(NVS_NAMESPACE, "available", (uint8_t)is_available);
    if (err != ESP_OK)
        ESP_LOGE(TAG, "Failed to persist 'available' async=%u: %s", (unsigned)is_available, esp_err_to_name(err));
}

// ========================
// Enable/Disable
// ========================
bool evse_config_is_enabled(void) { return is_enabled; }

void evse_config_set_enabled(bool enabled)
{
    bool newv = enabled;
    if (newv == is_enabled)
        return;

    is_enabled = newv;

    esp_err_t err = storage_set_u8_async(NVS_NAMESPACE, "enabled", (uint8_t)is_enabled);
    if (err != ESP_OK)
        ESP_LOGE(TAG, "Failed to persist 'enabled' async=%u: %s", (unsigned)is_enabled, esp_err_to_name(err));
}
