#include "evse_error.h"

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/portmacro.h"
#include "esp_log.h"
#include "ntc_sensor.h"

#include "esp_event.h"
#include "esp_timer.h"
#include "evse_events.h"
#include "evse_config.h"

static const char *TAG = "evse_error";

// ----------------------------------------------------
// Estado interno
// ----------------------------------------------------
// raw_bits     = erros “instantâneos” conforme checks (set/clear)
// visible_bits = erros expostos ao resto do sistema (com holdoff)
// clear_deadline = quando pode finalmente limpar visible_bits para 0
static uint32_t   raw_bits = 0;
static uint32_t   visible_bits = 0;
static TickType_t clear_deadline = 0;

// Sticky flag: "todos erros visíveis foram limpos"
static bool error_cleared = false;

// Proteção contra concorrência
static portMUX_TYPE error_mux = portMUX_INITIALIZER_UNLOCKED;

// ----------------------------------------------------
// Helper: publicar evento de alteração de erro (visible_bits)
// ----------------------------------------------------
static void evse_error_post_event(uint32_t new_bits, uint32_t changed_mask)
{
    evse_error_event_data_t ev = {
        .error_bits    = new_bits,
        .changed_mask  = changed_mask,
        .timestamp_us  = esp_timer_get_time(),
    };

    esp_err_t err = esp_event_post(
        EVSE_EVENTS,
        EVSE_EVENT_ERROR_CHANGED,
        &ev,
        sizeof(ev),
        portMAX_DELAY);

    if (err != ESP_OK)
    {
        ESP_LOGW(TAG, "Falha ao publicar EVSE_EVENT_ERROR_CHANGED: %s",
                 esp_err_to_name(err));
    }
}

// ----------------------------------------------------
// Helpers internos
// ----------------------------------------------------
static bool raw_has_bit(uint32_t bit)
{
    bool v;
    portENTER_CRITICAL(&error_mux);
    v = ((raw_bits & bit) != 0);
    portEXIT_CRITICAL(&error_mux);
    return v;
}

static void reconcile_visible_locked(TickType_t now)
{
    // Se existem erros reais, o visível segue imediatamente
    if (raw_bits != 0)
    {
        visible_bits = raw_bits;
        clear_deadline = 0;
        error_cleared = false;
        return;
    }

    // raw_bits == 0
    if (visible_bits == 0)
    {
        clear_deadline = 0;
        return;
    }

    // Ainda há erro visível (holdoff). Arma deadline 1x.
    if (clear_deadline == 0)
    {
        clear_deadline = now + pdMS_TO_TICKS(EVSE_ERROR_COOLDOWN_MS);
        return;
    }

    // Expirou -> limpar finalmente
    if ((int32_t)(now - clear_deadline) >= 0)
    {
        visible_bits = 0;
        clear_deadline = 0;
        error_cleared = true;
    }
}

// ----------------------------------------------------
// API pública
// ----------------------------------------------------
void evse_error_init(void)
{
    uint32_t old_vis, new_vis, changed;
    bool post = false;

    portENTER_CRITICAL(&error_mux);

    old_vis = visible_bits;

    raw_bits = 0;
    visible_bits = 0;
    clear_deadline = 0;
    error_cleared = false;

    new_vis = visible_bits;
    changed = old_vis ^ new_vis;
    post = (changed != 0);

    portEXIT_CRITICAL(&error_mux);

    if (post)
    {
        evse_error_post_event(new_vis, changed);
    }
}

uint32_t evse_get_error(void)
{
    portENTER_CRITICAL(&error_mux);
    uint32_t val = visible_bits;
    portEXIT_CRITICAL(&error_mux);
    return val;
}

bool evse_error_is_active(void)
{
    return evse_get_error() != 0;
}

uint32_t evse_error_get_bits(void)
{
    return evse_get_error();
}

bool evse_error_cleared_flag(void)
{
    portENTER_CRITICAL(&error_mux);
    bool v = error_cleared;
    portEXIT_CRITICAL(&error_mux);
    return v;
}

void evse_error_reset_flag(void)
{
    portENTER_CRITICAL(&error_mux);
    error_cleared = false;
    portEXIT_CRITICAL(&error_mux);
}

