// components/ocpp/src/ocpp.c
// Updated for newer MicroOcpp C-facade (init has ocpp201 flag, TxNotification type)
//
// Requested constraints / fixes:
// - In ocpp.c use ONLY storage_set_*_async (no storage_flush_* here)
// - Fix (2) ocpp_task_func must check enabled and "pause" loop when disabled
// - Fix (4) reduce race while ocpp_cleanup_internal runs (cut fast in handlers / stop flag)
// - Fix (5) don't call mg_mgr_free unless mg_mgr_init was called
// - Fix (7) validate energy conversion (NaN/Inf/negative, clamp), and handle unit kWh->Wh

#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <inttypes.h>
#include <math.h>

#include "esp_log.h"
#include "esp_err.h"
#include "esp_timer.h"
#include "esp_event.h"
#include "esp_wifi.h"
#include "esp_mac.h"

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"

#include "ocpp.h"
#include "ocpp_events.h"

#include "evse_error.h"
#include "auth_events.h"
#include "evse_events.h"
#include "evse_state.h"
#include "meter_events.h"

/* MicroOcpp includes */
#include <mongoose.h>
#include "MicroOcpp_c.h"               // C-facade of MicroOcpp
#include "MicroOcppMongooseClient_c.h" // WebSocket integration for ESP-IDF

// Storage layer
#include "storage_service.h"

#define NVS_NAMESPACE "ocpp"
#define NVS_OCPP_ENABLED "enabled"
#define NVS_OCPP_SERVER "ocpp_server"
#define NVS_OCPP_CHARGE_ID "charge_id"

static const char *TAG = "ocpp";

/**
 * Energy unit selection:
 * - If meter_manager publishes total_energy in kWh, set to 1
 * - If it publishes in Wh, keep 0
 */
#ifndef METER_TOTAL_ENERGY_IS_KWH
#define METER_TOTAL_ENERGY_IS_KWH 0
#endif

// Bounded timeout for esp_event_post from loop/callback paths
#define OCPP_EVENT_POST_TO pdMS_TO_TICKS(20)

// Storage read timeouts (sync getters only)
#define STORE_TO pdMS_TO_TICKS(800)

// Graceful stop wait (ocpp_stop)
#define OCPP_STOP_WAIT_TO pdMS_TO_TICKS(5000)

static bool enabled = false;
static bool s_ocpp201 = false; // keep false unless you really want OCPP 2.0.1

static TaskHandle_t ocpp_task = NULL;

static struct mg_mgr mgr;                   // Event manager
static bool s_mgr_inited = false;           // FIX (5)
static OCPP_Connection *g_ocpp_conn = NULL; // For clean shutdown

static esp_event_handler_instance_t s_auth_verify_inst = NULL;

// EVSE state flags (updated by events)
static volatile bool s_ev_plugged = false;
static volatile bool s_ev_ready = false;
static esp_event_handler_instance_t s_evse_state_inst = NULL;

// EVSE local config flags (from EVSE_EVENTS)
static volatile bool s_evse_enabled_local = true;
static volatile bool s_evse_available_local = true;
static esp_event_handler_instance_t s_evse_enable_inst = NULL;
static esp_event_handler_instance_t s_evse_available_inst = NULL;

// Remote operative flag (ChangeAvailability) from OCPP
static volatile bool s_remote_operative = true;

// Guard: while stopping/cleanup, handlers should exit fast  (FIX 4)
static volatile bool s_stopping = false;

// --- meter cache from METER_EVENT_DATA_READY ---
typedef struct
{
    float vrms[3];
    float irms[3];
    int32_t watt[3];
    float frequency;
    float power_factor;

    float total_energy_Wh;

    int32_t sum_watt;
    float avg_voltage;
    float sum_current;

    bool have_data;
} ocpp_meter_cache_t;

static ocpp_meter_cache_t s_meter = {0};
static portMUX_TYPE s_meter_mux = portMUX_INITIALIZER_UNLOCKED;
static esp_event_handler_instance_t s_meter_inst = NULL;

// Offered current (A)
static float s_current_offered_A = 16.0f;
static float getCurrentOffered(void) { return s_current_offered_A; }

// -----------------------------------------------------------------------------
// Graceful stop control
// -----------------------------------------------------------------------------
static EventGroupHandle_t s_ocpp_ctrl = NULL;
#define OCPP_CTRL_STOP_REQ BIT0
#define OCPP_CTRL_STOPPED BIT1

