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

#include "evse_state.h"
#include "evse_api.h"
#include "evse_limits.h"
#include "evse_meter.h"
#include "evse_session.h"

#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_err.h"
#include "esp_check.h"

#include "storage_service.h"

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

static portMUX_TYPE evse_mux = portMUX_INITIALIZER_UNLOCKED;

static bool limit_reached = false;
static uint32_t consumption_limit = 0;   // Wh
static uint32_t charging_time_limit = 0; // seconds
static uint16_t under_power_limit = 0;   // W

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

// ---------------------------------
// Init + defaults
// ---------------------------------
esp_err_t evse_limits_init(void)
{
    ESP_RETURN_ON_ERROR(storage_service_init(), TAG, "storage init failed");
    ESP_LOGI(TAG, "EVSE limits init OK (storage-backed)");
    return ESP_OK;
}

void evse_limits_check_defaults(void)
{
    esp_err_t err;
    bool needs_flush = false;

    uint32_t u32 = 0;
    uint16_t u16 = 0;

    ESP_LOGD(TAG, "Checking default limits...");

    // Consumption limit (Wh) default = 0 (disabled)
    err = storage_get_u32_sync(NVS_NAMESPACE, "def_cons_lim", &u32, BOOT_TO());
    if (err == ESP_OK)
    {
        portENTER_CRITICAL(&evse_mux);
        consumption_limit = u32;
        portEXIT_CRITICAL(&evse_mux);
    }
    else
    {
        portENTER_CRITICAL(&evse_mux);
        consumption_limit = 0;
        portEXIT_CRITICAL(&evse_mux);

        (void)storage_set_u32_async(NVS_NAMESPACE, "def_cons_lim", 0);
        needs_flush = true;
        ESP_LOGW(TAG, "Missing def_cons_lim (%s) -> default=0 (persisted).", esp_err_to_name(err));
    }

    // Charging time limit (s) default = 0 (disabled)
    err = storage_get_u32_sync(NVS_NAMESPACE, "def_ch_time_lim", &u32, BOOT_TO());
    if (err == ESP_OK)
    {
        portENTER_CRITICAL(&evse_mux);
        charging_time_limit = u32;
        portEXIT_CRITICAL(&evse_mux);
    }
    else
    {
        portENTER_CRITICAL(&evse_mux);
        charging_time_limit = 0;
        portEXIT_CRITICAL(&evse_mux);

        (void)storage_set_u32_async(NVS_NAMESPACE, "def_ch_time_lim", 0);
        needs_flush = true;
        ESP_LOGW(TAG, "Missing def_ch_time_lim (%s) -> default=0 (persisted).", esp_err_to_name(err));
    }

    // Under-power limit (W) default = 0 (disabled)
    err = storage_get_u16_sync(NVS_NAMESPACE, "def_un_pwr_lim", &u16, BOOT_TO());
    if (err == ESP_OK)
    {
        portENTER_CRITICAL(&evse_mux);
        under_power_limit = u16;
        portEXIT_CRITICAL(&evse_mux);
    }
    else
    {
        portENTER_CRITICAL(&evse_mux);
        under_power_limit = 0;
        portEXIT_CRITICAL(&evse_mux);

        (void)storage_set_u16_async(NVS_NAMESPACE, "def_un_pwr_lim", 0);
        needs_flush = true;
        ESP_LOGW(TAG, "Missing def_un_pwr_lim (%s) -> default=0 (persisted).", esp_err_to_name(err));
    }

    if (needs_flush)
    {
        esp_err_t fe = storage_flush_sync(TO_TICKS_MS(2000));
        if (fe != ESP_OK)
            ESP_LOGE(TAG, "storage_flush_sync failed: %s", esp_err_to_name(fe));
        else
            ESP_LOGD(TAG, "Defaults committed (flush).");
    }
}

// ---------------------------------
// Limit reached flag
// ---------------------------------
bool evse_get_limit_reached(void)
{
    bool val;
    portENTER_CRITICAL(&evse_mux);
    val = limit_reached;
    portEXIT_CRITICAL(&evse_mux);
    return val;
}

void evse_set_limit_reached(bool v)
{
    portENTER_CRITICAL(&evse_mux);
    limit_reached = v;
    portEXIT_CRITICAL(&evse_mux);
}

bool evse_is_limit_reached(void)
{
    return evse_get_limit_reached();
}

