#include <inttypes.h>
#include <string.h>
#include "evse_session.h"
#include "evse_meter.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "evse_events.h"
#include "esp_event.h"
#include "evse_limits.h"
#include "esp_timer.h"

#define EVSE_EVENT_POST_TIMEOUT_MS 50

static const char *TAG = "evse_session";

static TickType_t session_start_tick = 0;

// Tempo real (microsegundos)
static int64_t session_start_us = 0;
static int64_t last_tick_us = 0;

// Energia integrada com tempo real: soma de (W * us)
static uint64_t watt_microseconds = 0;

static evse_session_t last_session;
static bool last_session_valid = false;
static uint32_t session_counter = 0;
static char authorized_tag[AUTH_TAG_MAX_LEN];
static char current_session_tag[AUTH_TAG_MAX_LEN];

static portMUX_TYPE session_mux = portMUX_INITIALIZER_UNLOCKED;

static void post_session_event(const evse_session_event_data_t *evt)
{
    esp_err_t err = esp_event_post(
        EVSE_EVENTS,
        EVSE_EVENT_SESSION,
        evt,
        sizeof(*evt),
        portMAX_DELAY);

    if (err != ESP_OK)
    {
        ESP_LOGW(TAG, "esp_event_post(EVSE_EVENT_SESSION) failed: %s", esp_err_to_name(err));
    }
}

void evse_session_init(void)
{
    portENTER_CRITICAL(&session_mux);
    session_start_tick = 0;
    session_start_us = 0;
    last_tick_us = 0;
    watt_microseconds = 0;
    last_session_valid = false;
    session_counter = 0;
    authorized_tag[0] = '\0';
    current_session_tag[0] = '\0';
    portEXIT_CRITICAL(&session_mux);
}

void evse_session_set_authorized_tag(const char *tag)
{
    portENTER_CRITICAL(&session_mux);
    if (tag && tag[0] != '\0')
    {
        strncpy(authorized_tag, tag, AUTH_TAG_MAX_LEN - 1);
        authorized_tag[AUTH_TAG_MAX_LEN - 1] = '\0';
    }
    else
    {
        authorized_tag[0] = '\0';
    }
    portEXIT_CRITICAL(&session_mux);
}

void evse_session_clear_authorized_tag(void)
{
    evse_session_set_authorized_tag(NULL);
}

void evse_session_start(void)
{
    TickType_t tick = xTaskGetTickCount();
    int64_t now_us = esp_timer_get_time();

    portENTER_CRITICAL(&session_mux);
    session_start_tick = tick;
    session_start_us = now_us;
    last_tick_us = now_us;
    watt_microseconds = 0;
    session_counter++;
    uint32_t id = session_counter;
    strncpy(current_session_tag, authorized_tag, AUTH_TAG_MAX_LEN - 1);
    current_session_tag[AUTH_TAG_MAX_LEN - 1] = '\0';
    char tag[AUTH_TAG_MAX_LEN];
    strncpy(tag, current_session_tag, AUTH_TAG_MAX_LEN - 1);
    tag[AUTH_TAG_MAX_LEN - 1] = '\0';
    portEXIT_CRITICAL(&session_mux);

    evse_set_limit_reached(false);

    ESP_LOGI(TAG, "Session started (id=%" PRIu32 ", tag=%s) tick=%u us=%" PRId64,
             id, tag[0] ? tag : "-", (unsigned)tick, now_us);

    evse_session_event_data_t evt = {
        .type = EVSE_SESSION_EVENT_STARTED,
        .session_id = id,
        .duration_s = 0,
        .energy_wh = 0,
        .avg_power_w = 0,
        .is_current = true,
    };
    strncpy(evt.tag, tag, AUTH_TAG_MAX_LEN - 1);
    evt.tag[AUTH_TAG_MAX_LEN - 1] = '\0';
    post_session_event(&evt);
}

