#include "evse_manager.h"
#include "evse_state.h"
#include "evse_error.h"
#include "evse_hardware.h"
#include "evse_config.h"
#include "evse_api.h"
#include "evse_meter.h"
#include "evse_session.h"

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

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

#include <string.h>
#include <inttypes.h>

#include "auth_events.h"
#include "loadbalancer_events.h"
#include "ocpp_events.h"
#include "scheduler_events.h"

static const char *TAG = "EVSE_Manager";

static SemaphoreHandle_t evse_mutex;

// ✅ Proteção para flags partilhadas (event handlers vs task)
static portMUX_TYPE s_mgr_mux = portMUX_INITIALIZER_UNLOCKED;
static bool auth_enabled = false;
static char s_pending_ocpp_tag[AUTH_TAG_MAX_LEN];

// Estado de pausa controlado pelo Load Balancer
static bool lb_paused = false;
static bool lb_prev_authorized = false;

// Estado de janela do scheduler
static bool s_sched_allowed = true;
static portMUX_TYPE s_sched_mux = portMUX_INITIALIZER_UNLOCKED;

#define EVSE_MANAGER_TICK_PERIOD_MS 1000 // 1 segundo

static uint16_t s_sched_current_a = 0;

static void lb_clear_pause_state(void)
{
    portENTER_CRITICAL(&s_mgr_mux);
    lb_paused = false;
    lb_prev_authorized = false;
    portEXIT_CRITICAL(&s_mgr_mux);
}

bool evse_sched_is_allowed(void)
{
    bool v;
    portENTER_CRITICAL(&s_sched_mux);
    v = s_sched_allowed;
    portEXIT_CRITICAL(&s_sched_mux);
    return v;
}

static void evse_manager_handle_auth_on_tick(void)
{
    bool sched_allowed = evse_sched_is_allowed();
    uint32_t err_bits = evse_get_error(); // inclui holdoff interno
    bool has_error = (err_bits != 0);

    bool local_auth_enabled;
    bool local_lb_paused;

    portENTER_CRITICAL(&s_mgr_mux);
    local_auth_enabled = auth_enabled;
    local_lb_paused = lb_paused;
    portEXIT_CRITICAL(&s_mgr_mux);

    if (local_auth_enabled)
    {
        if (evse_state_get_authorized() && evse_get_state() == EVSE_STATE_A)
        {
            ESP_LOGI(TAG, "Vehicle disconnected → revoking authorization.");
            evse_session_clear_authorized_tag();
            evse_state_set_authorized(false);
            lb_clear_pause_state();
        }

        if (has_error && evse_state_get_authorized())
        {
            ESP_LOGI(TAG,
                     "[AUTH] error active (err=0x%08" PRIx32 ") → revoking authorization.",
                     err_bits);
            evse_session_clear_authorized_tag();
            evse_state_set_authorized(false);
            lb_clear_pause_state();
        }

        if (!sched_allowed && evse_state_get_authorized())
        {
            ESP_LOGI(TAG, "[SCHED] window closed (auth mode) → revoking authorization.");
            evse_session_clear_authorized_tag();
            evse_state_set_authorized(false);
        }
    }
    else
    {
        bool limit_hit = evse_is_limit_reached();
        bool can_operate = evse_config_is_available() && evse_config_is_enabled();

        if ((has_error || limit_hit || !sched_allowed || !can_operate || local_lb_paused) &&
            evse_state_get_authorized())
        {
            ESP_LOGI(TAG,
                     "[OPEN] blocking (err=%d limit=%d sched=%d operate=%d lb_paused=%d) → revoking authorization.",
                     (int)has_error, (int)limit_hit, (int)sched_allowed, (int)can_operate, (int)local_lb_paused);
            evse_state_set_authorized(false);
        }

        if (!local_lb_paused && sched_allowed && can_operate &&
            !has_error && !limit_hit &&
            !evse_state_get_authorized())
        {
            evse_session_clear_authorized_tag();
            evse_state_set_authorized(true);
            ESP_LOGI(TAG, "Authentication disabled → forced authorization (schedule ok, no error/limits).");
            lb_clear_pause_state();
        }
    }
}

static void evse_manager_task(void *arg)
{
    (void)arg;
    while (true)
    {
        evse_manager_tick();
        vTaskDelay(pdMS_TO_TICKS(EVSE_MANAGER_TICK_PERIOD_MS));
    }
}