void evse_error_set(uint32_t bitmask)
{
    uint32_t old_vis, new_vis, changed;
    TickType_t now = xTaskGetTickCount();

    portENTER_CRITICAL(&error_mux);

    old_vis = visible_bits;

    raw_bits |= bitmask;
    // se aparece qualquer erro, o "cleared" deixa de ser verdade
    error_cleared = false;

    reconcile_visible_locked(now);

    new_vis = visible_bits;
    changed = old_vis ^ new_vis;

    portEXIT_CRITICAL(&error_mux);

    if (changed != 0)
    {
        evse_error_post_event(new_vis, changed);
    }
}

void evse_error_clear(uint32_t bitmask)
{
    uint32_t old_vis, new_vis, changed;
    TickType_t now = xTaskGetTickCount();

    portENTER_CRITICAL(&error_mux);

    old_vis = visible_bits;

    raw_bits &= ~bitmask;

    // ✅ Aqui é onde o “60s depois do erro desaparecer” é armado:
    // quando raw_bits chega a 0, reconcile arma clear_deadline (uma vez)
    reconcile_visible_locked(now);

    new_vis = visible_bits;
    changed = old_vis ^ new_vis;

    portEXIT_CRITICAL(&error_mux);

    if (changed != 0)
    {
        evse_error_post_event(new_vis, changed);
    }
}

void evse_error_tick(void)
{
    uint32_t old_vis, new_vis, changed;
    TickType_t now = xTaskGetTickCount();

    portENTER_CRITICAL(&error_mux);

    old_vis = visible_bits;
    reconcile_visible_locked(now);
    new_vis = visible_bits;
    changed = old_vis ^ new_vis;

    portEXIT_CRITICAL(&error_mux);

    if (changed != 0)
    {
        evse_error_post_event(new_vis, changed);
    }
}

// ----------------------------------------------------
// Checks (raw -> set/clear)
// ----------------------------------------------------
void evse_error_check(pilot_voltage_t pilot_voltage, bool is_n12v)
{
    ESP_LOGD(TAG, "Verificando erro: pilot_voltage=%d, is_n12v=%s",
             pilot_voltage, is_n12v ? "true" : "false");

    // 1) Falha elétrica geral no pilot
    if (pilot_voltage == PILOT_VOLTAGE_1)
    {
        if (!raw_has_bit(EVSE_ERR_PILOT_FAULT_BIT))
        {
            ESP_LOGW(TAG, "Erro: pilot abaixo de 2V (falha)");
        }
        evse_error_set(EVSE_ERR_PILOT_FAULT_BIT);
    }
    else
    {
        evse_error_clear(EVSE_ERR_PILOT_FAULT_BIT);
    }

    // 2) Falta de -12V durante PWM (C ou D)
    if ((pilot_voltage == PILOT_VOLTAGE_6 || pilot_voltage == PILOT_VOLTAGE_3) && !is_n12v)
    {
        if (!raw_has_bit(EVSE_ERR_DIODE_SHORT_BIT))
        {
            ESP_LOGW(TAG, "Erro: ausência de -12V no PWM (sem diodo)");
        }
        evse_error_set(EVSE_ERR_DIODE_SHORT_BIT);
    }
    else
    {
        evse_error_clear(EVSE_ERR_DIODE_SHORT_BIT);
    }
}

void evse_temperature_check(void)
{
    float temp_c = ntc_temp_sensor();
    uint8_t threshold = evse_get_temp_threshold();

    ESP_LOGD(TAG, "Verificando temperatura: atual=%.2f °C, limite=%d °C",
             temp_c, threshold);

    // Temperatura inválida -> erro de sensor
    if (temp_c < -40.0f || temp_c > 150.0f)
    {
        if (!raw_has_bit(EVSE_ERR_TEMPERATURE_FAULT_BIT))
        {
            ESP_LOGW(TAG, "Sensor NTC falhou ou está desconectado");
        }
        evse_error_set(EVSE_ERR_TEMPERATURE_FAULT_BIT);
        return;
    }

    // Leitura válida -> limpa erro de sensor
    evse_error_clear(EVSE_ERR_TEMPERATURE_FAULT_BIT);

    // Temperatura máxima
    if (temp_c >= threshold)
    {
        if (!raw_has_bit(EVSE_ERR_TEMPERATURE_HIGH_BIT))
        {
            ESP_LOGW(TAG, "Temperatura acima do limite: %.2f °C ≥ %d °C",
                     temp_c, threshold);
        }
        evse_error_set(EVSE_ERR_TEMPERATURE_HIGH_BIT);
    }
    else
    {
        evse_error_clear(EVSE_ERR_TEMPERATURE_HIGH_BIT);
    }
}
