#include "auth.h"
#include "auth_events.h"
#include "esp_event.h"

#include <freertos/FreeRTOS.h>
#include <freertos/queue.h>
#include <freertos/task.h>

#include <esp_log.h>
#include <esp_err.h>
#include <string.h>
#include <strings.h>
#include <stdio.h>

#include "wiegand_reader.h"

#include "storage_service.h"

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

#define MAX_TAGS 50
static const char *TAG = "Auth";

/* ===== Estado ===== */
static auth_mode_t s_mode = AUTH_MODE_OPEN;
static bool waiting_for_registration = false;
static char valid_tags[MAX_TAGS][AUTH_TAG_MAX_LEN];
static int tag_count = 0;
static uint32_t s_next_req_id = 1;
static bool s_wiegand_started = false;

/* ===== Storage keys ===== */
#define NVS_NAMESPACE "auth"
#define NVS_TAG_PREFIX "tag_"
#define NVS_TAG_COUNT_KEY "count"
#define NVS_MODE_KEY "mode" // uint8_t

// timeout para operações sync do storage
#define STORAGE_TO pdMS_TO_TICKS(2000)

/* =========================
 * Helpers
 * ========================= */
static bool is_tag_valid(const char *tag)
{
    for (int i = 0; i < tag_count; i++)
    {
        if (strncmp(valid_tags[i], tag, AUTH_TAG_MAX_LEN) == 0)
            return true;
    }
    return false;
}

/* =========================
 * Storage Persistence (tags)
 * ========================= */
static void load_tags_from_storage(void)
{
    uint8_t count = 0;
    esp_err_t err = storage_get_u8_sync(NVS_NAMESPACE, NVS_TAG_COUNT_KEY, &count, STORAGE_TO);
    if (err == ESP_ERR_NOT_FOUND)
    {
        ESP_LOGD(TAG, "No stored tags (count not found)");
        tag_count = 0;
        return;
    }
    if (err != ESP_OK)
    {
        ESP_LOGW(TAG, "Failed to read tag count (%s)", esp_err_to_name(err));
        tag_count = 0;
        return;
    }

    tag_count = 0;
    for (int i = 0; i < (int)count && i < MAX_TAGS; i++)
    {
        char key[16];
        char tag_buf[AUTH_TAG_MAX_LEN] = {0};

        snprintf(key, sizeof(key), "%s%d", NVS_TAG_PREFIX, i);

        err = storage_get_str_sync(NVS_NAMESPACE, key, tag_buf, sizeof(tag_buf), STORAGE_TO);
        if (err == ESP_OK)
        {
            if (tag_buf[0] != '\0')
            {
                strncpy(valid_tags[tag_count], tag_buf, AUTH_TAG_MAX_LEN - 1);
                valid_tags[tag_count][AUTH_TAG_MAX_LEN - 1] = '\0';
                tag_count++;
            }
        }
        else if (err == ESP_ERR_NOT_FOUND)
        {
            // pode acontecer se count estiver desfasado; ignora
            continue;
        }
        else
        {
            ESP_LOGW(TAG, "Failed to load tag %d (%s)", i, esp_err_to_name(err));
        }
    }

    ESP_LOGI(TAG, "Loaded %d tags from storage", tag_count);
}

static void save_tags_to_storage(void)
{
    // ler count antigo (para apagar keys antigas se removemos tags)
    uint8_t old_count = 0;
    esp_err_t err = storage_get_u8_sync(NVS_NAMESPACE, NVS_TAG_COUNT_KEY, &old_count, STORAGE_TO);
    if (err == ESP_ERR_NOT_FOUND)
        old_count = 0;

    // grava count + tags
    (void)storage_set_u8_async(NVS_NAMESPACE, NVS_TAG_COUNT_KEY, (uint8_t)tag_count);

    for (int i = 0; i < tag_count; i++)
    {
        char key[16];
        snprintf(key, sizeof(key), "%s%d", NVS_TAG_PREFIX, i);
        (void)storage_set_str_async(NVS_NAMESPACE, key, valid_tags[i]);
    }

    // se removemos tags: apagar chaves antigas
    if (old_count > (uint8_t)tag_count)
    {
        for (int i = tag_count; i < (int)old_count && i < MAX_TAGS; i++)
        {
            char key[16];
            snprintf(key, sizeof(key), "%s%d", NVS_TAG_PREFIX, i);
            (void)storage_erase_key_async(NVS_NAMESPACE, key);
        }
    }

    // opcional: forçar commit “já”
    (void)storage_flush_async();

    ESP_LOGD(TAG, "Tags saved to storage (%d tags)", tag_count);
}

/* =========================
 * Storage Persistence (mode)
 * ========================= */