static void on_auth_event(void *arg, esp_event_base_t base, int32_t id, void *data)
{
    (void)arg;
    if (base != AUTH_EVENTS || !data)
        return;

    switch (id)
    {
    case AUTH_EVENT_TAG_PROCESSED:
    {
        const auth_tag_event_data_t *evt = (const auth_tag_event_data_t *)data;
        ESP_LOGI(TAG, "Tag %s -> %s", evt->tag, evt->authorized ? "AUTHORIZED" : "DENIED");
        if (evt->authorized)
        {
            evse_session_set_authorized_tag(evt->tag);
        }
        else
        {
            evse_session_clear_authorized_tag();
        }
        evse_state_set_authorized(evt->authorized);
        lb_clear_pause_state();
        break;
    }

    case AUTH_EVENT_TAG_VERIFY:
    {
        const auth_tag_verify_event_t *evt = (const auth_tag_verify_event_t *)data;
        ESP_LOGI(TAG, "Tag %s -> pending remote/OCPP verification", evt->tag);
        portENTER_CRITICAL(&s_mgr_mux);
        strncpy(s_pending_ocpp_tag, evt->tag, AUTH_TAG_MAX_LEN - 1);
        s_pending_ocpp_tag[AUTH_TAG_MAX_LEN - 1] = '\0';
        portEXIT_CRITICAL(&s_mgr_mux);
        break;
    }

    case AUTH_EVENT_MODE_CHANGED:
    case AUTH_EVENT_INIT:
    {
        const auth_mode_event_data_t *evt = (const auth_mode_event_data_t *)data;
        ESP_LOGI(TAG, "Auth mode = %s", auth_mode_to_str(evt->mode));

        portENTER_CRITICAL(&s_mgr_mux);
        auth_enabled = (evt->mode != AUTH_MODE_OPEN);
        s_pending_ocpp_tag[0] = '\0';
        portEXIT_CRITICAL(&s_mgr_mux);

        evse_session_clear_authorized_tag();
        evse_state_set_authorized(false);
        lb_clear_pause_state();
        break;
    }
    }
}

static void on_loadbalancer_event(void *handler_arg, esp_event_base_t event_base,
                                  int32_t event_id, void *event_data)
{
    (void)handler_arg;
    (void)event_base;

    if (!event_data)
        return;

    if (event_id == LOADBALANCER_EVENT_INIT || event_id == LOADBALANCER_EVENT_STATE_CHANGED)
    {
        const loadbalancer_state_event_t *evt = (const loadbalancer_state_event_t *)event_data;
        ESP_LOGI(TAG, "Loadbalancer %s (ts: %lld)",
                 evt->enabled ? "ENABLED" : "DISABLED",
                 (long long)evt->timestamp_us);
    }
    else if (event_id == LOADBALANCER_EVENT_MASTER_CURRENT_LIMIT)
    {
        const loadbalancer_master_limit_event_t *evt =
            (const loadbalancer_master_limit_event_t *)event_data;

        ESP_LOGI(TAG, "Novo limite de corrente (master): %u A (ts: %lld)",
                 evt->max_current, (long long)evt->timestamp_us);

        if (evt->max_current == 0)
        {
            bool prev_auth = evse_state_get_authorized();

            portENTER_CRITICAL(&s_mgr_mux);
            lb_paused = true;
            lb_prev_authorized = prev_auth;
            portEXIT_CRITICAL(&s_mgr_mux);

            if (prev_auth)
            {
                ESP_LOGI(TAG, "[LB] limit=0A → pausando sessão (authorized=false)");
                evse_state_set_authorized(false);
            }
        }
        else
        {
            evse_set_runtime_charging_current(evt->max_current);

            bool was_paused;
            bool prev_auth;

            portENTER_CRITICAL(&s_mgr_mux);
            was_paused = lb_paused;
            prev_auth = lb_prev_authorized;
            portEXIT_CRITICAL(&s_mgr_mux);

            if (was_paused)
            {
                bool can_resume =
                    (evse_get_error() == 0) &&
                    evse_config_is_available() &&
                    evse_config_is_enabled() &&
                    evse_sched_is_allowed() &&
                    !evse_is_limit_reached();

                if (!can_resume)
                {
                    ESP_LOGW(TAG,
                             "[LB] limit=%uA → não retoma automaticamente (erro/indisp/desab/fora de horário/limite)",
                             evt->max_current);
                    lb_clear_pause_state();
                    return;
                }

                bool local_auth_enabled;
                portENTER_CRITICAL(&s_mgr_mux);
                local_auth_enabled = auth_enabled;
                lb_paused = false; // já vai tentar retomar
                portEXIT_CRITICAL(&s_mgr_mux);

                if (!local_auth_enabled)
                {
                    ESP_LOGI(TAG, "[LB] limit=%uA → modo OPEN, reautorizando", evt->max_current);
                    evse_state_set_authorized(true);
                }
                else
                {
                    if (prev_auth)
                    {
                        ESP_LOGI(TAG, "[LB] limit=%uA → RFID/OCPP, retomando autorização anterior", evt->max_current);
                        evse_state_set_authorized(true);
                    }
                }

                portENTER_CRITICAL(&s_mgr_mux);
                lb_prev_authorized = false;
                portEXIT_CRITICAL(&s_mgr_mux);
            }
        }
    }
}

