// components/evse_link/src/evse_link_slave.c
//
// Correções aplicadas:
// 1) Evitar TX dentro do callback RX: confirmação (heartbeat) é deferida via queue/task.
// 2) Proteger safe_mode (e flags relacionadas) com mux (evita race entre RX e timer).
// 3) Opção de política no fallback: por default faz PAUSE (mais seguro). Pode ser alterado por macro.
// 4) Reduzir ruído de logs em caminho quente (RX frames em DEBUG).
// 5) Manter semântica: só sai de safe_mode com comando explícito de potência (SET_CURRENT / RESUME).
// 6) Mantém lógica de pause SET_CURRENT=0 e resume quando >0.
//
// Nota: A enum do evse_state_event_data_t que enviaste está correta para o handler.

#include "evse_link.h"
#include "evse_link_events.h"

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "freertos/queue.h"
#include "freertos/portmacro.h"

#include "esp_log.h"
#include "esp_event.h"
#include "esp_random.h"
#include "esp_err.h"

#include "evse_events.h"
#include "evse_state.h"
#include "evse_config.h"

#include <stdint.h>
#include <stdbool.h>
#include <string.h>

static const char *TAG = "evse_link_slave";

#define MASTER_ID 0x01

// Commands (opcode no payload[0])
#define CMD_POLL 0x01
#define CMD_HEARTBEAT 0x02
#define CMD_CONFIG_BROADCAST 0x03
#define CMD_SET_CURRENT 0x08
#define CMD_HEARTBEAT_ACK 0x09
#define CMD_AUTH_GRANTED 0x0A
// #define CMD_RESUME         0x0B   // se existir

// lengths (INCLUI opcode)
#define LEN_SET_CURRENT 3

#define FALLBACK_TIMEOUT_MS 180000

// --- Política de fallback ---
// 1 = mais seguro: PAUSE (revoga autorização)
// 0 = força corrente mínima (pode continuar a carregar dependendo do core)
#ifndef EVSE_LINK_FALLBACK_PAUSE
#define EVSE_LINK_FALLBACK_PAUSE 1
#endif

// --- Confirmações via heartbeat (deferidas) ---
#define HB_REQ_QUEUE_LEN 8

typedef enum
{
    HB_REQ_SEND = 1,
} hb_req_t;

static TimerHandle_t fallback_timer = NULL;
static TaskHandle_t hb_task_handle = NULL;

// Task para enviar heartbeat sem bloquear RX callback
static TaskHandle_t hb_sender_task_handle = NULL;
static QueueHandle_t hb_req_q = NULL;

// "safe mode" (master offline): aplica fallback local e nao sai com POLL/ACK.
// Só sai com comando explícito de potência (SET_CURRENT / RESUME).
static bool safe_mode = false;
static uint16_t saved_runtime_limit = 0; // informativo

// "remote pause" (SET_CURRENT=0)
static bool paused_by_master = false;
static bool paused_prev_authorized = false;

static portMUX_TYPE s_state_mux = portMUX_INITIALIZER_UNLOCKED;

static bool evse_handler_registered = false;

static size_t bounded_strlen_u8(const uint8_t *s, size_t max_len)
{
    size_t i = 0;
    if (!s)
        return 0;
    while (i < max_len && s[i] != 0)
        i++;
    return i;
}

static void send_heartbeat_frame_now(void)
{
    bool charging = evse_state_is_charging(evse_get_state());
    uint16_t hw_max = evse_get_max_charging_current();
    uint16_t runtime = evse_get_runtime_charging_current();

    uint8_t hb[] = {
        CMD_HEARTBEAT,
        charging ? 1 : 0,
        (uint8_t)(hw_max & 0xFF), (uint8_t)(hw_max >> 8),
        (uint8_t)(runtime & 0xFF), (uint8_t)(runtime >> 8)};

    (void)evse_link_send(MASTER_ID, hb, sizeof(hb)); // UNICAST
    ESP_LOGI(TAG, "Send Heartbeat Frame");
}

