// components/evse_link/src/evse_link_master.c
//
// Correções aplicadas:
// 1) Evitar TX dentro do callback RX (ACK deferido para task -> não bloqueia RX)
// 2) Dedupe de ACK por slave (evita rajadas se RX vier em chunks / queue acumular)
// 3) Log quando ACK queue enche (antes era silencioso)
// 4) Proteção concorrente no s_presence (int64_t não é atómico no ESP32 32-bit)
// 5) Comentário do poll corrigido + opção de jitter no ACK
//
// NOTA: Mantive o teu comportamento (POLL a cada 10s). Se quiseres, muda para 30s.

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

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

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

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

#include "loadbalancer_events.h"
#include "auth_events.h"

static const char *TAG = "evse_link_master";

// Link 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

// payload lengths (INCLUI opcode)
#define LEN_HEARTBEAT 6        // [ CMD_HEARTBEAT, charging, hw_max_lo, hw_max_hi, run_lo, run_hi ]
#define LEN_CONFIG_BROADCAST 2 // [ CMD_CONFIG_BROADCAST, ... ]
#define LEN_SET_CURRENT 3      // [ CMD_SET_CURRENT, amps_lo, amps_hi ]

// Presence monitoring
#define PRESENCE_CHECK_MS 5000
#define SLAVE_OFFLINE_TIMEOUT_MS 180000

// ACK defer
#define ACK_QUEUE_LEN 16
// backoff para espaçar ACK (evita burst)
// antes era 0..15ms; aqui fica ligeiramente mais suave.
#define ACK_BACKOFF_MIN_MS 5
#define ACK_BACKOFF_MAX_MS 35 // jitter 5..35ms

typedef struct
{
    int64_t last_seen_us;
    bool online;
} slave_presence_t;

static slave_presence_t s_presence[256];
static TimerHandle_t s_presence_timer = NULL;

// Proteção concorrente (int64_t não é atómico no ESP32)
static portMUX_TYPE s_presence_mux = portMUX_INITIALIZER_UNLOCKED;

typedef struct
{
    TimerHandle_t timer;
    TickType_t interval;
} timer_def_t;

// POLL a cada 60s (comentário corrigido)
static timer_def_t poll_timer = {.timer = NULL, .interval = pdMS_TO_TICKS(60000)};

static bool s_handlers_registered = false;

// ACK task/queue
static QueueHandle_t s_ack_q = NULL;
static TaskHandle_t s_ack_task = NULL;
// Dedupe: 1 ACK pendente por slave
static bool s_ack_pending[256] = {0};

static void post_presence_event(evse_link_event_t evt, uint8_t slave_id)
{
    evse_link_slave_presence_event_t p = {.slave_id = slave_id};
    (void)esp_event_post(EVSE_LINK_EVENTS, evt, &p, sizeof(p), portMAX_DELAY);
}

static void mark_slave_seen(uint8_t slave_id)
{
    const int64_t now = esp_timer_get_time();

    bool was_offline = false;

    portENTER_CRITICAL(&s_presence_mux);
    slave_presence_t *p = &s_presence[slave_id];
    p->last_seen_us = now;

    if (!p->online)
    {
        p->online = true;
        was_offline = true;
    }
    portEXIT_CRITICAL(&s_presence_mux);

    if (was_offline)
    {
        ESP_LOGI(TAG, "Slave 0x%02X ONLINE", slave_id);
        post_presence_event(LINK_EVENT_SLAVE_ONLINE, slave_id);
    }
}

static void presence_timer_cb(TimerHandle_t xTimer)
{
    (void)xTimer;
    const int64_t now = esp_timer_get_time();
    const int64_t timeout_us = (int64_t)SLAVE_OFFLINE_TIMEOUT_MS * 1000;

    const uint8_t self = evse_link_get_self_id();

    for (int i = 0; i < 256; ++i)
    {
        if ((uint8_t)i == self)
            continue;

        bool online;
        int64_t last_seen;

        portENTER_CRITICAL(&s_presence_mux);
        online = s_presence[i].online;
        last_seen = s_presence[i].last_seen_us;
        portEXIT_CRITICAL(&s_presence_mux);

        if (!online)
            continue;

        if (last_seen > 0 && (now - last_seen) > timeout_us)
        {
            portENTER_CRITICAL(&s_presence_mux);
            s_presence[i].online = false;
            portEXIT_CRITICAL(&s_presence_mux);

            ESP_LOGW(TAG, "Slave 0x%02X OFFLINE (no heartbeat for %d ms)", i, SLAVE_OFFLINE_TIMEOUT_MS);
            post_presence_event(LINK_EVENT_SLAVE_OFFLINE, (uint8_t)i);
        }
    }
}