// -----------------------------------------------------------------------------
// Storage init (idempotent)
// -----------------------------------------------------------------------------
static void storage_init_once(void)
{
    static bool s_inited = false;
    if (s_inited)
        return;

    esp_err_t e = storage_service_init();
    if (e != ESP_OK)
    {
        ESP_LOGW(TAG, "storage_service_init failed: %s", esp_err_to_name(e));
        return;
    }
    s_inited = true;
}

// -----------------------------------------------------------------------------
// Energy helper (always store internally as Wh) + validation (FIX 7)
// -----------------------------------------------------------------------------
static inline float energy_to_wh(float v)
{
#if METER_TOTAL_ENERGY_IS_KWH
    return v * 1000.0f;
#else
    return v;
#endif
}

static inline float sanitize_energy_wh(float wh)
{
    if (!isfinite(wh) || wh < 0.0f)
        return 0.0f;

    // Clamp very large values to a sane max to avoid float overflow downstream.
    // Choose a conservative ceiling: 1e12 Wh = 1e9 kWh (way above any EVSE lifetime counter)
    if (wh > 1.0e12f)
        return 1.0e12f;

    return wh;
}

// -----------------------------------------------------------------------------
// BootNotification credentials builder (JSON for ocpp_initialize_full)
// -----------------------------------------------------------------------------
static char s_boot_creds[320];

static void build_boot_creds_v16(char *out, size_t out_sz,
                                 const char *model, const char *vendor,
                                 const char *fw, const char *sn)
{
    snprintf(out, out_sz,
             "{"
             "\"chargePointModel\":\"%s\","
             "\"chargePointVendor\":\"%s\","
             "\"firmwareVersion\":\"%s\","
             "\"chargePointSerialNumber\":\"%s\""
             "}",
             model ? model : "",
             vendor ? vendor : "",
             fw ? fw : "",
             sn ? sn : "");
}