// pede heartbeat sem bloquear quem chama (RX callback, event handler, etc.)
static void request_heartbeat_send(void)
{
    if (hb_req_q)
    {
        hb_req_t req = HB_REQ_SEND;
        // não bloqueia; se encher, apenas ignora (heartbeat periódico já existe)
        (void)xQueueSendToBack(hb_req_q, &req, 0);
    }
    else
    {
        // fallback: se queue ainda não existe, manda direto
        send_heartbeat_frame_now();
    }
}

static void hb_sender_task(void *arg)
{
    (void)arg;

    hb_req_t req;
    for (;;)
    {
        if (xQueueReceive(hb_req_q, &req, portMAX_DELAY) != pdTRUE)
            continue;

        if (req == HB_REQ_SEND)
        {
            send_heartbeat_frame_now();
        }
    }
}

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;

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

    // Enforce pause: se algo tentar voltar a carregar enquanto paused_by_master, revoga auth.
    bool paused;
    portENTER_CRITICAL(&s_state_mux);
    paused = paused_by_master;
    portEXIT_CRITICAL(&s_state_mux);

    if (paused && evt->state == EVSE_STATE_EVENT_CHARGING)
    {
        // Garante que não continua a carregar por clamp/auto-auth
        evse_state_set_authorized(false);
    }

    // Envia heartbeat quando entra em IDLE ou CHARGING (estado relevante)
    if (evt->state == EVSE_STATE_EVENT_IDLE || evt->state == EVSE_STATE_EVENT_CHARGING)
        request_heartbeat_send();
}

// Sai de safe-mode APENAS com comando explícito (SET_CURRENT / RESUME).
static void maybe_exit_safe_mode_on_explicit_power_cmd(uint8_t cmd)
{
    bool in_safe;
    portENTER_CRITICAL(&s_state_mux);
    in_safe = safe_mode;
    portEXIT_CRITICAL(&s_state_mux);

    if (!in_safe)
        return;

    if (cmd == CMD_SET_CURRENT /*|| cmd == CMD_RESUME*/)
    {
        portENTER_CRITICAL(&s_state_mux);
        safe_mode = false;
        portEXIT_CRITICAL(&s_state_mux);

        ESP_LOGI(TAG, "Exiting safe mode due to explicit cmd 0x%02X", cmd);
    }
}

static void apply_pause_by_master(void)
{
    bool prev_auth = evse_state_get_authorized();

    portENTER_CRITICAL(&s_state_mux);
    paused_by_master = true;
    paused_prev_authorized = prev_auth;
    portEXIT_CRITICAL(&s_state_mux);

    // Revoga autorização para parar contactor/pilot via core
    if (prev_auth)
        evse_state_set_authorized(false);

    // Mantém runtime num valor seguro (não 0, pois clamp -> 6A).
    // O "pause" efetivo é pela autorização=false.
    evse_set_runtime_charging_current(MIN_CHARGING_CURRENT_LIMIT);
}

static void clear_pause_by_master_if_any(void)
{
    bool was_paused;
    bool prev_auth;

    portENTER_CRITICAL(&s_state_mux);
    was_paused = paused_by_master;
    prev_auth = paused_prev_authorized;
    paused_by_master = false;
    paused_prev_authorized = false;
    portEXIT_CRITICAL(&s_state_mux);

    // Se estava autorizado antes do pause, tenta reautorizar
    if (was_paused && prev_auth)
        evse_state_set_authorized(true);
}