static void on_ocpp_event(void *arg, esp_event_base_t base, int32_t id, void *data)
{
    (void)arg;
    if (base != OCPP_EVENTS)
        return;

    switch (id)
    {
    case OCPP_EVENT_AUTHORIZED:
    {
        char tag[AUTH_TAG_MAX_LEN];

        portENTER_CRITICAL(&s_mgr_mux);
        strncpy(tag, s_pending_ocpp_tag, AUTH_TAG_MAX_LEN - 1);
        tag[AUTH_TAG_MAX_LEN - 1] = '\0';
        s_pending_ocpp_tag[0] = '\0';
        portEXIT_CRITICAL(&s_mgr_mux);

        if (tag[0] != '\0')
        {
            evse_session_set_authorized_tag(tag);
            ESP_LOGI(TAG, "[OCPP] Authorized tag=%s", tag);
        }
        else
        {
            ESP_LOGI(TAG, "[OCPP] Authorized");
        }

        evse_state_set_authorized(true);
        lb_clear_pause_state();
        break;
    }

    case OCPP_EVENT_AUTH_REJECTED:
    case OCPP_EVENT_AUTH_TIMEOUT:
    case OCPP_EVENT_REMOTE_STOP:
    case OCPP_EVENT_STOP_TX:
        ESP_LOGW(TAG, "[OCPP] Authorization/Stop");
        portENTER_CRITICAL(&s_mgr_mux);
        s_pending_ocpp_tag[0] = '\0';
        portEXIT_CRITICAL(&s_mgr_mux);
        evse_session_clear_authorized_tag();
        evse_state_set_authorized(false);
        lb_clear_pause_state();
        break;

    case OCPP_EVENT_REMOTE_START:
        ESP_LOGI(TAG, "[OCPP] RemoteStart");
        portENTER_CRITICAL(&s_mgr_mux);
        s_pending_ocpp_tag[0] = '\0';
        portEXIT_CRITICAL(&s_mgr_mux);
        evse_session_clear_authorized_tag();
        evse_state_set_authorized(true);
        lb_clear_pause_state();
        break;

    case OCPP_EVENT_START_TX:
        ESP_LOGI(TAG, "[OCPP] StartTx");
        lb_clear_pause_state();
        break;

    case OCPP_EVENT_OPERATIVE_UPDATED:
    {
        if (!data)
        {
            ESP_LOGW(TAG, "[OCPP] OperativeUpdated sem payload — ignorado");
            break;
        }
        const ocpp_operative_event_t *ev = (const ocpp_operative_event_t *)data;
        ESP_LOGI(TAG, "[OCPP] OperativeUpdated: operative=%d ts=%lld",
                 (int)ev->operative, (long long)ev->timestamp_us);

        evse_config_set_enabled(ev->operative);
        break;
    }

    default:
        ESP_LOGD(TAG, "[OCPP] Unhandled event id=%" PRId32, id);
        break;
    }
}

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

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

    portENTER_CRITICAL(&s_sched_mux);
    s_sched_allowed = ev->allowed_now;
    s_sched_current_a = ev->current_limit_a;
    portEXIT_CRITICAL(&s_sched_mux);

    ESP_LOGI(TAG, "[SCHED] allowed_now=%d current=%uA",
             (int)ev->allowed_now, (unsigned)ev->current_limit_a);

    if (!ev->allowed_now && evse_state_get_authorized())
    {
        ESP_LOGI(TAG, "[SCHED] window closed → stopping session (authorized=false)");
        evse_session_clear_authorized_tag();
        evse_state_set_authorized(false);
    }

    // ✅ aplica corrente quando permitido
    if (ev->allowed_now && ev->current_limit_a > 0)
    {
        evse_set_runtime_charging_current(ev->current_limit_a);
    }
}

void evse_manager_init(void)
{
    evse_mutex = xSemaphoreCreateMutex();
    configASSERT(evse_mutex != NULL);

    esp_err_t err = evse_config_init();
    if (err != ESP_OK)
    {
        ESP_LOGE(TAG, "Failed to init EVSE config NVS: %s", esp_err_to_name(err));
    }

    evse_error_init();
    evse_hardware_init();
    evse_state_init();
    evse_meter_init();
    evse_session_init();

    ESP_ERROR_CHECK(esp_event_handler_register(AUTH_EVENTS, ESP_EVENT_ANY_ID, &on_auth_event, NULL));
    ESP_ERROR_CHECK(esp_event_handler_register(LOADBALANCER_EVENTS, ESP_EVENT_ANY_ID, &on_loadbalancer_event, NULL));
    ESP_ERROR_CHECK(esp_event_handler_register(OCPP_EVENTS, ESP_EVENT_ANY_ID, &on_ocpp_event, NULL));
    ESP_ERROR_CHECK(esp_event_handler_register(SCHED_EVENTS, ESP_EVENT_ANY_ID, &on_sched_event, NULL));

    ESP_LOGI(TAG, "EVSE Manager inicializado.");

    BaseType_t rc = xTaskCreate(evse_manager_task, "evse_manager_task", 8192, NULL, 4, NULL);
    configASSERT(rc == pdPASS);
}

void evse_manager_tick(void)
{
    xSemaphoreTake(evse_mutex, portMAX_DELAY);

    evse_hardware_tick();
    evse_error_tick();
    evse_state_tick();
    evse_temperature_check();
    evse_session_tick();

    evse_manager_handle_auth_on_tick();

    xSemaphoreGive(evse_mutex);
}