void evse_session_end(void)
{
    TickType_t start_tick;
    int64_t start_us;
    uint64_t w_us;
    uint32_t id;
    char tag[AUTH_TAG_MAX_LEN];

    int64_t end_us = esp_timer_get_time();

    portENTER_CRITICAL(&session_mux);
    if (session_start_tick == 0)
    {
        portEXIT_CRITICAL(&session_mux);
        ESP_LOGW(TAG, "evse_session_end called without active session");
        return;
    }

    start_tick = session_start_tick;
    start_us = session_start_us;
    w_us = watt_microseconds;
    id = session_counter;
    strncpy(tag, current_session_tag, AUTH_TAG_MAX_LEN - 1);
    tag[AUTH_TAG_MAX_LEN - 1] = '\0';

    session_start_tick = 0;
    session_start_us = 0;
    last_tick_us = 0;
    watt_microseconds = 0;
    current_session_tag[0] = '\0';
    portEXIT_CRITICAL(&session_mux);

    uint32_t duration_s = (end_us > start_us) ? (uint32_t)((end_us - start_us) / 1000000LL) : 0;
    uint32_t energy_wh = (uint32_t)(w_us / (3600ULL * 1000000ULL));
    uint64_t watt_seconds = (uint64_t)(w_us / 1000000ULL);
    uint32_t avg_power = (duration_s > 0) ? (uint32_t)(watt_seconds / duration_s) : 0;

    portENTER_CRITICAL(&session_mux);
    last_session.start_tick = start_tick;
    last_session.session_id = id;
    strncpy(last_session.tag, tag, AUTH_TAG_MAX_LEN - 1);
    last_session.tag[AUTH_TAG_MAX_LEN - 1] = '\0';
    last_session.duration_s = duration_s;
    last_session.energy_wh = energy_wh;
    last_session.avg_power_w = avg_power;
    last_session.is_current = false;
    last_session_valid = true;
    portEXIT_CRITICAL(&session_mux);

    ESP_LOGI(TAG,
             "Session ended (id=%" PRIu32 "): duration=%" PRIu32 " s, energy=%" PRIu32
             " Wh, avg_power=%" PRIu32 " W, tag=%s",
             id, duration_s, energy_wh, avg_power, tag[0] ? tag : "-");

    evse_session_event_data_t evt = {
        .type = EVSE_SESSION_EVENT_FINISHED,
        .session_id = id,
        .duration_s = duration_s,
        .energy_wh = energy_wh,
        .avg_power_w = avg_power,
        .is_current = false,
    };
    strncpy(evt.tag, tag, AUTH_TAG_MAX_LEN - 1);
    evt.tag[AUTH_TAG_MAX_LEN - 1] = '\0';
    post_session_event(&evt);
}

void evse_session_tick(void)
{
    // Potência instantânea pode ser negativa (ruído/overflow de sensor) -> clamp
    int p = evse_meter_get_instant_power();
    uint32_t power_w = (p > 0) ? (uint32_t)p : 0;

    int64_t now_us = esp_timer_get_time();

    portENTER_CRITICAL(&session_mux);
    if (session_start_tick != 0)
    {
        if (last_tick_us == 0)
        {
            last_tick_us = now_us;
        }
        int64_t dt_us = now_us - last_tick_us;
        if (dt_us > 0)
        {
            // Energia incremental: W * us (64-bit)
            watt_microseconds += ((uint64_t)power_w * (uint64_t)dt_us);
            last_tick_us = now_us;
        }
        else
        {
            // relógio não devia andar para trás; ignora
            last_tick_us = now_us;
        }
    }
    portEXIT_CRITICAL(&session_mux);
}

bool evse_session_get(evse_session_t *out)
{
    if (out == NULL)
        return false;

    TickType_t start_tick;
    int64_t start_us;
    uint64_t w_us;
    bool has_current;
    evse_session_t last_copy;
    bool last_valid;
    uint32_t id;
    char tag[AUTH_TAG_MAX_LEN];

    int64_t now_us = esp_timer_get_time();

    portENTER_CRITICAL(&session_mux);
    start_tick = session_start_tick;
    start_us = session_start_us;
    w_us = watt_microseconds;
    has_current = (session_start_tick != 0);
    id = session_counter;
    strncpy(tag, current_session_tag, AUTH_TAG_MAX_LEN - 1);
    tag[AUTH_TAG_MAX_LEN - 1] = '\0';
    last_copy = last_session;
    last_valid = last_session_valid;
    portEXIT_CRITICAL(&session_mux);

    if (has_current)
    {
        uint32_t duration_s = (now_us > start_us) ? (uint32_t)((now_us - start_us) / 1000000LL) : 0;
        uint32_t energy_wh = (uint32_t)(w_us / (3600ULL * 1000000ULL));
        uint64_t watt_seconds = (uint64_t)(w_us / 1000000ULL);
        uint32_t avg_power = (duration_s > 0) ? (uint32_t)(watt_seconds / duration_s) : 0;

        out->start_tick = start_tick;
        out->session_id = id;
        strncpy(out->tag, tag, AUTH_TAG_MAX_LEN - 1);
        out->tag[AUTH_TAG_MAX_LEN - 1] = '\0';
        out->duration_s = duration_s;
        out->energy_wh = energy_wh;
        out->avg_power_w = avg_power;
        out->is_current = true;
        return true;
    }

    if (last_valid)
    {
        *out = last_copy;
        return true;
    }

    return false;
}