static void on_frame_slave(uint8_t src, uint8_t dest,
                           const uint8_t *payload, uint8_t len)
{
    const uint8_t self = evse_link_get_self_id();

    // Muito verboso em caminho quente; deixa em DEBUG
    ESP_LOGD(TAG, "RX frames (src=0x%02X dest=0x%02X len=%u self=0x%02X)", src, dest, len, self);

    if (src == self)
        return;

    if (dest != self && dest != 0xFF)
        return;

    if (payload == NULL || len < 1)
    {
        ESP_LOGW(TAG, "RX invalid: payload NULL or len<1 (len=%u)", len);
        return;
    }

    // Só aceitar comandos do master
    if (src != MASTER_ID)
    {
        ESP_LOGW(TAG, "RX ignore: non-master src=0x%02X", src);
        return;
    }

    // Qualquer frame válido do master => link vivo (reset do fallback)
    if (fallback_timer)
        (void)xTimerReset(fallback_timer, 0);

    uint8_t cmd = payload[0];

    switch (cmd)
    {
    case CMD_POLL:
        // Liveness only. Não sai de safe-mode e não restaura limites.
        ESP_LOGI(TAG, "CMD_POLL from 0x%02X", src);
        break;

    case CMD_CONFIG_BROADCAST:
        ESP_LOGI(TAG, "CMD_CONFIG_BROADCAST from 0x%02X", src);
        break;

    case CMD_HEARTBEAT_ACK:
        ESP_LOGI(TAG, "HEARTBEAT_ACK from 0x%02X", src);
        break;

    case CMD_SET_CURRENT:
    {
        ESP_LOGI(TAG, "SET_CURRENT from 0x%02X", src);

        if (len < LEN_SET_CURRENT)
        {
            ESP_LOGW(TAG, "SET_CURRENT invalid len=%u from 0x%02X", len, src);
            break;
        }

        uint16_t amps = (uint16_t)(payload[1] | ((uint16_t)payload[2] << 8));

        // Comando explícito => pode sair de safe-mode (mesmo se for pause)
        maybe_exit_safe_mode_on_explicit_power_cmd(cmd);

        if (amps == 0)
        {
            // PAUSE explícito
            ESP_LOGI(TAG, "SET_CURRENT=0 => PAUSE (src=0x%02X)", src);
            apply_pause_by_master();

            // Confirma sem bloquear RX
            request_heartbeat_send();

            // Publica evento com 0A (semântica: pause)
            (void)esp_event_post(EVSE_LINK_EVENTS, LINK_EVENT_CURRENT_LIMIT_APPLIED,
                                 &amps, sizeof(amps), portMAX_DELAY);
            break;
        }

        clear_pause_by_master_if_any();

        evse_set_runtime_charging_current(amps);

        // confirma sem bloquear RX
        request_heartbeat_send();

        ESP_LOGI(TAG, "Applied runtime limit: %uA from 0x%02X", (unsigned)amps, src);

        (void)esp_event_post(EVSE_LINK_EVENTS, LINK_EVENT_CURRENT_LIMIT_APPLIED,
                             &amps, sizeof(amps), portMAX_DELAY);
        break;
    }

    case CMD_AUTH_GRANTED:
    {
        if (len < 2)
        {
            ESP_LOGW(TAG, "AUTH_GRANTED invalid len=%u from 0x%02X", len, src);
            break;
        }

        const uint8_t *tag_ptr = &payload[1];
        size_t tag_buf_len = (size_t)(len - 1);
        size_t tag_len = bounded_strlen_u8(tag_ptr, tag_buf_len);

        evse_link_auth_grant_event_t ev = {0};
        size_t copy_len = tag_len;
        if (copy_len > (EVSE_LINK_TAG_MAX_LEN - 1))
            copy_len = EVSE_LINK_TAG_MAX_LEN - 1;

        if (copy_len > 0)
            memcpy(ev.tag, tag_ptr, copy_len);
        ev.tag[copy_len] = '\0';

        // AUTH_GRANTED não deve sair de safe-mode automaticamente
        ESP_LOGI(TAG, "AUTH_GRANTED from 0x%02X tag='%s'", src, ev.tag);

        esp_err_t err = esp_event_post(EVSE_LINK_EVENTS,
                                       LINK_EVENT_REMOTE_AUTH_GRANTED,
                                       &ev, sizeof(ev),
                                       portMAX_DELAY);
        if (err != ESP_OK)
            ESP_LOGE(TAG, "Failed to post LINK_EVENT_REMOTE_AUTH_GRANTED: %s", esp_err_to_name(err));
        break;
    }

    default:
        ESP_LOGW(TAG, "Unknown cmd 0x%02X from 0x%02X", cmd, src);
        break;
    }
}