// Enfileira ACK sem duplicar por slave
static void enqueue_ack(uint8_t slave_id)
{
    if (!s_ack_q)
        return;

    // Dedupe: se já existe ACK pendente para este slave, não enfileira outro
    if (s_ack_pending[slave_id])
        return;

    s_ack_pending[slave_id] = true;

    if (xQueueSendToBack(s_ack_q, &slave_id, 0) != pdTRUE)
    {
        s_ack_pending[slave_id] = false;
        ESP_LOGW(TAG, "ACK queue full, dropping ACK for 0x%02X", slave_id);
    }
}

// --- ACK task (não bloqueia RX) ---
static void ack_task(void *arg)
{
    (void)arg;

    for (;;)
    {
        uint8_t slave_id = 0;
        if (xQueueReceive(s_ack_q, &slave_id, portMAX_DELAY) != pdTRUE)
            continue;

        // libera dedupe
        s_ack_pending[slave_id] = false;

        // backoff com jitter
        uint32_t backoff = ACK_BACKOFF_MIN_MS +
                           (esp_random() % (ACK_BACKOFF_MAX_MS - ACK_BACKOFF_MIN_MS + 1));
        vTaskDelay(pdMS_TO_TICKS(backoff));

        uint8_t ack[] = {CMD_HEARTBEAT_ACK};
        bool ok = evse_link_send(slave_id, ack, sizeof(ack));
        ESP_LOGI(TAG, "CMD_HEARTBEAT_ACK to 0x%02X ok=%d", slave_id, ok);
    }
}

// --- Send new limit to slave ---
static void on_new_limit(void *arg, esp_event_base_t base, int32_t id, void *data)
{
    (void)arg;
    (void)base;

    if (id != LOADBALANCER_EVENT_SLAVE_CURRENT_LIMIT || data == NULL)
        return;

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

    uint8_t slave_id = evt->slave_id;
    uint16_t max_current = evt->max_current;

    uint8_t buf[LEN_SET_CURRENT] = {
        CMD_SET_CURRENT,
        (uint8_t)(max_current & 0xFF),
        (uint8_t)(max_current >> 8)};

    (void)evse_link_send(slave_id, buf, sizeof(buf));
    ESP_LOGI(TAG, "Sent SET_CURRENT to 0x%02X: %uA", slave_id, (unsigned)max_current);
}

// --- Bridge AUTH -> EVSE-Link ---
static void on_auth_result(void *arg, esp_event_base_t base, int32_t id, void *data)
{
    (void)arg;

    if (base != AUTH_EVENTS || id != AUTH_EVENT_TAG_PROCESSED || data == NULL)
        return;

    const auth_tag_event_data_t *ev = (const auth_tag_event_data_t *)data;

    if (!ev->authorized)
    {
        ESP_LOGI(TAG, "Tag %s not authorized, not propagating to slaves", ev->tag);
        return;
    }

    uint8_t buf[1 + EVSE_LINK_TAG_MAX_LEN];
    buf[0] = CMD_AUTH_GRANTED;

    // Copia tag e garante NUL
    strncpy((char *)&buf[1], ev->tag, EVSE_LINK_TAG_MAX_LEN - 1);
    ((char *)&buf[1])[EVSE_LINK_TAG_MAX_LEN - 1] = '\0';

    // Payload inclui opcode + string + NUL
    uint8_t payload_len = 1 + (uint8_t)(strlen((char *)&buf[1]) + 1);

    (void)evse_link_send(0xFF, buf, payload_len);
    ESP_LOGI(TAG, "Sent CMD_AUTH_GRANTED (broadcast) tag=%s", (char *)&buf[1]);
}