static void load_mode_from_storage(void)
{
    uint8_t u = (uint8_t)AUTH_MODE_OPEN;
    esp_err_t err = storage_get_u8_sync(NVS_NAMESPACE, NVS_MODE_KEY, &u, STORAGE_TO);

    if (err == ESP_OK)
    {
        if (u <= (uint8_t)AUTH_MODE_OCPP_RFID)
            s_mode = (auth_mode_t)u;
        else
            s_mode = AUTH_MODE_OPEN;
    }
    else if (err == ESP_ERR_NOT_FOUND)
    {
        s_mode = AUTH_MODE_OPEN;
        ESP_LOGD(TAG, "No stored mode -> default OPEN");
    }
    else
    {
        s_mode = AUTH_MODE_OPEN;
        ESP_LOGW(TAG, "Failed to read mode (%s) -> default OPEN", esp_err_to_name(err));
    }

    ESP_LOGI(TAG, "Loaded mode = %d (%s)", (int)s_mode, auth_mode_to_str(s_mode));
}

static void save_mode_to_storage(auth_mode_t mode)
{
    (void)storage_set_u8_async(NVS_NAMESPACE, NVS_MODE_KEY, (uint8_t)mode);
    (void)storage_flush_async(); // opcional: commit mais rápido
    ESP_LOGD(TAG, "Saved mode = %d (%s)", (int)mode, auth_mode_to_str(mode));
}

/* =========================
 * Bridge: EVSE-Link -> AUTH (remote AUTH_GRANTED no slave)
 * ========================= */
static void on_remote_auth_grant(void *arg, esp_event_base_t base, int32_t id, void *data)
{
    (void)arg;

    if (base != EVSE_LINK_EVENTS || id != LINK_EVENT_REMOTE_AUTH_GRANTED || data == NULL)
        return;

    const evse_link_auth_grant_event_t *src = (const evse_link_auth_grant_event_t *)data;

    if (evse_link_get_mode() != EVSE_LINK_MODE_SLAVE)
        return;

    auth_tag_event_data_t ev = {0};
    strncpy(ev.tag, src->tag, AUTH_TAG_MAX_LEN - 1);
    ev.tag[AUTH_TAG_MAX_LEN - 1] = '\0';
    ev.authorized = true;

    ESP_LOGD(TAG, "Remote auth grant on SLAVE for tag=%s", ev.tag);

    esp_err_t err = esp_event_post(
        AUTH_EVENTS,
        AUTH_EVENT_TAG_PROCESSED,
        &ev,
        sizeof(ev),
        portMAX_DELAY);

    if (err != ESP_OK)
    {
        ESP_LOGE(TAG, "Failed to post AUTH_EVENT_TAG_PROCESSED (remote grant): %s",
                 esp_err_to_name(err));
    }
}

/* =========================
 * Public API
 * ========================= */
void auth_init(void)
{
    // garantir que o storage service está pronto
    ESP_ERROR_CHECK(storage_service_init());

    load_mode_from_storage();
    load_tags_from_storage();

    bool need_wiegand = (s_mode == AUTH_MODE_LOCAL_RFID || s_mode == AUTH_MODE_OCPP_RFID);
    if (need_wiegand)
    {
        initWiegand();
        s_wiegand_started = true;
        ESP_LOGI(TAG, "Wiegand reader initialized (mode=%s)", auth_mode_to_str(s_mode));
    }
    else
    {
        ESP_LOGI(TAG, "Mode OPEN: Wiegand not started");
    }

    // bridge EVSE-Link -> AUTH
    {
        esp_err_t err = esp_event_handler_register(
            EVSE_LINK_EVENTS,
            LINK_EVENT_REMOTE_AUTH_GRANTED,
            on_remote_auth_grant,
            NULL);
        if (err != ESP_OK)
        {
            ESP_LOGE(TAG, "Failed to register EVSE-Link auth grant handler: %s",
                     esp_err_to_name(err));
        }
    }

    auth_mode_event_data_t evt = {.mode = s_mode};
    esp_event_post(AUTH_EVENTS, AUTH_EVENT_INIT, &evt, sizeof(evt), portMAX_DELAY);
    ESP_LOGI(TAG, "AUTH INIT sent (mode=%s)", auth_mode_to_str(s_mode));
}

void auth_set_mode(auth_mode_t mode)
{
    if (mode < AUTH_MODE_OPEN || mode > AUTH_MODE_OCPP_RFID)
    {
        ESP_LOGW(TAG, "Invalid mode: %d", (int)mode);
        return;
    }

    if (mode == s_mode)
    {
        ESP_LOGD(TAG, "Mode unchanged: %s", auth_mode_to_str(mode));
        return;
    }

    auth_mode_t old = s_mode;
    s_mode = mode;
    save_mode_to_storage(mode);

    bool need_wiegand = (s_mode == AUTH_MODE_LOCAL_RFID || s_mode == AUTH_MODE_OCPP_RFID);

    if (need_wiegand && !s_wiegand_started)
    {
        ESP_LOGD(TAG, "Mode changed %s -> %s, starting Wiegand",
                 auth_mode_to_str(old), auth_mode_to_str(s_mode));
        initWiegand();
        s_wiegand_started = true;
    }
    else if (!need_wiegand && s_wiegand_started)
    {
        ESP_LOGD(TAG, "Mode changed %s -> %s, Wiegand remains started (no deinit implemented)",
                 auth_mode_to_str(old), auth_mode_to_str(s_mode));
    }
    else
    {
        ESP_LOGD(TAG, "Mode changed %s -> %s, no change in Wiegand state",
                 auth_mode_to_str(old), auth_mode_to_str(s_mode));
    }

    auth_mode_event_data_t evt = {.mode = s_mode};
    esp_event_post(AUTH_EVENTS, AUTH_EVENT_MODE_CHANGED, &evt, sizeof(evt), portMAX_DELAY);
}