static void slave_heartbeat_task(void *arg)
{
    (void)arg;

    const uint32_t period_ms = 60000;
    uint8_t id = evse_link_get_self_id();

    // desfasamento por ID: 2s, 4s, 6s...
    vTaskDelay(pdMS_TO_TICKS((uint32_t)id * 2000));

    for (;;)
    {
        request_heartbeat_send();

        // jitter opcional
        uint32_t jitter_ms = esp_random() % 201; // 0..200ms
        vTaskDelay(pdMS_TO_TICKS(period_ms + jitter_ms));
    }
}

static void fallback_timer_cb(TimerHandle_t xTimer)
{
    (void)xTimer;

    // entra safe_mode uma vez
    bool already_safe;
    portENTER_CRITICAL(&s_state_mux);
    already_safe = safe_mode;
    if (!safe_mode)
        safe_mode = true;
    portEXIT_CRITICAL(&s_state_mux);

    if (already_safe)
        return;

    saved_runtime_limit = evse_get_runtime_charging_current();

#if EVSE_LINK_FALLBACK_PAUSE
    ESP_LOGW(TAG, "Fallback timeout: entering safe mode (saved %uA). Policy=PAUSE",
             (unsigned)saved_runtime_limit);

    // pausar é mais seguro quando o master “morre”
    apply_pause_by_master();
#else
    ESP_LOGW(TAG, "Fallback timeout: entering safe mode (saved %uA, forcing %uA). Policy=MIN",
             (unsigned)saved_runtime_limit, (unsigned)MIN_CHARGING_CURRENT_LIMIT);

    evse_set_runtime_charging_current(MIN_CHARGING_CURRENT_LIMIT);
#endif

    (void)esp_event_post(EVSE_LINK_EVENTS, LINK_EVENT_SLAVE_OFFLINE,
                         NULL, 0, portMAX_DELAY);

    // opcional: manda heartbeat para indicar estado atual
    request_heartbeat_send();
}

void evse_link_slave_init(void)
{
    if (evse_link_get_mode() != EVSE_LINK_MODE_SLAVE || !evse_link_is_enabled())
        return;

    ESP_LOGI(TAG, "Initializing SLAVE mode (ID=0x%02X)", evse_link_get_self_id());

    evse_link_register_rx_cb(on_frame_slave);

    // cria queue/task do sender (para não mandar UART TX no callback RX)
    if (hb_req_q == NULL)
    {
        hb_req_q = xQueueCreate(HB_REQ_QUEUE_LEN, sizeof(hb_req_t));
        if (!hb_req_q)
        {
            ESP_LOGE(TAG, "Failed to create HB request queue (fallback to direct send)");
        }
        else
        {
            if (xTaskCreate(hb_sender_task, "hb_sender", 3072, NULL, 3, &hb_sender_task_handle) != pdPASS)
            {
                ESP_LOGE(TAG, "Failed to create hb_sender task");
                vQueueDelete(hb_req_q);
                hb_req_q = NULL;
                hb_sender_task_handle = NULL;
            }
        }
    }

    if (hb_task_handle == NULL)
    {
        if (xTaskCreate(slave_heartbeat_task, "slave_hb", 4096, NULL, 3, &hb_task_handle) != pdPASS)
        {
            ESP_LOGE(TAG, "Failed to create slave_heartbeat_task");
            hb_task_handle = NULL;
        }
    }

    if (fallback_timer == NULL)
    {
        fallback_timer = xTimerCreate("fallback_tmr",
                                      pdMS_TO_TICKS(FALLBACK_TIMEOUT_MS),
                                      pdFALSE, NULL,
                                      fallback_timer_cb);
        if (fallback_timer)
            (void)xTimerStart(fallback_timer, 0);
        else
            ESP_LOGE(TAG, "Failed to create fallback timer");
    }
    else
    {
        (void)xTimerReset(fallback_timer, 0);
    }

    if (!evse_handler_registered)
    {
        ESP_ERROR_CHECK(esp_event_handler_register(
            EVSE_EVENTS, EVSE_EVENT_STATE_CHANGED,
            evse_event_handler, NULL));
        evse_handler_registered = true;
    }
}