// ---------------------------------
// Consumption limit
// ---------------------------------
uint32_t evse_get_consumption_limit(void)
{
    uint32_t val;
    portENTER_CRITICAL(&evse_mux);
    val = consumption_limit;
    portEXIT_CRITICAL(&evse_mux);
    return val;
}

void evse_set_consumption_limit(uint32_t value)
{
    bool changed = false;

    portENTER_CRITICAL(&evse_mux);
    if (consumption_limit != value)
    {
        consumption_limit = value;
        changed = true;
    }
    portEXIT_CRITICAL(&evse_mux);

    if (!changed)
        return;

    esp_err_t err = storage_set_u32_async(NVS_NAMESPACE, "def_cons_lim", value);
    if (err != ESP_OK)
    {
        ESP_LOGE(TAG,
                 "Failed to persist consumption limit (%" PRIu32 " Wh): %s",
                 value, esp_err_to_name(err));
    }
}

// ---------------------------------
// Charging time limit
// ---------------------------------
uint32_t evse_get_charging_time_limit(void)
{
    uint32_t val;
    portENTER_CRITICAL(&evse_mux);
    val = charging_time_limit;
    portEXIT_CRITICAL(&evse_mux);
    return val;
}

void evse_set_charging_time_limit(uint32_t value)
{
    bool changed = false;

    portENTER_CRITICAL(&evse_mux);
    if (charging_time_limit != value)
    {
        charging_time_limit = value;
        changed = true;
    }
    portEXIT_CRITICAL(&evse_mux);

    if (!changed)
        return;

    esp_err_t err = storage_set_u32_async(NVS_NAMESPACE, "def_ch_time_lim", value);
    if (err != ESP_OK)
    {
        ESP_LOGE(TAG,
                 "Failed to persist charging time limit (%" PRIu32 " s): %s",
                 value, esp_err_to_name(err));
    }
}

// ---------------------------------
// Under-power limit
// ---------------------------------
uint16_t evse_get_under_power_limit(void)
{
    uint16_t val;
    portENTER_CRITICAL(&evse_mux);
    val = under_power_limit;
    portEXIT_CRITICAL(&evse_mux);
    return val;
}

void evse_set_under_power_limit(uint16_t value)
{
    bool changed = false;

    portENTER_CRITICAL(&evse_mux);
    if (under_power_limit != value)
    {
        under_power_limit = value;
        changed = true;
    }
    portEXIT_CRITICAL(&evse_mux);

    if (!changed)
        return;

    esp_err_t err = storage_set_u16_async(NVS_NAMESPACE, "def_un_pwr_lim", value);
    if (err != ESP_OK)
    {
        ESP_LOGE(TAG,
                 "Failed to persist under-power limit (%" PRIu32 " W): %s",
                 (uint32_t)value, esp_err_to_name(err));
    }
}

// ---------------------------------
// Runtime check
// ---------------------------------
void evse_limits_check(void)
{
    // Só faz sentido quando há energia ativa (C2/D2)
    if (!evse_state_is_charging(evse_get_state()))
        return;

    evse_session_t sess;
    if (!evse_session_get(&sess) || !sess.is_current)
        return;

    uint32_t cons_lim;
    uint32_t time_lim;
    uint16_t unp_lim;

    portENTER_CRITICAL(&evse_mux);
    cons_lim = consumption_limit;
    time_lim = charging_time_limit;
    unp_lim  = under_power_limit;
    portEXIT_CRITICAL(&evse_mux);

    bool reached = false;

    if (cons_lim > 0 && sess.energy_wh >= cons_lim)
    {
        ESP_LOGW(TAG, "Energy limit reached: %" PRIu32 " Wh ≥ %" PRIu32 " Wh",
                 sess.energy_wh, cons_lim);
        reached = true;
    }

    if (time_lim > 0 && sess.duration_s >= time_lim)
    {
        ESP_LOGW(TAG, "Charging time limit reached: %" PRIu32 " s ≥ %" PRIu32 " s",
                 sess.duration_s, time_lim);
        reached = true;
    }

    int32_t p = evse_meter_get_instant_power();
    uint32_t inst_power = (p > 0) ? (uint32_t)p : 0;

    if (unp_lim > 0 && inst_power < (uint32_t)unp_lim)
    {
        ESP_LOGW(TAG, "Under-power limit reached: %" PRIu32 " W < %" PRIu32 " W",
                 inst_power, (uint32_t)unp_lim);
        reached = true;
    }

    if (reached)
        evse_set_limit_reached(true);
}