auth_mode_t auth_get_mode(void)
{
    return s_mode;
}

bool auth_add_tag(const char *tag)
{
    if (!tag || strlen(tag) >= AUTH_TAG_MAX_LEN)
        return false;
    if (tag_count >= MAX_TAGS)
        return false;

    if (is_tag_valid(tag))
        return true;

    strncpy(valid_tags[tag_count], tag, AUTH_TAG_MAX_LEN - 1);
    valid_tags[tag_count][AUTH_TAG_MAX_LEN - 1] = '\0';
    tag_count++;

    save_tags_to_storage();
    ESP_LOGD(TAG, "Tag added: %s", tag);
    return true;
}

bool auth_remove_tag(const char *tag)
{
    if (!tag)
        return false;

    for (int i = 0; i < tag_count; i++)
    {
        if (strncmp(valid_tags[i], tag, AUTH_TAG_MAX_LEN) == 0)
        {
            for (int j = i; j < tag_count - 1; j++)
            {
                strncpy(valid_tags[j], valid_tags[j + 1], AUTH_TAG_MAX_LEN);
                valid_tags[j][AUTH_TAG_MAX_LEN - 1] = '\0';
            }
            tag_count--;

            save_tags_to_storage();
            ESP_LOGD(TAG, "Tag removed: %s", tag);
            return true;
        }
    }
    return false;
}

bool auth_tag_exists(const char *tag)
{
    if (!tag)
        return false;
    return is_tag_valid(tag);
}

void auth_list_tags(void)
{
    ESP_LOGD(TAG, "Registered Tags (%d):", tag_count);
    for (int i = 0; i < tag_count; i++)
        ESP_LOGD(TAG, "- %s", valid_tags[i]);
}

void auth_wait_for_tag_registration(void)
{
    if (s_mode != AUTH_MODE_LOCAL_RFID)
    {
        ESP_LOGW(TAG, "Registration is only available in LOCAL mode");
        return;
    }
    waiting_for_registration = true;
    ESP_LOGD(TAG, "Tag registration mode enabled.");
}

void auth_process_tag(const char *tag)
{
    if (!tag || !*tag)
    {
        ESP_LOGW(TAG, "NULL/empty tag received");
        return;
    }

    switch (s_mode)
    {
    case AUTH_MODE_OPEN:
        ESP_LOGD(TAG, "Mode OPEN: tag=%s (no verification)", tag);
        break;

    case AUTH_MODE_LOCAL_RFID:
    {
        if (waiting_for_registration)
        {
            waiting_for_registration = false;

            if (auth_add_tag(tag))
            {
                auth_tag_event_data_t ev = {0};
                strncpy(ev.tag, tag, AUTH_TAG_MAX_LEN - 1);
                ev.tag[AUTH_TAG_MAX_LEN - 1] = '\0';
                ev.authorized = true;

                esp_event_post(AUTH_EVENTS, AUTH_EVENT_TAG_SAVED,
                               &ev, sizeof(ev), portMAX_DELAY);
                ESP_LOGD(TAG, "Tag registered: %s", tag);
            }
            else
            {
                ESP_LOGW(TAG, "Failed to register tag: %s", tag);
            }
            return;
        }

        auth_tag_event_data_t ev = {0};
        strncpy(ev.tag, tag, AUTH_TAG_MAX_LEN - 1);
        ev.tag[AUTH_TAG_MAX_LEN - 1] = '\0';
        ev.authorized = is_tag_valid(tag);

        ESP_LOGD(TAG, "LOCAL tag %s: %s", tag,
                 ev.authorized ? "AUTHORIZED" : "DENIED");

        esp_event_post(AUTH_EVENTS, AUTH_EVENT_TAG_PROCESSED,
                       &ev, sizeof(ev), portMAX_DELAY);
        break;
    }

    case AUTH_MODE_OCPP_RFID:
    {
        auth_tag_verify_event_t rq = {0};
        strncpy(rq.tag, tag, AUTH_TAG_MAX_LEN - 1);
        rq.tag[AUTH_TAG_MAX_LEN - 1] = '\0';
        rq.req_id = s_next_req_id++;

        ESP_LOGD(TAG, "OCPP VERIFY requested for tag=%s (req_id=%u)",
                 rq.tag, (unsigned)rq.req_id);

        esp_event_post(AUTH_EVENTS, AUTH_EVENT_TAG_VERIFY,
                       &rq, sizeof(rq), portMAX_DELAY);
        break;
    }
    }
}

int auth_get_tag_count(void)
{
    return tag_count;
}

const char *auth_get_tag_by_index(int index)
{
    if (index < 0 || index >= tag_count)
        return NULL;
    return valid_tags[index];
}