static void build_fw_sn(char *fw_out, size_t fw_sz, char *sn_out, size_t sn_sz)
{
    snprintf(fw_out, fw_sz, "FW-PLXV1.0");

    uint8_t mac[6] = {0};
    esp_read_mac(mac, ESP_MAC_WIFI_STA);
    snprintf(sn_out, sn_sz, "%02X%02X%02X%02X%02X%02X",
             mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
}

// -----------------------------------------------------------------------------
// Cleanup (called from OCPP task for graceful stop or from stop fallback)
// FIX (4): set stopping/enabled/conn early so handlers bail out ASAP
// FIX (5): mg_mgr_free only if mgr_inited
// -----------------------------------------------------------------------------
static void ocpp_cleanup_internal(void)
{
    ESP_LOGI(TAG, "OCPP cleanup");

    // Cut fast for handlers
    s_stopping = true;
    enabled = false;

    // Ensure handlers see "not connected" ASAP
    OCPP_Connection *conn = g_ocpp_conn;
    g_ocpp_conn = NULL;

    ocpp_deinitialize();

    if (conn)
    {
        ocpp_deinitConnection(conn);
    }

    if (s_mgr_inited)
    {
        mg_mgr_free(&mgr);
        s_mgr_inited = false;
    }

    // Unregister handlers (best-effort)
    if (s_auth_verify_inst)
    {
        esp_err_t e = esp_event_handler_instance_unregister(
            AUTH_EVENTS, AUTH_EVENT_TAG_VERIFY, s_auth_verify_inst);
        if (e != ESP_OK)
            ESP_LOGW(TAG, "unregister AUTH_EVENT_TAG_VERIFY failed: %s", esp_err_to_name(e));
        s_auth_verify_inst = NULL;
    }

    if (s_evse_state_inst)
    {
        esp_err_t e = esp_event_handler_instance_unregister(
            EVSE_EVENTS, EVSE_EVENT_STATE_CHANGED, s_evse_state_inst);
        if (e != ESP_OK)
            ESP_LOGW(TAG, "unregister EVSE_EVENT_STATE_CHANGED failed: %s", esp_err_to_name(e));
        s_evse_state_inst = NULL;
    }

    if (s_evse_enable_inst)
    {
        esp_err_t e = esp_event_handler_instance_unregister(
            EVSE_EVENTS, EVSE_EVENT_ENABLE_UPDATED, s_evse_enable_inst);
        if (e != ESP_OK)
            ESP_LOGW(TAG, "unregister EVSE_EVENT_ENABLE_UPDATED failed: %s", esp_err_to_name(e));
        s_evse_enable_inst = NULL;
    }

    if (s_evse_available_inst)
    {
        esp_err_t e = esp_event_handler_instance_unregister(
            EVSE_EVENTS, EVSE_EVENT_AVAILABLE_UPDATED, s_evse_available_inst);
        if (e != ESP_OK)
            ESP_LOGW(TAG, "unregister EVSE_EVENT_AVAILABLE_UPDATED failed: %s", esp_err_to_name(e));
        s_evse_available_inst = NULL;
    }

    if (s_meter_inst)
    {
        esp_err_t e = esp_event_handler_instance_unregister(
            METER_EVENT, METER_EVENT_DATA_READY, s_meter_inst);
        if (e != ESP_OK)
            ESP_LOGW(TAG, "unregister METER_EVENT_DATA_READY failed: %s", esp_err_to_name(e));
        s_meter_inst = NULL;
    }

    // Reset runtime flags/cache
    s_remote_operative = true;

    portENTER_CRITICAL(&s_meter_mux);
    memset(&s_meter, 0, sizeof(s_meter));
    portEXIT_CRITICAL(&s_meter_mux);

    // Allow future starts
    s_stopping = false;
}

// -----------------------------------------------------------------------------
// Storage GETs (sync) - config read only
// -----------------------------------------------------------------------------
bool ocpp_get_enabled(void)
{
    storage_init_once();

    uint8_t value = 0;
    esp_err_t err = storage_get_u8_sync(NVS_NAMESPACE, NVS_OCPP_ENABLED, &value, STORE_TO);

    if (err == ESP_ERR_NOT_FOUND)
        return false;
    if (err != ESP_OK)
    {
        ESP_LOGW(TAG, "storage_get_u8_sync(enabled) failed: %s", esp_err_to_name(err));
        return false;
    }
    return value != 0;
}

void ocpp_get_server(char *value /* out, size>=64 */)
{
    if (!value)
        return;
    value[0] = '\0';

    storage_init_once();

    esp_err_t e = storage_get_str_sync(NVS_NAMESPACE, NVS_OCPP_SERVER, value, 64, STORE_TO);
    if (e == ESP_ERR_NOT_FOUND)
    {
        value[0] = '\0';
        return;
    }
    if (e != ESP_OK)
    {
        ESP_LOGW(TAG, "storage_get_str_sync(server) failed: %s", esp_err_to_name(e));
        value[0] = '\0';
        return;
    }
    value[63] = '\0';
}

void ocpp_get_charge_id(char *value /* out, size>=64 */)
{
    if (!value)
        return;
    value[0] = '\0';

    storage_init_once();

    esp_err_t e = storage_get_str_sync(NVS_NAMESPACE, NVS_OCPP_CHARGE_ID, value, 64, STORE_TO);
    if (e == ESP_ERR_NOT_FOUND)
    {
        value[0] = '\0';
        return;
    }
    if (e != ESP_OK)
    {
        ESP_LOGW(TAG, "storage_get_str_sync(charge_id) failed: %s", esp_err_to_name(e));
        value[0] = '\0';
        return;
    }
    value[63] = '\0';
}

// -----------------------------------------------------------------------------
// Storage SETs (async only, per request)
// -----------------------------------------------------------------------------
void ocpp_set_enabled(bool value)
{
    storage_init_once();

    ESP_LOGI(TAG, "set enabled %d", value);

    esp_err_t e = storage_set_u8_async(NVS_NAMESPACE, NVS_OCPP_ENABLED, value ? 1 : 0);
    if (e != ESP_OK)
    {
        ESP_LOGE(TAG, "storage_set_u8_async(enabled) failed: %s", esp_err_to_name(e));
        return;
    }

    // Apply immediately at runtime
    if (value)
        ocpp_start();
    else
        ocpp_stop();
}

void ocpp_set_server(char *value)
{
    storage_init_once();

    ESP_LOGI(TAG, "set server %s", value ? value : "(null)");

    esp_err_t e = storage_set_str_async(NVS_NAMESPACE, NVS_OCPP_SERVER, value ? value : "");
    if (e != ESP_OK)
    {
        ESP_LOGE(TAG, "storage_set_str_async(server) failed: %s", esp_err_to_name(e));
        return;
    }
}

void ocpp_set_charge_id(char *value)
{
    storage_init_once();

    ESP_LOGI(TAG, "set charge_id %s", value ? value : "(null)");

    esp_err_t e = storage_set_str_async(NVS_NAMESPACE, NVS_OCPP_CHARGE_ID, value ? value : "");
    if (e != ESP_OK)
    {
        ESP_LOGE(TAG, "storage_set_str_async(charge_id) failed: %s", esp_err_to_name(e));
        return;
    }
}

// -----------------------------------------------------------------------------
// Event handlers (AUTH / EVSE / METER)
// FIX (4): bail out if stopping or not enabled
// -----------------------------------------------------------------------------
static void ocpp_on_auth_verify(void *arg, esp_event_base_t base, int32_t id, void *event_data)
{
    (void)arg;
    (void)base;
    (void)id;

    if (s_stopping || !enabled || g_ocpp_conn == NULL)
        return;

    const auth_tag_verify_event_t *rq = (const auth_tag_verify_event_t *)event_data;
    if (!rq)
        return;

    char idtag[AUTH_TAG_MAX_LEN];
    if (rq->tag[0] == '\0')
    {
        strncpy(idtag, "IDTAG", sizeof(idtag));
        idtag[sizeof(idtag) - 1] = '\0';
    }
    else
    {
        strncpy(idtag, rq->tag, sizeof(idtag) - 1);
        idtag[sizeof(idtag) - 1] = '\0';
    }

    ESP_LOGI(TAG, "AUTH_EVENT_TAG_VERIFY: tag=%s req_id=%u", idtag, (unsigned)rq->req_id);

    if (ocpp_isTransactionActive())
    {
        ESP_LOGI(TAG, "Transaction active -> ocpp_endTransaction(\"%s\")", idtag);
        (void)ocpp_endTransaction(idtag, "Local");
    }
    else
    {
        ESP_LOGI(TAG, "No active transaction -> ocpp_beginTransaction(\"%s\")", idtag);
        (void)ocpp_beginTransaction(idtag);
    }
}

static void evse_event_handler(void *arg, esp_event_base_t base, int32_t id, void *data)
{
    (void)arg;
    if (base != EVSE_EVENTS || id != EVSE_EVENT_STATE_CHANGED || data == NULL)
        return;

    if (s_stopping)
        return;

    const evse_state_event_data_t *evt = (const evse_state_event_data_t *)data;
    ESP_LOGI(TAG, "EVSE event received: state = %d", (int)evt->state);

    switch (evt->state)
    {
    case EVSE_STATE_EVENT_IDLE:
        s_ev_plugged = false;
        s_ev_ready = false;
        break;

    case EVSE_STATE_EVENT_WAITING:
        s_ev_plugged = true;
        s_ev_ready = false;
        break;

    case EVSE_STATE_EVENT_CHARGING:
        s_ev_plugged = true;
        s_ev_ready = true;
        break;

    case EVSE_STATE_EVENT_FAULT:
    default:
        s_ev_ready = false;
        break;
    }
}

static void evse_enable_available_handler(void *arg, esp_event_base_t base, int32_t id, void *data)
{
    (void)arg;

    if (base != EVSE_EVENTS || data == NULL)
        return;

    if (s_stopping)
        return;

    if (id == EVSE_EVENT_ENABLE_UPDATED)
    {
        const evse_enable_event_data_t *e = (const evse_enable_event_data_t *)data;
        s_evse_enabled_local = e->enabled;
        ESP_LOGI(TAG, "[EVSE] ENABLE_UPDATED: enabled=%d (ts=%lld)",
                 (int)e->enabled, (long long)e->timestamp_us);
        return;
    }

    if (id == EVSE_EVENT_AVAILABLE_UPDATED)
    {
        const evse_available_event_data_t *e = (const evse_available_event_data_t *)data;
        s_evse_available_local = e->available;
        ESP_LOGI(TAG, "[EVSE] AVAILABLE_UPDATED: available=%d (ts=%lld)",
                 (int)e->available, (long long)e->timestamp_us);
        return;
    }
}

static void on_meter_event(void *arg, esp_event_base_t base, int32_t id, void *data)
{
    (void)arg;

    if (base != METER_EVENT || id != METER_EVENT_DATA_READY || !data)
        return;

    if (s_stopping)
        return;

    const meter_event_data_t *evt = (const meter_event_data_t *)data;

    if (!evt->source || strcmp(evt->source, "EVSE") != 0)
        return;

    int32_t sum_w = (int32_t)evt->watt[0] + (int32_t)evt->watt[1] + (int32_t)evt->watt[2];
    float avg_v = (evt->vrms[0] + evt->vrms[1] + evt->vrms[2]) / 3.0f;
    float sum_i = evt->irms[0] + evt->irms[1] + evt->irms[2];

    float total_wh = sanitize_energy_wh(energy_to_wh(evt->total_energy));

    portENTER_CRITICAL(&s_meter_mux);
    memcpy(s_meter.vrms, evt->vrms, sizeof(s_meter.vrms));
    memcpy(s_meter.irms, evt->irms, sizeof(s_meter.irms));
    s_meter.watt[0] = evt->watt[0];
    s_meter.watt[1] = evt->watt[1];
    s_meter.watt[2] = evt->watt[2];
    s_meter.frequency = evt->frequency;
    s_meter.power_factor = evt->power_factor;
    s_meter.total_energy_Wh = total_wh;
    s_meter.sum_watt = sum_w;
    s_meter.avg_voltage = avg_v;
    s_meter.sum_current = sum_i;
    s_meter.have_data = true;
    portEXIT_CRITICAL(&s_meter_mux);
}

// -----------------------------------------------------------------------------
// MicroOCPP Inputs/CBs
// -----------------------------------------------------------------------------
bool setConnectorPluggedInput(void) { return s_ev_plugged; }
bool setEvReadyInput(void) { return s_ev_ready; }

bool setEvseReadyInput(void)
{
    // If OCPP disabled, EVSE not ready from OCPP perspective
    if (!enabled || s_stopping || g_ocpp_conn == NULL)
        return false;

    return s_evse_enabled_local && s_evse_available_local && s_remote_operative;
}

float setPowerMeterInput(void)
{
    int32_t w = 0;
    bool have = false;

    portENTER_CRITICAL(&s_meter_mux);
    have = s_meter.have_data;
    if (have)
        w = s_meter.sum_watt;
    portEXIT_CRITICAL(&s_meter_mux);

    return (float)w;
}

float setEnergyMeterInput(void)
{
    float wh = 0.0f;
    bool have = false;

    portENTER_CRITICAL(&s_meter_mux);
    have = s_meter.have_data;
    if (have)
        wh = s_meter.total_energy_Wh;
    portEXIT_CRITICAL(&s_meter_mux);

    return wh;
}

int setEnergyInput(void)
{
    float wh = setEnergyMeterInput();
    if (!isfinite(wh) || wh < 0.0f)
        wh = 0.0f;

    // clamp to int range defensively
    if (wh > 2147483000.0f)
        wh = 2147483000.0f;

    return (int)lrintf((double)wh);
}

float setCurrentInput(void)
{
    float a = 0.0f;
    bool have = false;

    portENTER_CRITICAL(&s_meter_mux);
    have = s_meter.have_data;
    if (have)
        a = s_meter.sum_current;
    portEXIT_CRITICAL(&s_meter_mux);

    return a;
}

float setVoltageInput(void)
{
    float v = 0.0f;
    bool have = false;

    portENTER_CRITICAL(&s_meter_mux);
    have = s_meter.have_data;
    if (have)
        v = s_meter.avg_voltage;
    portEXIT_CRITICAL(&s_meter_mux);

    return v;
}

float setTemperatureInput(void)
{
    return 16.5f;
}

void setSmartChargingCurrentOutput(float limit)
{
    ESP_LOGI(TAG, "SmartChargingCurrentOutput: %.0f", limit);
    // TODO: apply to EVSE/loadbalancer
}

void setSmartChargingPowerOutput(float limit)
{
    ESP_LOGI(TAG, "SmartChargingPowerOutput: %.0f", limit);
    // TODO: apply to EVSE/loadbalancer
}

void setSmartChargingOutput(float power, float current, int nphases)
{
    ESP_LOGI(TAG, "SmartChargingOutput: P=%.0f W, I=%.0f A, phases=%d", power, current, nphases);
    // TODO: apply to EVSE/loadbalancer
}

void OnResetExecute(bool state)
{
    ESP_LOGI(TAG, "OnResetExecute (state=%d)", state);
    esp_restart();
}

bool setStopTxReadyInput(void) { return true; }

bool setOnResetNotify(bool value)
{
    (void)value;
    return true;
}

// TxNotification callback signature updated: uses TxNotification (new C-facade)
// TxNotification callback signature updated: uses TxNotification (new C-facade)
void notificationOutput(OCPP_Transaction *transaction, TxNotification txNotification)
{
    (void)transaction;

    ESP_LOGI(TAG, "TxNotification: %d", (int)txNotification);

    switch (txNotification)
    {
    case TxNotification_Authorized: // == 1
        ESP_LOGI(TAG, "Authorized");
        (void)esp_event_post(OCPP_EVENTS, OCPP_EVENT_AUTHORIZED, NULL, 0, OCPP_EVENT_POST_TO);
        break;

    case TxNotification_AuthorizationRejected:
        ESP_LOGI(TAG, "AuthorizationRejected");
        (void)esp_event_post(OCPP_EVENTS, OCPP_EVENT_AUTH_REJECTED, NULL, 0, OCPP_EVENT_POST_TO);
        break;

    case TxNotification_AuthorizationTimeout:
        ESP_LOGI(TAG, "AuthorizationTimeout");
        (void)esp_event_post(OCPP_EVENTS, OCPP_EVENT_AUTH_TIMEOUT, NULL, 0, OCPP_EVENT_POST_TO);
        break;

    case TxNotification_ReservationConflict:
        ESP_LOGI(TAG, "ReservationConflict");
        // opcional: criar evento específico se precisares
        break;

    case TxNotification_ConnectionTimeout:
        ESP_LOGI(TAG, "ConnectionTimeout");
        // opcional
        break;

    case TxNotification_DeAuthorized:
        ESP_LOGI(TAG, "DeAuthorized");
        // opcional
        break;

    case TxNotification_RemoteStart:
        ESP_LOGI(TAG, "RemoteStart");
        (void)esp_event_post(OCPP_EVENTS, OCPP_EVENT_REMOTE_START, NULL, 0, OCPP_EVENT_POST_TO);
        break;

    case TxNotification_RemoteStop:
        ESP_LOGI(TAG, "RemoteStop");
        (void)esp_event_post(OCPP_EVENTS, OCPP_EVENT_REMOTE_STOP, NULL, 0, OCPP_EVENT_POST_TO);
        break;

    case TxNotification_StartTx:
        ESP_LOGI(TAG, "StartTx");
        (void)esp_event_post(OCPP_EVENTS, OCPP_EVENT_START_TX, NULL, 0, OCPP_EVENT_POST_TO);
        break;

    case TxNotification_StopTx:
        ESP_LOGI(TAG, "StopTx");
        (void)esp_event_post(OCPP_EVENTS, OCPP_EVENT_STOP_TX, NULL, 0, OCPP_EVENT_POST_TO);
        break;

    case TxNotification_UNDEFINED:
    default:
        ESP_LOGW(TAG, "TxNotification_UNDEFINED/Unknown: %d", (int)txNotification);
        break;
    }
}

bool ocpp_is_connected(void)
{
    return g_ocpp_conn != NULL;
}

const char *addErrorCodeInput(void)
{
    const char *ptr = NULL;
    uint32_t error = evse_get_error();

    if (error & EVSE_ERR_PILOT_FAULT_BIT)
        ptr = "InternalError";
    else if (error & EVSE_ERR_DIODE_SHORT_BIT)
        ptr = "InternalError";
    else if (error & EVSE_ERR_LOCK_FAULT_BIT)
        ptr = "ConnectorLockFailure";
    else if (error & EVSE_ERR_UNLOCK_FAULT_BIT)
        ptr = "ConnectorLockFailure";
    else if (error & EVSE_ERR_RCM_TRIGGERED_BIT)
        ptr = "OtherError";
    else if (error & EVSE_ERR_RCM_SELFTEST_FAULT_BIT)
        ptr = "OtherError";
    else if (error & EVSE_ERR_TEMPERATURE_HIGH_BIT)
        ptr = "HighTemperature";
    else if (error & EVSE_ERR_TEMPERATURE_FAULT_BIT)
        ptr = "OtherError";

    return ptr;
}

// -----------------------------------------------------------------------------
// Task / Main Loop
// FIX (2): pause when !enabled (no mg_mgr_poll/ocpp_loop while disabled)
// -----------------------------------------------------------------------------
static void ocpp_task_func(void *param)
{
    (void)param;

    while (true)
    {
        if (s_ocpp_ctrl && (xEventGroupGetBits(s_ocpp_ctrl) & OCPP_CTRL_STOP_REQ))
            break;

        if (!enabled || s_stopping || g_ocpp_conn == NULL)
        {
            vTaskDelay(pdMS_TO_TICKS(250));
            continue;
        }

        mg_mgr_poll(&mgr, 100);
        ocpp_loop();

        bool operative = ocpp_isOperative();
        if (operative != s_remote_operative)
        {
            s_remote_operative = operative;

            ocpp_operative_event_t ev = {
                .operative = operative,
                .timestamp_us = esp_timer_get_time()};
            (void)esp_event_post(OCPP_EVENTS,
                                 OCPP_EVENT_OPERATIVE_UPDATED,
                                 &ev, sizeof(ev),
                                 OCPP_EVENT_POST_TO);

            ESP_LOGI(TAG, "[OCPP] ChangeAvailability remoto → operative=%d", (int)operative);
        }
    }

    ESP_LOGI(TAG, "OCPP stop requested (task)");

    ocpp_cleanup_internal();

    if (s_ocpp_ctrl)
    {
        xEventGroupClearBits(s_ocpp_ctrl, OCPP_CTRL_STOP_REQ);
        xEventGroupSetBits(s_ocpp_ctrl, OCPP_CTRL_STOPPED);
    }

    ocpp_task = NULL;
    vTaskDelete(NULL);
}

// -----------------------------------------------------------------------------
// Start / Stop OCPP
// -----------------------------------------------------------------------------
void ocpp_start(void)
{
    ESP_LOGI(TAG, "Starting OCPP");

    if (ocpp_task != NULL)
    {
        ESP_LOGW(TAG, "OCPP already running");
        return;
    }

    storage_init_once();

    enabled = ocpp_get_enabled();
    if (!enabled)
    {
        ESP_LOGW(TAG, "OCPP disabled");
        return;
    }

    char serverstr[64] = {0};
    ocpp_get_server(serverstr);
    if (serverstr[0] == '\0')
    {
        ESP_LOGW(TAG, "No OCPP server configured. Skipping connection.");
        enabled = false;
        return;
    }

    char charge_id[64] = {0};
    ocpp_get_charge_id(charge_id);
    if (charge_id[0] == '\0')
    {
        ESP_LOGW(TAG, "No OCPP charge_id configured. Skipping connection.");
        enabled = false;
        return;
    }

    if (!s_ocpp_ctrl)
    {
        s_ocpp_ctrl = xEventGroupCreate();
        if (!s_ocpp_ctrl)
        {
            ESP_LOGE(TAG, "Failed to create OCPP control event group");
            enabled = false;
            return;
        }
    }
    xEventGroupClearBits(s_ocpp_ctrl, OCPP_CTRL_STOP_REQ | OCPP_CTRL_STOPPED);

#if METER_TOTAL_ENERGY_IS_KWH
    ESP_LOGI(TAG, "Meter total_energy unit: kWh (converted to Wh)");
#else
    ESP_LOGI(TAG, "Meter total_energy unit: Wh");
#endif

    // init mongoose manager
    mg_mgr_init(&mgr);
    s_mgr_inited = true;
    mg_log_set(MG_LL_ERROR);

    struct OCPP_FilesystemOpt fsopt = {.use = true, .mount = true, .formatFsOnFail = true};

    g_ocpp_conn = ocpp_makeConnection(&mgr,
                                      serverstr,
                                      charge_id,
                                      "",
                                      "",
                                      fsopt);
    if (!g_ocpp_conn)
    {
        ESP_LOGE(TAG, "ocpp_makeConnection failed");
        if (s_mgr_inited)
        {
            mg_mgr_free(&mgr);
            s_mgr_inited = false;
        }
        enabled = false;
        return;
    }

    // BootNotification credentials JSON (OCPP 1.6)
    char fw[32], sn[32];
    build_fw_sn(fw, sizeof(fw), sn, sizeof(sn));
    build_boot_creds_v16(s_boot_creds, sizeof(s_boot_creds),
                         "EPower M1", "Plixin", fw, sn);

    // new signature includes ocpp201 flag
    ocpp_initialize_full(g_ocpp_conn, s_boot_creds, fsopt, false, s_ocpp201);

    // Inputs / Outputs
    ocpp_setEvReadyInput(&setEvReadyInput);
    ocpp_setEvseReadyInput(&setEvseReadyInput);
    ocpp_setConnectorPluggedInput(&setConnectorPluggedInput);
    ocpp_setOnResetExecute(&OnResetExecute);
    ocpp_setTxNotificationOutput(&notificationOutput);
    ocpp_setStopTxReadyInput(&setStopTxReadyInput);
    ocpp_setOnResetNotify(&setOnResetNotify);

    ocpp_setEnergyMeterInput(&setEnergyInput);

    ocpp_addMeterValueInputFloat(&setCurrentInput, "Current.Import", "A", NULL, NULL);
    ocpp_addMeterValueInputFloat(&getCurrentOffered, "Current.Offered", "A", NULL, NULL);
    ocpp_addMeterValueInputFloat(&setVoltageInput, "Voltage", "V", NULL, NULL);
    ocpp_addMeterValueInputFloat(&setTemperatureInput, "Temperature", "Celsius", NULL, NULL);
    ocpp_addMeterValueInputFloat(&setPowerMeterInput, "Power.Active.Import", "W", NULL, NULL);
    ocpp_addMeterValueInputFloat(&setEnergyMeterInput, "Energy.Active.Import.Register", "Wh", NULL, NULL);

    ocpp_addErrorCodeInput(&addErrorCodeInput);

    // SmartCharging outputs
    ocpp_setSmartChargingCurrentOutput(&setSmartChargingCurrentOutput);
    ocpp_setSmartChargingPowerOutput(&setSmartChargingPowerOutput);
    ocpp_setSmartChargingOutput(&setSmartChargingOutput);

    // Register event handlers before starting task
    if (!s_auth_verify_inst)
    {
        ESP_ERROR_CHECK(esp_event_handler_instance_register(
            AUTH_EVENTS, AUTH_EVENT_TAG_VERIFY,
            &ocpp_on_auth_verify, NULL, &s_auth_verify_inst));
        ESP_LOGI(TAG, "Registered AUTH_EVENT_TAG_VERIFY listener");
    }

    if (!s_evse_state_inst)
    {
        ESP_ERROR_CHECK(esp_event_handler_instance_register(
            EVSE_EVENTS, EVSE_EVENT_STATE_CHANGED,
            &evse_event_handler, NULL, &s_evse_state_inst));
    }

    if (!s_evse_enable_inst)
    {
        ESP_ERROR_CHECK(esp_event_handler_instance_register(
            EVSE_EVENTS, EVSE_EVENT_ENABLE_UPDATED,
            &evse_enable_available_handler, NULL, &s_evse_enable_inst));
    }

    if (!s_evse_available_inst)
    {
        ESP_ERROR_CHECK(esp_event_handler_instance_register(
            EVSE_EVENTS, EVSE_EVENT_AVAILABLE_UPDATED,
            &evse_enable_available_handler, NULL, &s_evse_available_inst));
    }

    if (!s_meter_inst)
    {
        ESP_ERROR_CHECK(esp_event_handler_instance_register(
            METER_EVENT, METER_EVENT_DATA_READY,
            &on_meter_event, NULL, &s_meter_inst));
        ESP_LOGI(TAG, "Registered METER_EVENT_DATA_READY listener (EVSE source)");
    }

    // Clear stopping flag for runtime
    s_stopping = false;

    BaseType_t rc = xTaskCreate(ocpp_task_func, "ocpp_task", 5 * 1024, NULL, 3, &ocpp_task);
    if (rc != pdPASS)
    {
        ESP_LOGE(TAG, "Failed to create ocpp_task");
        ocpp_task = NULL;
        ocpp_cleanup_internal();
        if (s_ocpp_ctrl)
            xEventGroupSetBits(s_ocpp_ctrl, OCPP_CTRL_STOPPED);
        return;
    }
}

void ocpp_stop(void)
{
    ESP_LOGI(TAG, "Stopping OCPP");

    // Set stopping early so handlers stop doing work ASAP
    s_stopping = true;
    enabled = false;

    if (ocpp_task == NULL)
    {
        // Defensive: if conn exists without task, cleanup now
        if (g_ocpp_conn != NULL || s_mgr_inited)
        {
            ESP_LOGW(TAG, "ocpp_task NULL but resources exist; forcing cleanup");
            ocpp_cleanup_internal();
        }
        return;
    }

    if (!s_ocpp_ctrl)
    {
        // No control group -> fallback (rare)
        ESP_LOGW(TAG, "No control group; forcing stop");
        vTaskDelete(ocpp_task);
        ocpp_task = NULL;
        ocpp_cleanup_internal();
        return;
    }

    // Graceful stop: ask task to stop and wait it to finish cleanup
    xEventGroupClearBits(s_ocpp_ctrl, OCPP_CTRL_STOPPED);
    xEventGroupSetBits(s_ocpp_ctrl, OCPP_CTRL_STOP_REQ);

    EventBits_t bits = xEventGroupWaitBits(
        s_ocpp_ctrl,
        OCPP_CTRL_STOPPED,
        pdTRUE,
        pdTRUE,
        OCPP_STOP_WAIT_TO);

    if (!(bits & OCPP_CTRL_STOPPED))
    {
        ESP_LOGW(TAG, "Timeout waiting OCPP stop; forcing task delete");
        vTaskDelete(ocpp_task);
        ocpp_task = NULL;
        ocpp_cleanup_internal();
    }
}