// --- Polling broadcast callback ---
static void poll_timer_cb(TimerHandle_t xTimer)
{
    (void)xTimer;

    uint8_t poll[] = {CMD_POLL};
    bool ok = evse_link_send(0xFF, poll, sizeof(poll));
    ESP_LOGI(TAG, "POLL send ok=%d", ok);
}

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

    // ignora eco do próprio master e frames que não são para nós nem broadcast
    if (src == self)
        return;
    if (dest != self && dest != 0xFF)
        return;

    if (payload == NULL || len < 1)
        return;

    uint8_t cmd = payload[0];

    switch (cmd)
    {
    case CMD_HEARTBEAT:
    {
        ESP_LOGD(TAG, "HEARTBEAT from 0x%02X: %u bytes", src, len);

        if (len != LEN_HEARTBEAT)
        {
            ESP_LOGW(TAG, "HEARTBEAT len invalid from 0x%02X: %u bytes", src, len);
            return;
        }

        bool charging = payload[1] != 0;
        uint16_t hw_max = (uint16_t)(payload[2] | ((uint16_t)payload[3] << 8));
        uint16_t runtime = (uint16_t)(payload[4] | ((uint16_t)payload[5] << 8));

        mark_slave_seen(src);

        loadbalancer_slave_status_event_t status = {
            .slave_id = src,
            .charging = charging,
            .hw_max_current = (float)hw_max,
            .runtime_current = (float)runtime,
            .timestamp_us = esp_timer_get_time()};

        (void)esp_event_post(LOADBALANCER_EVENTS,
                             LOADBALANCER_EVENT_SLAVE_STATUS,
                             &status, sizeof(status), portMAX_DELAY);

        // ACK deferido e deduplicado
        enqueue_ack(src);
        break;
    }

    case CMD_CONFIG_BROADCAST:
        if (len >= LEN_CONFIG_BROADCAST)
            ESP_LOGI(TAG, "Slave 0x%02X acked CONFIG_BROADCAST: new_max=%uA", src, payload[1]);
        else
            ESP_LOGW(TAG, "CONFIG_BROADCAST ack short len=%u from 0x%02X", len, src);
        break;

    default:
        ESP_LOGD(TAG, "Cmd 0x%02X from 0x%02X (ignored/unknown)", cmd, src);
        break;
    }
}

void evse_link_master_init(void)
{
    if (evse_link_get_mode() != EVSE_LINK_MODE_MASTER || !evse_link_is_enabled())
        return;

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

    evse_link_register_rx_cb(on_frame_master);

    // Cria queue/task de ACK uma vez
    if (s_ack_q == NULL)
    {
        s_ack_q = xQueueCreate(ACK_QUEUE_LEN, sizeof(uint8_t));
        if (!s_ack_q)
        {
            ESP_LOGE(TAG, "Failed to create ACK queue");
        }
        else
        {
            if (xTaskCreate(ack_task, "evse_ack", 4096, NULL, 4, &s_ack_task) != pdPASS)
            {
                ESP_LOGE(TAG, "Failed to create ACK task");
                vQueueDelete(s_ack_q);
                s_ack_q = NULL;
                s_ack_task = NULL;
            }
        }
    }

    if (!s_handlers_registered)
    {
        s_handlers_registered = true;

        ESP_ERROR_CHECK(esp_event_handler_register(
            LOADBALANCER_EVENTS,
            LOADBALANCER_EVENT_SLAVE_CURRENT_LIMIT,
            on_new_limit,
            NULL));

        ESP_ERROR_CHECK(esp_event_handler_register(
            AUTH_EVENTS,
            AUTH_EVENT_TAG_PROCESSED,
            on_auth_result,
            NULL));
    }

    if (poll_timer.timer == NULL)
    {
        poll_timer.timer = xTimerCreate("poll_tmr",
                                        poll_timer.interval,
                                        pdTRUE, NULL,
                                        poll_timer_cb);
        if (poll_timer.timer)
            (void)xTimerStart(poll_timer.timer, 0);
    }

    if (s_presence_timer == NULL)
    {
        s_presence_timer = xTimerCreate("presence_tmr",
                                        pdMS_TO_TICKS(PRESENCE_CHECK_MS),
                                        pdTRUE, NULL,
                                        presence_timer_cb);
        if (s_presence_timer)
            (void)xTimerStart(s_presence_timer, 0);
        else
            ESP_LOGE(TAG, "Failed to create presence timer");
    }
}
